See run-time type information on Wikipedia.
A type-safe schema system for describing TypeScript types at runtime and validating unknown values against them. The guide explains it one concept per page, in four areas: schemas, readers, outputs and in the project.
A schema here is an ordinary immutable value, built from a few constructors, and everything a consumer needs about a type is derived from that one value. Nothing derived can drift from the declaration, because there is no second declaration:
From one schema s | Where | |
|---|---|---|
| a decoder that builds a fresh value | parse(s)(value) | parse/ |
| a membership check that returns the value it was given | validate(s)(value) | validate/ |
| the static TypeScript type | Ts<typeof s> | ts/types.ts |
| the TypeScript type as text | printer()(s), dataToTs()(toData(s)) | ts/module.f.mjs |
a canonical form with equal, cmp, subset | toData(s) | data/ |
| a JSON Schema (draft 2020-12) | toJsonSchema(s) | ../media/json/schema |
import { array, number, open, option, or, string } from 'functionalscript/fjs/rtti/module.f.mjs'
import { parse } from 'functionalscript/fjs/rtti/parse/module.f.mjs'
import { validate } from 'functionalscript/fjs/rtti/validate/module.f.mjs'
const person = { name: string, age: number, tags: or(option, array(string)) }
// Ts<typeof person> is
// { readonly name: string, readonly age: number, readonly tags?: readonly string[] }
parse(person)({ name: 'Ann', age: 3 }) // ['ok', …]
parse(person)({ name: 'Ann' }) // ['error', { path: ['age'], … }]
parse(person)({ name: 'Ann', age: 3, x: 1 }) // ['error', …] — closed
parse(open(person))({ name: 'Ann', age: 3, x: 1 }) // ['ok', …] — open says so
The outputs area of the guide has a page for each row but the readers. The module page has a demo: pick a lesson or one of the project's own schemas, see it drawn as a graph with its TypeScript type, JSON Schema and canonical form, compare it with any other schema, and type a value to see what parse and validate make of it (demo.f.mjs).
Everything the repository reads from outside, and the compiler's own intermediate form, is declared as a schema: the protocols and MCP tools, the media formats and JSON itself, the expression graph, data-shaped effects, and the files and responses the tooling reads. Each of those pages names the modules and offers their schemas in its demo.
todo/rtti-type-system.md is the direction: RTTI as the single source of truth for FunctionalScript's types, checked at compile time through //: name annotations naming a schema, with .d.ts generated rather than written. The run-time half above exists; the compile-time half does not yet. The issues for this directory are in todo/.
rtti used to live under fjs/types/, but it is a peer of the modules that hold the data model — DataJS the model, rtti the types described over it — not a member of types/. Nothing under types/ imports it; every consumer (media, protocol, mcp, edag, ci, emergent_testing) is a peer of types/, the same relationship every outside consumer of types/list, types/result or types/object has. It does depend on several types/* modules (object, result, list, array, ts, phantom), but that is consumption, not membership — the rest of fjs/ depends on those the same way. Its own outward dependencies (fjs/asserts, fjs/js/keywords, fjs/media/datajs) point sideways to other top-level directories rather than down to a foundation types/ sits under, unlike the types/* modules that do reach outside (bigint, bit_vec, number, prime_field, string → fjs/common/monoid; uint8array → fjs/text). It was also the largest thing filed under types/, while its siblings there are single data structures and type-level helpers.
guide/— the concept pages: what a schema means, one page per conceptmodule.f.mjs— schema construction: definesType,Info, and schema builder valuests/types.ts— type-level transformer:Ts<T>maps a schema to its TypeScript type (seets/README.md)ts/module.f.mjs— the runtime printer: a schema's TypeScript type as text, through the data formcommon/module.f.mjs— shared kernel for runtime consumers: error shape, path bookkeeping, primitive checks,visit/orVisit(theTypedispatchers), and the container skeletons the two schema-form readers read throughparse/module.f.mjs— runtime deserialization:parse(schema)(value)returns a freshly constructed value containing only the declared fields/elementsvalidate/module.f.mjs— runtime validation:validate(schema)(value)returns the original value on success. Same acceptance asparse, so the two differ only in what a success carries — seeparseandvalidatedata/module.f.mjs— the serializable data form:toData(schema)converts a thunk-form schema into a function-free, canonical representation withcmp,equal,subset, and a data-drivenvalidate(seedata/README.md)demo.f.mjs— the module page's demo: every guide page's lessons, then schemas the project itself uses, imported from the modules that declare them (its module comment describes each part)schema-demo/module.f.mjs— the demo as a factory,schemaDemo, over the examples it offers: the module page's demo and every guide page's demo are this one function of their lessons
parse builds a value from what a schema declares, validate hands back the value it was given, and both accept exactly the same values. The readers area of the guide says what each is for, what a refusal carries, and what the readers assume of a value.