-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add README, and basic example * Add note about current known limitations
- Loading branch information
1 parent
5f9709e
commit 523f0a4
Showing
6 changed files
with
650 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
> [!CAUTION] | ||
> | ||
> # THIS LIBRARY IS CURRENTLY PRE-RELEASE | ||
> | ||
> `pkl-typescript` is currently major version `v0`, and **breaking changes will happen** between versions. | ||
> | ||
> Please read the section [Roadmap](#roadmap) below to learn more. | ||
# Pkl Bindings for TypeScript | ||
|
||
This library exposes TypeScript language bindings for the Pkl configuration language. | ||
|
||
These language bindings are made up of: | ||
|
||
- an "evaluator", that can execute Pkl code and deserialise the result into JavaScript runtime objects | ||
- the `pkl-gen-typescript` CLI, that can analyse a Pkl schema and generate matching TypeScript definitions | ||
|
||
Together, this allows you to embed Pkl into your TypeScript application, complete with code generation for full type safety and ease of use. | ||
|
||
## Getting Started | ||
|
||
First, install `pkl-typescript` from NPM: | ||
|
||
```bash | ||
npm install @pkl-community/pkl-typescript | ||
``` | ||
|
||
Then, generate a TypeScript file from your Pkl schema (eg. for a file called "config.pkl"): | ||
|
||
```bash | ||
npx pkl-gen-typescript config.pkl -o ./generated | ||
``` | ||
|
||
Lastly, in your TypeScript code, you can import the generated types and loader code: | ||
|
||
```typescript | ||
import { type Config, loadFromPath } from "./generated/config.pkl.ts"; | ||
|
||
const config: Config = await loadFromPath("config.pkl"); | ||
``` | ||
|
||
See more example usage in the [examples directory](./examples/). | ||
|
||
### Note on Schemas vs. Configs | ||
|
||
`pkl-gen-typescript` generates a TypeScript file based on Pkl's **type information** only, _not_ Pkl's runtime values. For example, a Pkl file with `x: String = "hello"` would produce the TypeScript type `x: string`. | ||
Conversely, the evaluator (used by the `loadFromPath(string)` function) evaluates a Pkl module that **renders values**. | ||
|
||
You may choose to have your Pkl schemas and values defined in separate Pkl files (eg. `schema.pkl` and `config.pkl`, where `config.pkl` starts with `amends "schema.pkl"`). In such a case, you would pass `schema.pkl` to `pkl-gen-typescript`, but then evaluate `config.pkl` at runtime (ie. `await loadFromPath("config.pkl")`). | ||
|
||
## Roadmap | ||
|
||
This library is currently in pre-release: we believe it is usable and productive in its current state, but not feature-complete, and not yet API-stable. | ||
|
||
We will keep the major version at `v0` until we are ready to commit to stability in: | ||
|
||
- the evaluator API (as provided by the `@pkl-community/pkl-typescript` NPM package) | ||
- the TypeScript type definitions generated by `pkl-gen-typescript` | ||
|
||
Until then, minor and patch releases may contain breaking changes. | ||
|
||
> [!WARNING] | ||
> **We strongly recommend** you regenerate your generated TypeScript code (with `pkl-gen-typescript`) **every time you upgrade** `@pkl-community/pkl-typescript`. If you don't, you may end up with unexpected runtime errors from type mismatches. | ||
### Known Current Limitations | ||
|
||
- **Inlined imports**: Imported Pkl types are inlined into the output TypeScript file. For example, if `foo.pkl` has an import like `import "bar.pkl"`, and you run `pkl-gen-typescript foo.pkl`, the resulting `foo.pkl.ts` file will include all types defined in `foo.pkl` _as well as_ all types defined in `bar.pkl`. This means that the resulting TypeScript generated files (in a multi-file codegen) will match the set of input root files, not the file structure of the source Pkl files. This behaviour may create unintended name conflicts; these can be resolved using the `@typescript.Name { value = "..." }` annotation. It may also cause duplication (eg. if the same shared Pkl library file is imported in two schemas); TypeScript's structural typing (where equivalent type shapes can be used interchangeably) should mean that any duplicate types can be safely used as each other. | ||
- **Regex deserialisation**: Pkl's `Regex` type will be decoded as a `pklTypescript.Regex` object, which contains a `.pattern` property. Pkl uses Java's regular expression syntax, which may not always be perfectly compatible with JavaScript's regular expression syntax. If you want to use your Pkl `Regex` as a JavaScript `RegExp`, and you are confident that the expression will behave the same way in JavaScript as in Pkl, you can instantiate a new `RegExp` using the `pklTypescript.Regex.pattern` property, eg. `const myConfigRegexp = new RegExp(myConfig.someRegex.pattern)`. | ||
- **IntSeq deserialisation**: Pkl's `IntSeq` type is intended to be used internally within a Pkl program to create a range loop. It is unlikely to be useful as a property type in JavaScript, and is therefore decoded into a custom `pklTypescript.IntSeq` type with signature `{ start: number; end: number: step: number }` - it is _not_ decoded into an array containing the ranged values. If you have a use-case to use `IntSeq` as an array of ranged values in a TypeScript program, please file a GitHub Issue. | ||
- **Duration and DataSize APIs**: Pkl has a rich API for many of its custom types, but two of note (that are not common in standard libraries of other languages) are `Duration` and `DataSize`, which include convenience APIs for eg. converting between units or summing values. These types are decoded into `pklTypescript.DataSize`/`pklTypescript.Duration` types (each of which have a `value` and `unit` property), and do not yet have the convenience APIs from Pkl. | ||
|
||
## Appendix | ||
|
||
### Type Mappings | ||
|
||
When code-generating TypeScript type definitions from Pkl schemas, each Pkl type is converted to an associated TypeScript type, as per the table below. While in pre-release, these mappings are subject to change! | ||
|
||
| Pkl type | TypeScript type | | ||
| ---------------- | -------------------------- | | ||
| Null | `null` | | ||
| Boolean | `boolean` | | ||
| String | `string` | | ||
| Int | `number` | | ||
| Int8 | `number` | | ||
| Int16 | `number` | | ||
| Int32 | `number` | | ||
| UInt | `number` | | ||
| UInt8 | `number` | | ||
| UInt16 | `number` | | ||
| UInt32 | `number` | | ||
| Float | `number` | | ||
| Number | `number` | | ||
| List<T> | `Array<T>` | | ||
| Listing<T> | `Array<T>` | | ||
| Map<K, V> | `Map<K, V>` | | ||
| Mapping<K, V> | `Map<K, V>` | | ||
| Set<T> | `Set<T>` | | ||
| Pair<A, B> | `pklTypescript.Pair<A, B>` | | ||
| Dynamic | `pklTypescript.Dynamic` | | ||
| DataSize | `pklTypescript.DataSize` | | ||
| Duration | `pklTypescript.Duration` | | ||
| IntSeq | `pklTypescript.IntSeq` | | ||
| Class | `interface` | | ||
| TypeAlias | `typealias` | | ||
| Any | `pklTypescript.Any` | | ||
| Unions (A\|B\|C) | `A\|B\|C` | | ||
| Regex | `pklTypescript.Regex` | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
firstName: String = "Phillip" | ||
lastName: String = "Pklton" | ||
age: Int8 = 76 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
// This file was generated by `pkl-typescript` from Pkl module `config`. | ||
// DO NOT EDIT. | ||
import * as pklTypescript from "@pkl-community/pkl-typescript" | ||
|
||
// Ref: Module root. | ||
export interface Config { | ||
firstName: string | ||
|
||
lastName: string | ||
|
||
age: number | ||
} | ||
|
||
// LoadFromPath loads the pkl module at the given path and evaluates it into a Config | ||
export const loadFromPath = async (path: string): Promise<Config> => { | ||
const evaluator = await pklTypescript.newEvaluator(pklTypescript.PreconfiguredOptions); | ||
try { | ||
const result = await load(evaluator, pklTypescript.FileSource(path)); | ||
return result | ||
} finally { | ||
evaluator.close() | ||
} | ||
}; | ||
|
||
export const load = (evaluator: pklTypescript.Evaluator, source: pklTypescript.ModuleSource): Promise<Config> => | ||
evaluator.evaluateModule(source) as Promise<Config>; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { type Config, loadFromPath } from "./generated/config.pkl"; | ||
|
||
const main = async () => { | ||
const config: Config = await loadFromPath("config.pkl"); | ||
console.log( | ||
`Hello, ${config.firstName} ${config.lastName}! I hear you are ${config.age} years old.` | ||
); | ||
}; | ||
|
||
main(); |
Oops, something went wrong.