Chapter 1
Ref<T>: the branded phantom type
Ref<T> is the primitive everything else is built from. It carries two things at once:
- At compile time, a phantom type
T— the type of the value at the referenced path. - At runtime, an array of JSONPath segments, stored under a unique symbol.
Think of it as a typed pointer into the state data. Holding a Ref<number> means the compiler knows the value at that path is a number, even though at runtime there is no number — only the string "$.width".
type WidthRef = type Ref<T> = {
readonly __refType: T;
readonly [REF_PATH]: string[];
}
A branded reference that carries both:
- A phantom type `T` (compile-time only) representing the referenced value's type
- A path (runtime) representing the JSONPath segments to reach it
At runtime, a Ref is a Proxy object whose [REF_PATH] property returns
the accumulated path segments.Ref<number>;
__refType is the phantom brand. It never exists at runtime; it is there purely so that Ref<number> and Ref<string> are different types.
The runtime half
REF_PATH is a unique symbol used as the storage key. A symbol is used so the path can never collide with a real property name in the state data.
const const widthRef: Ref<number>widthRef = const ctx: Proxied<State>ctx.width: Ref<number>width;
const widthRef: Ref<number>widthRef[const REF_PATH: typeof REF_PATHSymbol used as a key to store the JSONPath segments on a Ref proxy.
Using a symbol prevents collisions with real property names.REF_PATH]; // ['$', 'width']
function pathOf(ref: Ref<unknown>): stringExtracts the JSONPath string from a Ref.
Converts the internal path segments into a dot-separated JSONPath string,
with array indices attached directly (no dot before brackets).pathOf(const widthRef: Ref<number>widthRef); // '$.width'
createProxy is the subject of the next chapter; for now the only thing that matters is that accessing .width produced something with a path on it. pathOf joins the segments into the JSONPath string that ASL wants.
The compile-time half
Both of these are, at runtime, the same kind of proxy object holding a path. The compiler treats them as unrelated:
type type State = {
width: number;
name: string;
}
State = { width: numberwidth: number; name: stringname: string };
const const ctx: Proxied<State>ctx = createProxy<State>(path?: string[]): Proxied<State>Creates a typed Proxy that records property access as JSONPath segments.
Every property access on the returned proxy returns a new proxy with
the property name appended to the path. Numeric keys (e.g. `[0]`) are
recorded as array index segments.createProxy<type State = {
width: number;
name: string;
}
State>();
const w = const ctx: Proxied<State>ctx.width: Ref<number>width;
const n = const ctx: Proxied<State>ctx.name: Ref<string>name;
That distinction is the entire point. In a payload mapping, { width: ctx.name } is a compile error while { width: ctx.width } is fine — even though both would serialize to a perfectly valid JSONPath string, and both would be accepted by ASL.
Checking at runtime
isRef is a type guard for the cases where a value could be either a ref or a plain literal — which is most payload fields, since they accept both.
function isRef(value: unknown): value is Ref<unknown>Type guard to check whether a value is a Ref (i.e. a proxy created by createProxy).isRef(const ctx: Proxied<{
x: number;
}>
ctx.x: Ref<number>x); // true
function isRef(value: unknown): value is Ref<unknown>Type guard to check whether a value is a Ref (i.e. a proxy created by createProxy).isRef(const ctx: Proxied<{
x: number;
}>
ctx); // true — the root is a Ref too
function isRef(value: unknown): value is Ref<unknown>Type guard to check whether a value is a Ref (i.e. a proxy created by createProxy).isRef(42); // false
function isRef(value: unknown): value is Ref<unknown>Type guard to check whether a value is a Ref (i.e. a proxy created by createProxy).isRef('$.x'); // false — a path string is not a Ref
function isRef(value: unknown): value is Ref<unknown>Type guard to check whether a value is a Ref (i.e. a proxy created by createProxy).isRef(null); // false
function isRef(value: unknown): value is Ref<unknown>Type guard to check whether a value is a Ref (i.e. a proxy created by createProxy).isRef({ path: stringpath: '$.x' }); // false — no REF_PATH symbol
The '$.x' case is worth noting: a hand-written path string is not a ref, and passing one where a ref is expected will not typecheck. That is deliberate — a string carries no type information, which is the problem this library exists to solve.