kriti-lang
Version:
A TypeScript implementation of the Kriti templating language
313 lines (247 loc) • 6.87 kB
Markdown
# Kriti Lang TypeScript
A TypeScript implementation of the **Kriti templating language** - a minimal JSON templating language inspired by Go's template language.
Kriti templates are a superset of JSON with path lookups, if/then/else expressions, loops, and some basic predicate and conditional operators.
> **Note**: This is a TypeScript port of the original [Kriti Lang](https://github.com/hasura/kriti-lang) implementation by Hasura, which is written in Haskell.
## Installation
```bash
npm install kriti-lang
```
## Quick Start
```typescript
import { evaluate } from 'kriti-lang';
const template = `{
"author": {
"name": {{$.event.name}},
"age": {{$.event.age}},
"articles": [
{{ range _, x := $.event.author.articles }}
{
"id": {{x.id}},
"title": {{x.title}}
}
{{ end }}
]
}
}`;
const data = {
"event": {
"name": "Freddie Jones",
"age": 27,
"author": {
"articles": [
{ "id": 0, "title": "The Elements", "length": 150, "published": true},
{ "id": 1, "title": "ARRL Handbook", "length": 1000, "published": true},
{ "id": 2, "title": "The Mars Trilogy", "length": 500, "published": false}
]
}
}
};
const result = evaluate(template, { $: data });
console.log(result);
// Output: Transformed JSON with author info and filtered articles
// {
// "author": {
// "name": "Freddie Jones",
// "age": 27,
// "articles": [
// {"id": 0, "title": "The Elements"},
// {"id": 1, "title": "ARRL Handbook"},
// {"id": 2, "title": "The Mars Trilogy"}
// ]
// }
// }
```
## Kriti Expressions
### Path Accessors
Values can be looked up in bound json expressions using the standard path lookup syntax:
```
{{ $body.foo.bar[0]['my key'] }}
```
- `.` is used to look up object fields
- `[x]` is used to lookup array indices
- `['a b c']` is used to lookup string literal object fields
If a variable is unbound, the kriti template fail and throw an exception. To prevent such failures, we provide an "optional" lookup operator:
```
{{ $body?.foo }}
```
This example will return a `null` if `foo` is not bound in `$body`. Optional lookups will immediately shortcircuit with `null` so that the following will not attempt any lookups past the unbound `foo`:
```
{{ $body?.foo.bar.baz }}
```
- `foo?` is used to optionally look up a variable.
- `?.` is used to optionally look up object fields
- `?[x]` is used to optionally lookup array indices
- `?['a b c']` is used to optionally lookup string literal object fields
### Defaulting Operator
The defaulting operator `??` can be used to replace a `null` value with any other value. The expression `null ?? true` will evaluate to `true`. This is especially useful when used with path lookups:
```
$foo?.bar ?? true
```
### Loops
The `range` identifier is used to declare for loops:
```
{{ range i, x := $.event.author.articles }}
{{ x.title }}
{{ end }}
```
`i` and `x` above are binders for the index and value of the array element from `$.event.author.articles`. The index can be omitted by using an underscore in its place.
### If Statements
Kriti supports if statements and `>` `<` `==` `||` and `&&` operators.
```
{{ if x.published && (x.post_id > 100) }}
{
"id": {{x.id}},
"title": {{x.title}}
}
{{ else }}
null
{{ end }}
```
Use `elif` for multiple conditionals.
```
{{ if x.published && (x.post_id > 100) }}
{
"id": {{x.id}},
"title": {{x.title}}
}
{{ elif x.published && (x.post_id <= 100) }}
{
"id": {{x.id}},
"title": {{x.title}},
"content": {{x.content}}
}
{{ else }}
null
{{ end }}
```
### String Interpolation
Bound variables, booleans, integers, object/array lookups, and functions can be interpolated:
```
"http://www.{{$.domain}}.com/{{$.path}}"
```
```
"http://www.{{$.domain}}.com/{{ $?[1000] }}"
```
## API Reference
### Main Functions
#### `evaluate(template, variables?, customFunctions?)`
Evaluates a Kriti template with the provided data.
```typescript
function evaluate(
input: string,
variables?: RuntimeObject,
customFunctions?: Map<string, CustomFunction>
): any
```
**Parameters:**
- `input` - The Kriti template string
- `variables` - Data object (typically passed as `{ $: yourData }`)
- `customFunctions` - Map of custom function implementations
### Low-Level API
For advanced use cases, you can use the individual components:
```typescript
import { tokenize, parseTokens, evaluateAST } from 'kriti-lang';
// Tokenization
const tokens = tokenize(template);
// Parsing
const ast = parseTokens(tokens);
// Evaluation
const result = evaluateAST(ast, variables, customFunctions);
```
### Custom Functions
Extend Kriti with your own functions:
```typescript
import { evaluate } from 'kriti-lang';
const customFunctions = new Map();
customFunctions.set('upperCase', (args) => {
return args[0]?.toString().toUpperCase();
});
const template = '{{ upperCase($.name) }}';
const result = evaluate(template, { $: { name: "alice" } }, customFunctions);
// Output: "ALICE"
```
## Transformation Examples
JSON Input:
```json
{
"event": {
"name": "Freddie Jones",
"age": 27,
"author": {
"articles": [
{ "id": 0, "title": "The Elements", "length": 150, "published": true},
{ "id": 1, "title": "ARRL Handbook", "length": 1000, "published": true},
{ "id": 2, "title": "The Mars Trilogy", "length": 500, "published": false}
]
}
}
}
```
Template Example:
```kriti
{
"author": {
"name": {{$.event.name}},
"age": {{$.event.age}},
"articles": [
{{ range _, x := $.event.author.articles }}
{
"id": {{x.id}},
"title": {{x.title}}
}
{{ end }}
]
}
}
```
JSON Output:
```json
{
"author": {
"name": "Freddie Jones",
"age": 27,
"articles": [
{"id": 0, "title": "The Elements"},
{"id": 1, "title": "ARRL Handbook"},
{"id": 2, "title": "The Mars Trilogy"}
]
}
}
```
Template Example 2:
```kriti
{
"author": {
"name": {{$.event.name}},
"age": {{$.event.age}},
"articles": [
{{ range _, x := $.event.author.articles }}
{{ if x.published }}
{
"id": {{x.id}},
"title": {{x.title}}
}
{{ else }}
null
{{ end }}
{{ end }}
]
}
}
```
JSON Output 2:
```json
{
"author": {
"name": "Freddie Jones",
"age": 27,
"articles": [
{"id": 0, "title": "The Elements"},
{"id": 1, "title": "ARRL Handbook"},
null
]
}
}
```
## Original Implementation
This TypeScript implementation is based on the original [Kriti Lang](https://github.com/hasura/kriti-lang) by Hasura, written in Haskell. For more information about the language specification and the original implementation, visit the [original repository](https://github.com/hasura/kriti-lang).