119 lines
4.9 KiB
Markdown
119 lines
4.9 KiB
Markdown
# 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:
|
|
|
|
```typescript
|
|
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:
|
|
|
|
```typescript
|
|
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:
|
|
|
|
```typescript
|
|
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`)
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
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. |