Reflection
Reflection gives you your TypeScript types at runtime: a short, stable id, or a RunType node you can walk. Validation, the JSON round trip and mocks read the same graph. Each type is one node, with a kind and links to the inner types it holds.
Getting a RunType Id
getRunTypeId returns a short, stable id for any type. Two types with the same shape share one id. Use it as a cache key, a registry key or a discriminator.
import {getRunTypeId} from '@mionjs/run-types';
// static form: pass the type, no value needed
const stringId = getRunTypeId<string>(); // e.g. "Sq3kZ1"
const userId = getRunTypeId<{id: number; name: string}>();
// value form: T is inferred from `order`, which is ignored at runtime
const order = {id: 1, total: 42};
const orderId = getRunTypeId(order);
export {stringId, userId, orderId};
getRunTypeId throws.Getting a RunType Node
getRunType<T>() returns the node for a type.
// pass the type, or a value to infer it from
const orderRT = getRunType<Order>();
console.log(orderRT.kind === RunTypeKind.objectLiteral); // true: an object shape
console.log(orderRT.children?.map((prop) => prop.name)); // ['id', 'total', 'items']
// a property's `child` is its own type
const itemsRT = orderRT.children?.find((prop) => prop.name === 'items');
console.log(itemsRT?.child?.kind === RunTypeKind.array); // true: items is an array
console.log(itemsRT?.child?.child?.kind === RunTypeKind.objectLiteral); // true: of {sku, qty}
RunType Node Fields
Every node has a kind, one of the RunTypeKind values. The rest depends on that kind, but the edges that hold inner types are always one of these:
childis the single inner type, like an array's element, a promise's value, or a property's type.childrenare the inner types of a container, like an object's properties, a union's members, or a tuple's slots.parametersandreturnare a function's argument nodes and its return node.
Members also carry name and optional, literals and enums carry literal and values, and a branded TypeFormat carries formatAnnotation.
A formatAnnotation holds the canonical format name (uuid, email, dateTime) and optional params with that format's constraints. To map a format onto a database column or a UI label, look its name up in typeFormats: every built-in format name with the base kind it refines. Import typeFormats and the FormatName union of its names from @mionjs/run-types/runtime.
Names come from your source, including tuple labels and function parameter names. Two tuples with the same element types but different labels are two reflected types, each with its own labels.
Walking the RunType Graph
A full walk is a switch over kind that recurses through those edges. This printer renders any node back to a TypeScript-like string.
function describe(rt: RunType): string {
switch (rt.kind as number) {
// leaves: no inner types
case RunTypeKind.string:
return 'string';
case RunTypeKind.number:
return 'number';
case RunTypeKind.boolean:
return 'boolean';
case RunTypeKind.bigint:
return 'bigint';
case RunTypeKind.symbol:
return 'symbol';
case RunTypeKind.null:
return 'null';
case RunTypeKind.undefined:
return 'undefined';
case RunTypeKind.void:
return 'void';
case RunTypeKind.never:
return 'never';
case RunTypeKind.any:
return 'any';
case RunTypeKind.unknown:
return 'unknown';
case RunTypeKind.object:
return 'object';
case RunTypeKind.regexp:
return 'RegExp';
case RunTypeKind.templateLiteral:
return 'string'; // a `${...}` template
case RunTypeKind.literal:
return JSON.stringify(rt.literal); // the value itself
case RunTypeKind.enum:
return (rt.values as unknown[])
.map((value) => JSON.stringify(value))
.join(' | ');
// single child: recurse into `child`
case RunTypeKind.array:
return `${describe(rt.child as RunType)}[]`;
case RunTypeKind.promise:
return `Promise<${describe(rt.child as RunType)}>`;
case RunTypeKind.rest:
return `...${describe(rt.child as RunType)}[]`;
case RunTypeKind.tupleMember:
return `${describe(rt.child as RunType)}${rt.optional ? '?' : ''}`;
// containers: recurse over `children`
case RunTypeKind.tuple:
return `[${(rt.children as RunType[]).map(describe).join(', ')}]`;
case RunTypeKind.union:
return (rt.children as RunType[]).map(describe).join(' | ');
case RunTypeKind.intersection:
return (rt.children as RunType[]).map(describe).join(' & ');
case RunTypeKind.objectLiteral:
return `{ ${(rt.children as RunType[]).map(describe).join('; ')} }`;
// members: `name`, `optional` and the member's own `child`
case RunTypeKind.property:
case RunTypeKind.propertySignature:
case RunTypeKind.parameter:
return `${rt.name as string}${rt.optional ? '?' : ''}: ${describe(rt.child as RunType)}`;
// a key type and a value type
case RunTypeKind.indexSignature:
return `[key: ${describe(rt.index as RunType)}]: ${describe(rt.child as RunType)}`;
// callables: recurse over `parameters` and `return`
case RunTypeKind.function:
case RunTypeKind.method:
case RunTypeKind.methodSignature:
case RunTypeKind.callSignature:
return `(${(rt.parameters as RunType[]).map(describe).join(', ')}) => ${describe(rt.return as RunType)}`;
// builtins (Date, Map, Set, Temporal) have no children: print the name
case RunTypeKind.class:
return rt.children
? `{ ${(rt.children as RunType[]).map(describe).join('; ')} }`
: String(rt.typeName ?? 'object');
// typeParameter, infer, ref, enumMember: rare in data shapes
default:
return `/* kind ${rt.kind} */`;
}
}
const orderRT = getRunType<Order>();
console.log(describe(orderRT));
// { id: string; total: number; items: { sku: string; qty: number }[]; status: "open" | "shipped" | "cancelled" }
subKind and a name instead of children, so treat them as leaves. Detect them by subKind (node.subKind === RunTypeSubKind.date), not by name: your own class could also be called Date. RunTypeSubKind comes from the package root, like RunTypeKind.RunType Node Kinds
A node's kind is always one of these, grouped by how you walk it:
Atomic (leaves) string number boolean bigint symbol null undefined void
never any unknown object regexp literal templateLiteral enum
Single child array promise rest (read child)
Containers tuple union intersection objectLiteral (read children)
Members property propertySignature parameter tupleMember indexSignature
Callables function method callSignature methodSignature (read parameters, return)
Class class, covering Date / URL / Map / Set / Temporal or a plain shape (read subKind)
Type level (rare) typeParameter infer enumMember ref