4.9 KiB
Prior UJSX POC: Source Code Reference
Source: /workspace/aui/ujsx/ (Deno/JSR package @ade/ujsx)
Summary: /workspace/aui/SUMMARY.md
What to Preserve
Transform Registry (transform/registry.ts)
Priority-based transformation with continuation-passing style:
interface TransformRule<T, U, A> {
name: string;
match: (node: T) => boolean;
transform: (node: T, ctx: TransformContext<A>, next: TransformFn<T, U, A>) => U;
priority?: number;
}
Key pattern: next continuation allows recursive child transforms. Rules sorted by priority (higher = first). V2 extends this with direction and schema fields.
HostConfig + Reconciler (host/config.ts)
React-reconciler-inspired host adapter:
interface HostConfig<TTag, Instance, RootCtx> {
name: string;
createRootContext(container, options?): RootCtx;
createInstance(tag, props, ctx, parent?): Instance;
createTextInstance(text, ctx, parent?): Instance;
appendChild(parent, child, ctx): void;
insertBefore?(parent, child, before, ctx): void;
removeChild?(parent, child, ctx): void;
prepareUpdate?(instance, tag, prevProps, nextProps, ctx): unknown | null;
commitUpdate?(instance, payload, tag, prevProps, nextProps, ctx): void;
}
The createRoot() reconciler is mount-only (MVP). V2 needs full reconciliation with key-based diffing.
Graphology Host (host/graphology.ts)
Dirty bitmask pattern on graph nodes:
const DIRTY = { Props: 1<<0, Content: 1<<1, Structure: 1<<2 } as const;
Edges use parent->child#order keys. Version tracking on nodes. Issue: createInstance has commented-out append logic, prepareUpdate uses JSON.stringify diffing.
Streaming Transformer (streaming/transformer.ts)
Chunk-based async iterable processor. V2 preserves this but flush should pass real ancestor context instead of empty arrays.
What to Remove/Rewrite
UniversalProps (core/types.ts)
HTML-specific props that don't belong in a universal IR:
onClick,onSubmit,onInput,onChange(event handlers)className,class(HTML-specific)data-*,aria-*template keys__html(dangerouslySetInnerHTML)
V2 replaces with plain Record<string, unknown>.
genId() (core/jsx.ts)
function genId(): string {
return `e_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
}
Non-deterministic. V2 uses counter-based or injectable IDs.
Metadata Injection (core/jsx.ts)
Every element gets { timestamp: new Date(), id: genId() }. This is overhead without clear use in a universal IR. Move to host-specific concerns if needed.
TypeBox Research Examples
Source: /workspace/research/typebox_research/ujsx/
unist.ts - Unist schema as TypeBox Module
export const Unist = Type.Module({
Data: Type.Object({},{additionalProperties: Type.Unknown()}),
Point: Type.Object({ line: Type.Number(), column: Type.Number(), ... }),
Position: Type.Object({ start: Type.Ref('Point'), end: Type.Ref("Point") }),
Node: Type.Object({ type: Type.String(), data: Type.Optional(...), position: Type.Optional(...) }),
Literal: Type.Composite([Type.Ref("Node"), Type.Object({ value: Type.Unknown() })]),
Parent: Type.Composite([Type.Ref("Node"), Type.Object({ children: Type.Array(Type.Ref("Node")) })]),
})
ujsx.ts - UJSX schema as TypeBox Module
export const UJSX = Type.Module({
ElementMetadata: Type.Object({ id: Type.Optional(Type.String()), timestamp: Type.Optional(Type.Date()) }, { additionalProperties: Type.Unknown() }),
Children: Type.Union([Type.Ref("UniversalNode"), Type.Array(Type.Ref("UniversalNode"))]),
PropValue: Type.Union([Type.String(), Type.Number(), ..., Type.Function([...Type.Rest(Type.Array(Type.Unknown()))], Type.Void())]),
UniversalProps: Type.Object({ id: Type.Optional(Type.String()), children: Type.Optional(Type.Ref("Children")) }, { additionalProperties: Type.Union([Type.Ref("PropValue"), Type.Undefined()]) }),
RootElement: Type.Object({ type: Type.Literal("root"), props: Type.Intersect([...]), children: Type.Array(Type.Ref("UniversalNode")), ... }),
UniversalElement: Type.Object({ type: Type.Union([Type.String(), Type.Function([Type.Ref("UniversalProps")], Type.Ref("UniversalNode"))]), props: Type.Ref("UniversalProps"), children: Type.Array(Type.Ref("UniversalNode")), ... }),
})
Key insight: Type.Module creates a TModule whose $defs is a live string → TSchema map. Access via ValuePointer.Get(UJSX, "$defs/Children"). Add at runtime via ValuePointer.Set(). Import resolved schemas via UJSX.Import("Element").
Note: ElementMetadata is defined twice in the research file (duplicate). V2 should clean this up and likely drop metadata from the core schema entirely.
ts2typebox
CLI tool ts2typebox can convert TypeScript types to TypeBox defs but has issues with complex types (JSDoc comments, linked references). Useful for basic types, unreliable for complex ones like unist/mdast type definitions.