Skip to content

Custom functions ​

Typeflow ships a JSONata-level standard library with typed signatures (see Functions), plus use declarations to bring your own TypeScript functions into a mapping. Unknown functions are TF2007, wrong argument types are TF2008 — both at compile time.

When the standard library isn't enough, define your own functions. All three flavors are checked at compile time exactly like builtins — unknown names, wrong arity, and wrong argument types are compile errors.

In the language: fn ​

Define pure functions directly in the mapping — the body is a Typeflow expression over the parameters (the input is not in scope, so functions stay reusable and position-independent). The return type is optional: it is inferred from the body, and checked (TF2017) when declared. Functions can call builtins and functions declared above them — no forward references, so mappings stay terminating.

Fully serializable: fn definitions travel inside the compiled artifact, so they work everywhere the runtime does — including right here:

Mapping✔Playground ↗
Input JSON
Output
{
  "name": "Ada Lovelace",
  "grades": [
    {
      "value": 16,
      "grade": "A"
    },
    {
      "value": 9,
      "grade": "C"
    },
    {
      "value": 12,
      "grade": "B"
    }
  ]
}
inferred type{ name: string; grades: { value: number; grade: "A" | "B" | "C" }[]; }

From TypeScript: use ​

Declare the typed signature in the .typeflow file; the implementation is imported from the module when the mapping is loaded:

use slugify(value: string): string from "./helpers"
ts
// helpers
export function slugify(value: string): string {
  return value.toLowerCase().replace(/[^a-z0-9]+/g, '-');
}

loadTypeflowMapping("./user.typeflow") and typeflow run import ./helpers automatically. With the low-level runtime API, pass implementations explicitly — instantiation fails fast if one is missing:

ts
createMapping(compiled, { functions: { slugify } });

From your app: defineFunction ​

Register functions once, application-side — no use line needed in the mappings. A definition is declared like a builtin: typed signature, doc, implementation:

ts
import { defineFunction, compile, createMapping } from 'typeflowjs';

const slugify = defineFunction('slugify(value: string): string', {
  doc: 'Lowercase, dash-separated slug.',
  impl: (value) => String(value).toLowerCase().replace(/[^a-z0-9]+/g, '-'),
});

const result = compile(source, { functions: [slugify] });
const run = createMapping(result.compiled!, { functions: [slugify] });

Optional parameters work (clamp(n: number, max?: number): number), the compiled artifact records which registered functions the mapping actually calls, and createMapping fails fast if one is missing. loadTypeflowMapping(path, { functions }) accepts them too.

Try it live ​

The playground can't import files, so it provides two demo implementations: slugify and capitalize (note the ? — optional parameters are supported):

Mapping✔Playground ↗
Input JSON
Output
{
  "handle": "ada-lovelace",
  "display": "Ada"
}
inferred type{ handle: string; display: string }

Signatures are enforced like builtins — try slugify(42) above (TF2008), or rename a use to upper (TF2016: conflicts with a builtin).

This example is intentionally broken ​

Unknown functions are compile errors, and the diagnostic lists what's available:

Mapping✖ 1Playground ↗
Input JSON
Output
// Fix the mapping errors to see the output.
inferred type{ name: any }
  • 4:9errorTF2007Unknown function 'capitalizeWords'.
    hint: Available functions: string, length, contains, matches, upper, lower, trim, pad, substring, split, replace, base64encode, base64decode, encodeUrl, decodeUrl, encodeUrlComponent, decodeUrlComponent, number, formatBase, floor, ceil, round, abs, power, sqrt, random, sum, max, min, average, boolean, not, exists, count, join, append, zip, sort, reverse, shuffle, distinct, keys, values, lookup, merge, spread, type, now, millis, fromMillis, toMillis.

Released under the MIT License.