Last active
February 18, 2026 20:38
-
-
Save mikesol/2f9aaba142d262b5a4c4d808d6e90aaf to your computer and use it in GitHub Desktop.
Issue 190: Codex ur-spike 0
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { fromTypedAdj, type Desc, type Expr, type NodeEntry, type RuntimeNode } from "./spike-ur-core"; | |
| export interface DagBridge { | |
| expr: Expr<unknown, Desc<unknown, string, Record<string, NodeEntry<string, string[], unknown>>, string, true>>; | |
| } | |
| function nextIdRuntime(s: string): string { | |
| const chars = s.split(""); | |
| for (let i = chars.length - 1; i >= 0; i--) { | |
| if (chars[i] !== "z") { | |
| chars[i] = String.fromCharCode(chars[i].charCodeAt(0) + 1); | |
| return chars.join(""); | |
| } | |
| chars[i] = "a"; | |
| } | |
| return `a${chars.join("")}`; | |
| } | |
| export function toDAG(ast: any): DagBridge { | |
| const adj: Record<string, NodeEntry<string, string[], unknown>> = {}; | |
| const nodeMeta: Record<string, Pick<RuntimeNode, "fields" | "data">> = {}; | |
| let counter = "a"; | |
| const walk = (node: any): string => { | |
| const id = counter; | |
| counter = nextIdRuntime(counter); | |
| const children: string[] = []; | |
| const fields: Record<string, string | string[]> = {}; | |
| const data: Record<string, unknown> = {}; | |
| for (const [k, v] of Object.entries(node)) { | |
| if (k === "kind" || k.startsWith("__")) continue; | |
| if (Array.isArray(v) && v.length > 0 && typeof v[0] === "object" && v[0] !== null && "kind" in (v[0] as any)) { | |
| const ids = (v as any[]).map((c) => walk(c)); | |
| fields[k] = ids; | |
| children.push(...ids); | |
| } else if (typeof v === "object" && v !== null && "kind" in (v as any)) { | |
| const cid = walk(v); | |
| fields[k] = cid; | |
| children.push(cid); | |
| } else { | |
| data[k] = v; | |
| } | |
| } | |
| adj[id] = { kind: node.kind, children, out: undefined }; | |
| nodeMeta[id] = { fields, data }; | |
| return id; | |
| }; | |
| const root = walk(ast); | |
| const expr = fromTypedAdj(root, adj, counter); | |
| for (const [id, meta] of Object.entries(nodeMeta)) { | |
| const node = expr.__adj[id] as RuntimeNode; | |
| expr.__adj[id] = { ...node, fields: meta.fields, data: meta.data } as any; | |
| } | |
| return { expr }; | |
| } | |
| export function toAST(bridge: DagBridge): any { | |
| const materialize = (id: string): any => { | |
| const raw = bridge.expr.__adj[id]; | |
| if (!raw || raw.kind === "@alias") throw new Error(`missing node '${id}'`); | |
| const node = raw as RuntimeNode; | |
| const out: Record<string, unknown> = { kind: node.kind }; | |
| const fields = node.fields; | |
| if (!fields && node.children.length > 0) throw new Error(`missing fields metadata for node '${id}'`); | |
| const data = node.data || {}; | |
| for (const [k, v] of Object.entries(fields ?? {})) { | |
| out[k] = Array.isArray(v) ? v.map((cid) => materialize(cid)) : materialize(v as string); | |
| } | |
| for (const [k, v] of Object.entries(data)) out[k] = v; | |
| return out; | |
| }; | |
| return materialize(bridge.expr.__id); | |
| } | |
| export function withExpr(bridge: DagBridge, expr: DagBridge["expr"]): DagBridge { | |
| return { ...bridge, expr }; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { | |
| fromTypedAdj, | |
| named, | |
| type AdjOf, | |
| type Desc, | |
| type Expr, | |
| type IdOf, | |
| type NextID, | |
| type NodeEntry, | |
| } from "./spike-ur-core"; | |
| export type FirstID = "a"; | |
| type IncRuntime = (s: string) => string; | |
| const inc: IncRuntime = (s) => { | |
| const chars = s.split(""); | |
| for (let i = chars.length - 1; i >= 0; i--) { | |
| if (chars[i] !== "z") { | |
| chars[i] = String.fromCharCode(chars[i].charCodeAt(0) + 1); | |
| return chars.join(""); | |
| } | |
| chars[i] = "a"; | |
| } | |
| return `a${chars.join("")}`; | |
| }; | |
| export interface BuildCtx<C extends string> { | |
| numLit<V extends number>(value: V): [ | |
| Expr<number, Desc<number, C, Record<C, NodeEntry<"core/literal", [], number>>, NextID<C>, true>>, | |
| BuildCtx<NextID<C>>, | |
| ]; | |
| add<RA extends Desc<number, string, Record<string, any>, string, true>, RB extends Desc<number, string, Record<string, any>, string, true>>( | |
| left: Expr<number, RA>, | |
| right: Expr<number, RB>, | |
| ): [ | |
| Expr<number, Desc<number, C, AdjOf<RA> & AdjOf<RB> & Record<C, NodeEntry<"num/add", [IdOf<RA>, IdOf<RB>], number>>, NextID<C>, true>>, | |
| BuildCtx<NextID<C>>, | |
| ]; | |
| mul<RA extends Desc<number, string, Record<string, any>, string, true>, RB extends Desc<number, string, Record<string, any>, string, true>>( | |
| left: Expr<number, RA>, | |
| right: Expr<number, RB>, | |
| ): [ | |
| Expr<number, Desc<number, C, AdjOf<RA> & AdjOf<RB> & Record<C, NodeEntry<"num/mul", [IdOf<RA>, IdOf<RB>], number>>, NextID<C>, true>>, | |
| BuildCtx<NextID<C>>, | |
| ]; | |
| named<Name extends string, O, R extends Desc<O, string, Record<string, any>, string, true>>( | |
| name: Name, | |
| expr: Expr<O, R>, | |
| ): [Expr<O, ReturnType<typeof named<Name, O, R>> extends Expr<O, infer NR> ? NR : never>, BuildCtx<C>]; | |
| } | |
| function createCtx<C extends string>(counter: C): BuildCtx<C> { | |
| return { | |
| numLit(value: number) { | |
| const id = counter; | |
| const next = inc(counter); | |
| const expr = fromTypedAdj<number, any, any, any>( | |
| id, | |
| { | |
| [id]: { | |
| kind: "core/literal", | |
| children: [], | |
| out: value, | |
| fields: {}, | |
| data: { value }, | |
| }, | |
| }, | |
| next, | |
| ); | |
| return [expr, createCtx(next)] as any; | |
| }, | |
| add(left: Expr<any, any>, right: Expr<any, any>) { | |
| const id = counter; | |
| const next = inc(counter); | |
| const expr = fromTypedAdj<number, any, any, any>( | |
| id, | |
| { | |
| ...left.__adj, | |
| ...right.__adj, | |
| [id]: { | |
| kind: "num/add", | |
| children: [left.__id, right.__id], | |
| out: undefined, | |
| fields: { left: left.__id, right: right.__id }, | |
| data: {}, | |
| }, | |
| }, | |
| next, | |
| ); | |
| return [expr, createCtx(next)] as any; | |
| }, | |
| mul(left: Expr<any, any>, right: Expr<any, any>) { | |
| const id = counter; | |
| const next = inc(counter); | |
| const expr = fromTypedAdj<number, any, any, any>( | |
| id, | |
| { | |
| ...left.__adj, | |
| ...right.__adj, | |
| [id]: { | |
| kind: "num/mul", | |
| children: [left.__id, right.__id], | |
| out: undefined, | |
| fields: { left: left.__id, right: right.__id }, | |
| data: {}, | |
| }, | |
| }, | |
| next, | |
| ); | |
| return [expr, createCtx(next)] as any; | |
| }, | |
| named(name: string, expr: Expr<any, any>) { | |
| return [named(name, expr), createCtx(counter)] as any; | |
| }, | |
| } as BuildCtx<C>; | |
| } | |
| export function freshCtx(): BuildCtx<FirstID> { | |
| return createCtx("a" as FirstID); | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Unified ur-spike core: typed DAG, dirty/commit, predicates, transforms. | |
| import type { BuildRev, GCAdjBidir } from "./spike-ur-types"; | |
| export type NodeEntry<Kind extends string, ChildIDs extends readonly string[], Out> = { | |
| readonly kind: Kind; | |
| readonly children: ChildIDs; | |
| readonly out: Out; | |
| }; | |
| export type NameAlias<Name extends string, TargetID extends string, Out> = { | |
| readonly kind: "@alias"; | |
| readonly target: TargetID; | |
| readonly out: Out; | |
| readonly name: Name; | |
| }; | |
| export type Desc< | |
| Out, | |
| ID extends string, | |
| Adj extends Record<string, any>, | |
| C extends string, | |
| Clean extends boolean, | |
| Rev extends Record<string, any> = BuildRev<Adj>, | |
| > = { | |
| readonly o: Out; | |
| readonly id: ID; | |
| readonly adj: Adj; | |
| readonly c: C; | |
| readonly clean: Clean; | |
| readonly rev: Rev; | |
| }; | |
| declare const exprBrand: unique symbol; | |
| export type RuntimeNode = { kind: string; children: string[]; out: unknown; fields?: Record<string, string | string[]>; data?: Record<string, unknown> }; | |
| type RuntimeAlias = { kind: "@alias"; target: string; name: string }; | |
| export type RuntimeAdj = Record<string, RuntimeNode | RuntimeAlias>; | |
| export interface Expr<O, R extends Desc<O, string, Record<string, any>, string, true>> { | |
| readonly [exprBrand]: R; | |
| readonly __id: string; | |
| readonly __adj: RuntimeAdj; | |
| readonly __counter: string; | |
| } | |
| export interface DirtyExpr<O, R extends Desc<O, string, Record<string, any>, string, false>> { | |
| readonly [exprBrand]: R; | |
| readonly __id: string; | |
| readonly __adj: RuntimeAdj; | |
| readonly __counter: string; | |
| } | |
| export type OutOf<R> = R extends Desc<infer O, any, any, any, any> ? O : never; | |
| export type IdOf<R> = R extends Desc<any, infer ID, any, any, any> ? ID : never; | |
| export type AdjOf<R> = R extends Desc<any, any, infer Adj, any, any> ? Adj : never; | |
| export type CtrOf<R> = R extends Desc<any, any, any, infer C, any> ? C : never; | |
| export type RevOf<R> = R extends Desc<any, any, any, any, any, infer Rev> ? Rev : never; | |
| type ReplaceAt<Adj, Key extends string, Val> = Omit<Adj, Key> & Record<Key, Val>; | |
| type RemoveAt<Adj, Key extends string> = Omit<Adj, Key>; | |
| type IncLast<S extends string> = | |
| S extends `${infer R}a` ? `${R}b` : S extends `${infer R}b` ? `${R}c` : | |
| S extends `${infer R}c` ? `${R}d` : S extends `${infer R}d` ? `${R}e` : | |
| S extends `${infer R}e` ? `${R}f` : S extends `${infer R}f` ? `${R}g` : | |
| S extends `${infer R}g` ? `${R}h` : S extends `${infer R}h` ? `${R}i` : | |
| S extends `${infer R}i` ? `${R}j` : S extends `${infer R}j` ? `${R}k` : | |
| S extends `${infer R}k` ? `${R}l` : S extends `${infer R}l` ? `${R}m` : | |
| S extends `${infer R}m` ? `${R}n` : S extends `${infer R}n` ? `${R}o` : | |
| S extends `${infer R}o` ? `${R}p` : S extends `${infer R}p` ? `${R}q` : | |
| S extends `${infer R}q` ? `${R}r` : S extends `${infer R}r` ? `${R}s` : | |
| S extends `${infer R}s` ? `${R}t` : S extends `${infer R}t` ? `${R}u` : | |
| S extends `${infer R}u` ? `${R}v` : S extends `${infer R}v` ? `${R}w` : | |
| S extends `${infer R}w` ? `${R}x` : S extends `${infer R}x` ? `${R}y` : | |
| S extends `${infer R}y` ? `${R}z` : never; | |
| export type NextID<S extends string> = S extends `${infer R}z` ? (R extends "" ? "aa" : `${NextID<R>}a`) : IncLast<S>; | |
| function isAlias(v: RuntimeNode | RuntimeAlias): v is RuntimeAlias { | |
| return v.kind === "@alias"; | |
| } | |
| function nextIdRuntime(s: string): string { | |
| const chars = s.split(""); | |
| for (let i = chars.length - 1; i >= 0; i--) { | |
| if (chars[i] !== "z") { | |
| chars[i] = String.fromCharCode(chars[i].charCodeAt(0) + 1); | |
| return chars.join(""); | |
| } | |
| chars[i] = "a"; | |
| } | |
| return `a${chars.join("")}`; | |
| } | |
| // Internal bridge cast. Users never call this directly. | |
| function makeExpr(id: string, adj: RuntimeAdj, counter: string): any { | |
| return { __id: id, __adj: adj, __counter: counter }; | |
| } | |
| export function fromTypedAdj<O, ID extends string, Adj extends Record<string, NodeEntry<string, readonly string[], any>>, C extends string>( | |
| id: ID, | |
| adj: Adj, | |
| counter: C, | |
| ): Expr<O, Desc<O, ID, Adj, C, true>> { | |
| const rt: RuntimeAdj = {}; | |
| for (const [k, v] of Object.entries(adj)) { | |
| const meta = v as any; | |
| rt[k] = { | |
| kind: v.kind, | |
| children: [...v.children], | |
| out: v.out, | |
| fields: meta.fields, | |
| data: meta.data, | |
| }; | |
| } | |
| return makeExpr(id, rt, counter) as Expr<O, Desc<O, ID, Adj, C, true>>; | |
| } | |
| export function named<Name extends string, O, R extends Desc<O, string, Record<string, any>, string, true>>( | |
| name: Name, | |
| expr: Expr<O, R>, | |
| ): Expr<O, Desc<O, IdOf<R>, AdjOf<R> & Record<`@${Name}`, NameAlias<Name, IdOf<R>, O>>, CtrOf<R>, true>> { | |
| const adj: RuntimeAdj = { | |
| ...expr.__adj, | |
| [`@${name}`]: { kind: "@alias", target: expr.__id, name: String(name) }, | |
| }; | |
| return makeExpr(expr.__id, adj, expr.__counter) as Expr< | |
| O, | |
| Desc<O, IdOf<R>, AdjOf<R> & Record<`@${Name}`, NameAlias<Name, IdOf<R>, O>>, CtrOf<R>, true> | |
| >; | |
| } | |
| export function dirty<O, R extends Desc<O, string, Record<string, any>, string, true>>( | |
| expr: Expr<O, R>, | |
| ): DirtyExpr<O, Desc<O, IdOf<R>, AdjOf<R>, CtrOf<R>, false, RevOf<R>>> { | |
| return makeExpr(expr.__id, { ...expr.__adj }, expr.__counter) as DirtyExpr< | |
| O, | |
| Desc<O, IdOf<R>, AdjOf<R>, CtrOf<R>, false, RevOf<R>> | |
| >; | |
| } | |
| export function addEntry<O, R extends Desc<O, string, Record<string, any>, string, false>, ID extends string, E extends NodeEntry<string, readonly string[], any>>( | |
| expr: DirtyExpr<O, R>, | |
| id: ID, | |
| entry: E, | |
| ): DirtyExpr<O, Desc<O, IdOf<R>, AdjOf<R> & Record<ID, E>, CtrOf<R>, false>> { | |
| const meta = entry as any; | |
| const adj = { | |
| ...expr.__adj, | |
| [id]: { | |
| kind: entry.kind, | |
| children: [...entry.children], | |
| out: entry.out, | |
| fields: meta.fields, | |
| data: meta.data, | |
| }, | |
| }; | |
| return makeExpr(expr.__id, adj, expr.__counter) as DirtyExpr< | |
| O, | |
| Desc<O, IdOf<R>, AdjOf<R> & Record<ID, E>, CtrOf<R>, false, BuildRev<AdjOf<R> & Record<ID, E>>> | |
| >; | |
| } | |
| export function removeEntry<O, R extends Desc<O, string, Record<string, any>, string, false>, ID extends string>( | |
| expr: DirtyExpr<O, R>, | |
| id: ID, | |
| ): DirtyExpr<O, Desc<O, IdOf<R>, RemoveAt<AdjOf<R>, ID>, CtrOf<R>, false>> { | |
| const adj = { ...expr.__adj }; | |
| delete adj[id]; | |
| return makeExpr(expr.__id, adj, expr.__counter) as DirtyExpr< | |
| O, | |
| Desc<O, IdOf<R>, RemoveAt<AdjOf<R>, ID>, CtrOf<R>, false, BuildRev<RemoveAt<AdjOf<R>, ID>>> | |
| >; | |
| } | |
| export function swapEntry<O, R extends Desc<O, string, Record<string, any>, string, false>, ID extends string, E extends NodeEntry<string, readonly string[], any>>( | |
| expr: DirtyExpr<O, R>, | |
| id: ID, | |
| entry: E, | |
| ): DirtyExpr<O, Desc<O, IdOf<R>, ReplaceAt<AdjOf<R>, ID, E>, CtrOf<R>, false>> { | |
| const meta = entry as any; | |
| const adj = { | |
| ...expr.__adj, | |
| [id]: { | |
| kind: entry.kind, | |
| children: [...entry.children], | |
| out: entry.out, | |
| fields: meta.fields, | |
| data: meta.data, | |
| }, | |
| }; | |
| return makeExpr(expr.__id, adj, expr.__counter) as DirtyExpr< | |
| O, | |
| Desc<O, IdOf<R>, ReplaceAt<AdjOf<R>, ID, E>, CtrOf<R>, false, BuildRev<ReplaceAt<AdjOf<R>, ID, E>>> | |
| >; | |
| } | |
| export function rewireChildren<O, R extends Desc<O, string, Record<string, any>, string, false>>( | |
| expr: DirtyExpr<O, R>, oldRef: string, newRef: string, | |
| ): DirtyExpr<O, R> { | |
| return rewireChildrenExcept(expr, oldRef, newRef, new Set()); | |
| } | |
| function rewireChildrenExcept<O, R extends Desc<O, string, Record<string, any>, string, false>>( | |
| expr: DirtyExpr<O, R>, | |
| oldRef: string, | |
| newRef: string, | |
| skipIds: Set<string>, | |
| ): DirtyExpr<O, R> { | |
| const adj: RuntimeAdj = { ...expr.__adj }; | |
| for (const [id, e] of Object.entries(adj)) { | |
| if (isAlias(e) || id.startsWith("@") || skipIds.has(id)) continue; | |
| const children = e.children.map((c) => (c === oldRef ? newRef : c)); | |
| const fields = e.fields | |
| ? Object.fromEntries( | |
| Object.entries(e.fields).map(([k, v]) => [ | |
| k, | |
| Array.isArray(v) ? v.map((x) => (x === oldRef ? newRef : x)) : v === oldRef ? newRef : v, | |
| ]), | |
| ) | |
| : undefined; | |
| adj[id] = { ...e, children, fields }; | |
| } | |
| return makeExpr(expr.__id, adj, expr.__counter) as DirtyExpr<O, R>; | |
| } | |
| export function setRoot<O, R extends Desc<O, string, Record<string, any>, string, false>, ID extends string>( | |
| expr: DirtyExpr<O, R>, | |
| rootId: ID, | |
| ): DirtyExpr<O, Desc<O, ID, AdjOf<R>, CtrOf<R>, false, RevOf<R>>> { | |
| return makeExpr(rootId, { ...expr.__adj }, expr.__counter) as DirtyExpr< | |
| O, | |
| Desc<O, ID, AdjOf<R>, CtrOf<R>, false, RevOf<R>> | |
| >; | |
| } | |
| export function gc<O, R extends Desc<O, string, Record<string, any>, string, false>>( | |
| expr: DirtyExpr<O, R>, | |
| ): DirtyExpr< | |
| O, | |
| Desc< | |
| O, | |
| IdOf<R>, | |
| GCAdjBidir<AdjOf<R>, RevOf<R>, IdOf<R>>, | |
| CtrOf<R>, | |
| false, | |
| BuildRev<GCAdjBidir<AdjOf<R>, RevOf<R>, IdOf<R>>> | |
| > | |
| > { | |
| const live = new Set<string>(); | |
| const stack = [expr.__id]; | |
| while (stack.length) { | |
| const id = stack.pop() as string; | |
| if (live.has(id)) continue; | |
| live.add(id); | |
| const e = expr.__adj[id]; | |
| if (!e || isAlias(e)) continue; | |
| for (const c of e.children) stack.push(c); | |
| } | |
| for (const [k, e] of Object.entries(expr.__adj)) { | |
| if (isAlias(e) && live.has(e.target)) live.add(k); | |
| } | |
| const adj: RuntimeAdj = {}; | |
| for (const [k, e] of Object.entries(expr.__adj)) if (live.has(k)) adj[k] = e; | |
| return makeExpr(expr.__id, adj, expr.__counter) as DirtyExpr< | |
| O, | |
| Desc< | |
| O, | |
| IdOf<R>, | |
| GCAdjBidir<AdjOf<R>, RevOf<R>, IdOf<R>>, | |
| CtrOf<R>, | |
| false, | |
| BuildRev<GCAdjBidir<AdjOf<R>, RevOf<R>, IdOf<R>>> | |
| > | |
| >; | |
| } | |
| export function commit<O, R extends Desc<O, string, Record<string, any>, string, false>>( | |
| expr: DirtyExpr<O, R>, | |
| ): Expr<O, Desc<O, IdOf<R>, AdjOf<R>, CtrOf<R>, true>> { | |
| if (!(expr.__id in expr.__adj)) throw new Error(`commit: root '${expr.__id}' missing`); | |
| for (const [id, e] of Object.entries(expr.__adj)) { | |
| if (isAlias(e)) { | |
| if (!(e.target in expr.__adj)) throw new Error(`commit: alias '${id}' target '${e.target}' missing`); | |
| continue; | |
| } | |
| for (const c of e.children) if (!(c in expr.__adj)) throw new Error(`commit: node '${id}' refs missing '${c}'`); | |
| } | |
| return makeExpr(expr.__id, { ...expr.__adj }, expr.__counter) as Expr< | |
| O, | |
| Desc<O, IdOf<R>, AdjOf<R>, CtrOf<R>, true> | |
| >; | |
| } | |
| export function commitTyped<O, R extends Desc<O, string, Record<string, any>, string, false>>( | |
| expr: DirtyExpr<O, R>, | |
| ): Expr< | |
| O, | |
| Desc< | |
| O, | |
| IdOf<R>, | |
| GCAdjBidir<AdjOf<R>, RevOf<R>, IdOf<R>>, | |
| CtrOf<R>, | |
| true, | |
| BuildRev<GCAdjBidir<AdjOf<R>, RevOf<R>, IdOf<R>>> | |
| > | |
| > { | |
| return commit(gc(expr as any)) as any; | |
| } | |
| type PredEval = (e: RuntimeNode, id: string, adj: RuntimeAdj) => boolean; | |
| export type Pred = { eval: PredEval }; | |
| export type KindPred<K extends string> = Pred & { tag: "kind"; kind: K }; | |
| export type KindGlobPred<P extends string> = Pred & { tag: "glob"; prefix: P }; | |
| export type NamePred<N extends string> = Pred & { tag: "name"; name: N }; | |
| export type LeafPred = Pred & { tag: "leaf" }; | |
| export type CountPred<N extends number> = Pred & { tag: "count"; count: N }; | |
| export type NotPred<P extends Pred> = Pred & { tag: "not"; inner: P }; | |
| export type AndPred<A extends Pred, B extends Pred> = Pred & { tag: "and"; left: A; right: B }; | |
| export type OrPred<A extends Pred, B extends Pred> = Pred & { tag: "or"; left: A; right: B }; | |
| export function detachChildRef<O, R extends Desc<O, string, Record<string, any>, string, false>>( | |
| expr: DirtyExpr<O, R>, | |
| oldRef: string, | |
| ): DirtyExpr<O, R> { | |
| const adj: RuntimeAdj = { ...expr.__adj }; | |
| for (const [id, e] of Object.entries(adj)) { | |
| if (isAlias(e) || id.startsWith("@")) continue; | |
| if (!e.children.includes(oldRef)) continue; | |
| const children = e.children.filter((c) => c !== oldRef); | |
| const fields = e.fields | |
| ? (() => { | |
| const out: Record<string, string | string[]> = {}; | |
| for (const [k, v] of Object.entries(e.fields)) { | |
| if (Array.isArray(v)) { | |
| out[k] = v.filter((x) => x !== oldRef); | |
| } else if (v !== oldRef) { | |
| out[k] = v; | |
| } | |
| } | |
| return out; | |
| })() | |
| : undefined; | |
| adj[id] = { ...e, children, fields }; | |
| } | |
| return makeExpr(expr.__id, adj, expr.__counter) as DirtyExpr<O, R>; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { defaults, foldAST, mvfm } from "./packages/core/src"; | |
| import { num } from "./packages/core/src/plugins/num"; | |
| import { semiring } from "./packages/core/src/plugins/semiring"; | |
| import { | |
| AdjOf, | |
| addEntry, | |
| commitTyped, | |
| commit, | |
| dirty, | |
| fromTypedAdj, | |
| gc, | |
| named, | |
| removeEntry, | |
| type NodeEntry, | |
| } from "./spike-ur-core"; | |
| import { dagql } from "./spike-ur-fluent"; | |
| import { mapWhere, replaceWhere, spliceWhere, wrapByName, wrapWhere } from "./spike-ur-ops"; | |
| import { | |
| byKind, | |
| byKindGlob, | |
| byName, | |
| hasChildCount, | |
| isLeaf, | |
| not, | |
| } from "./spike-ur-predicates"; | |
| import { toAST, toDAG, withExpr } from "./spike-ur-bridge"; | |
| import { freshCtx } from "./spike-ur-buildctx"; | |
| import type { BuildRev, GCAdjBidir } from "./spike-ur-types"; | |
| function assert(cond: boolean, message: string): void { | |
| if (!cond) throw new Error(message); | |
| } | |
| async function main(): Promise<void> { | |
| console.log("=== Ur-spike: compile-time typed adjacency ==="); | |
| const typed = fromTypedAdj<number, "c", { | |
| a: NodeEntry<"num/literal", [], number>; | |
| b: NodeEntry<"num/literal", [], number>; | |
| c: NodeEntry<"num/add", ["a", "b"], number>; | |
| }, "d">( | |
| "c", | |
| { | |
| a: { kind: "num/literal", children: [], out: 3 }, | |
| b: { kind: "num/literal", children: [], out: 4 }, | |
| c: { kind: "num/add", children: ["a", "b"], out: 7 }, | |
| }, | |
| "d", | |
| ); | |
| const typedDirty = dirty(typed); | |
| addEntry(typedDirty, "x", { kind: "num/literal", children: [], out: 5 }); | |
| // @ts-expect-error children must be string IDs | |
| addEntry(typedDirty, "bad", { kind: "num/literal", children: [42], out: 0 }); | |
| console.log("type-level checks compile as expected"); | |
| type BidirAdj = { | |
| a: NodeEntry<"core/literal", [], number>; | |
| b: NodeEntry<"core/literal", [], number>; | |
| c: NodeEntry<"num/add", ["a", "b"], number>; | |
| x: NodeEntry<"core/literal", [], number>; | |
| }; | |
| type BidirRev = BuildRev<BidirAdj>; | |
| type BidirGC = GCAdjBidir<BidirAdj, BidirRev, "c">; | |
| const _bidirKeepsC: BidirGC["c"]["kind"] = "num/add"; | |
| // @ts-expect-error orphan x should be removed by bidir type-level gc | |
| const _bidirRemovesX: BidirGC["x"]["kind"] = "core/literal"; | |
| // commitTyped: compile-time graph validation path | |
| const _typedCommitOk = commitTyped(typedDirty); | |
| console.log("=== Ur-spike: public dirty/commit primitives ==="); | |
| const broken = removeEntry(typedDirty, "a"); | |
| let rejected = false; | |
| try { | |
| commit(broken); | |
| } catch { | |
| rejected = true; | |
| } | |
| assert(rejected, "commit should reject missing child refs"); | |
| const repaired = addEntry(broken, "a", { kind: "num/literal", children: [], out: 3 }); | |
| const clean = commit(gc(repaired)); | |
| assert(clean.__id === "c", "root stays c after repair"); | |
| console.log("dirty/commit path validated"); | |
| console.log("=== Ur-spike: mapWhere and predicate composition ==="); | |
| const mapped = mapWhere(clean, byKind("num/add"), (e) => ({ ...e, kind: "num/mul" as const })); | |
| type MappedDesc = (typeof mapped) extends import("./spike-ur-core").Expr<any, infer R> ? R : never; | |
| type MappedAdj = AdjOf<MappedDesc>; | |
| const _mappedKind: MappedAdj["c"]["kind"] = "num/mul"; | |
| // @ts-expect-error c is no longer num/add after mapWhere | |
| const _mappedKindWrong: MappedAdj["c"]["kind"] = "num/add"; | |
| const onlyLeafNums = dagql(mapped) | |
| .mapWhere( | |
| byKindGlob("num/"), | |
| (e, _id, _dag) => (e.children.length === 0 ? { ...e, out: (e.out as number) + 1 } : e), | |
| ) | |
| .result(); | |
| assert((onlyLeafNums.__adj.a as any).out === 4, "leaf a incremented"); | |
| assert((onlyLeafNums.__adj.b as any).out === 5, "leaf b incremented"); | |
| assert((onlyLeafNums.__adj.c as any).kind === "num/mul", "add replaced with mul"); | |
| const filtered = dagql(onlyLeafNums) | |
| .replaceWhere(not(isLeaf()), "num/mul") | |
| .replaceWhere(hasChildCount(2), "num/mul") | |
| .result(); | |
| assert((filtered.__adj.c as any).kind === "num/mul", "predicate combinators applied"); | |
| console.log("mapWhere and predicates validated"); | |
| console.log("=== Ur-spike: real usage with current API shape ==="); | |
| const app = mvfm(num, semiring); | |
| const prog = app(($) => $.mul($.add(3, 4), 5)); | |
| const interp = defaults(app); | |
| const base = await foldAST(interp, prog); | |
| assert(base === 35, `baseline expected 35, got ${String(base)}`); | |
| const bridged = toDAG(prog.ast); | |
| const transformedExpr = dagql(bridged.expr) | |
| .replaceWhere(byKind("num/add"), "num/mul") | |
| .result(); | |
| const transformedAst = toAST(withExpr(bridged, transformedExpr)); | |
| const transformedResult = await foldAST(interp, transformedAst); | |
| assert(transformedResult === 60, `transformed expected 60, got ${String(transformedResult)}`); | |
| console.log("app -> prog -> dagql transform -> foldAST works"); | |
| console.log("=== Ur-spike: BuildCtx direct typed construction ==="); | |
| const $ = freshCtx(); | |
| const [l3, $1] = $.numLit(3); | |
| const [l4, $2] = $1.numLit(4); | |
| const [sum, _] = $2.add(l3, l4); | |
| const sumAst = toAST({ expr: sum }); | |
| const sumResult = await foldAST(interp, sumAst); | |
| assert(sumResult === 7, `BuildCtx sum expected 7, got ${String(sumResult)}`); | |
| console.log("=== Ur-spike: named node targeting ==="); | |
| const namedExpr = named("sum-node", transformedExpr); | |
| const retargeted = replaceWhere(namedExpr, byName("sum-node"), "num/add"); | |
| assert((retargeted.__adj[retargeted.__id] as any).kind === "num/add", "byName retargeted named root"); | |
| assert((retargeted.__adj["@sum-node"] as any).target === retargeted.__id, "alias remains stable"); | |
| console.log("=== Ur-spike: wrapWhere root rewiring ==="); | |
| const wrappedRoot = wrapWhere(typed, byKind("num/add"), "debug/wrap"); | |
| assert(wrappedRoot.__id !== typed.__id, "wrapping root rewires root id to wrapper"); | |
| assert((wrappedRoot.__adj[wrappedRoot.__id] as any).children[0] === typed.__id, "wrapper points to old root"); | |
| console.log("=== Ur-spike: splice leaf rewiring ==="); | |
| const leafCase = fromTypedAdj<number, "d", { | |
| a: NodeEntry<"num/literal", [], number>; | |
| b: NodeEntry<"num/literal", [], number>; | |
| c: NodeEntry<"debug/marker", [], number>; | |
| d: NodeEntry<"num/add", ["a", "b", "c"], number>; | |
| }, "e">("d", { | |
| a: { kind: "num/literal", children: [], out: 1 }, | |
| b: { kind: "num/literal", children: [], out: 2 }, | |
| c: { kind: "debug/marker", children: [], out: 0 }, | |
| d: { kind: "num/add", children: ["a", "b", "c"], out: 3, fields: { values: ["a", "b", "c"] }, data: {} } as any, | |
| }, "e"); | |
| const leafSpliced = spliceWhere(leafCase, byKind("debug/marker")); | |
| assert((leafSpliced.__adj.d as any).children.length === 2, "leaf splice removes parent reference"); | |
| assert((leafSpliced.__adj.d as any).children.includes("c") === false, "leaf child id removed"); | |
| console.log("=== Ur-spike: wrapByName typed path ==="); | |
| const wrappedByName = wrapByName(namedExpr, "sum-node", "debug/wrap-one"); | |
| assert((wrappedByName.__adj[wrappedByName.__id] as any).kind === "debug/wrap-one", "wrapByName wrapped named root"); | |
| console.log("named transforms validated"); | |
| console.log("\nAll ur-spike checks passed."); | |
| } | |
| main().catch((err) => { | |
| console.error(err); | |
| process.exit(1); | |
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { | |
| type Desc, | |
| type Expr, | |
| type NodeEntry, | |
| type Pred, | |
| type RuntimeNode, | |
| } from "./spike-ur-core"; | |
| import { type DagView, mapWhere, replaceWhere, spliceWhere, wrapWhere } from "./spike-ur-ops"; | |
| export type DagQL<E extends Expr<any, any>> = { | |
| mapWhere<P extends Pred, NE extends NodeEntry<string, readonly string[], any>>( | |
| pred: P, | |
| fn: (entry: RuntimeNode, id: string, dag: DagView) => NE, | |
| ): DagQL<ReturnType<typeof mapWhere<any, any, P, NE>>>; | |
| wrapWhere(pred: Pred, kind: string): DagQL<ReturnType<typeof wrapWhere<any, any>>>; | |
| replaceWhere<P extends Pred>(pred: P, kind: string): DagQL<ReturnType<typeof replaceWhere<any, any, P>>>; | |
| spliceWhere(pred: Pred): DagQL<ReturnType<typeof spliceWhere<any, any>>>; | |
| result(): E; | |
| }; | |
| export function dagql<E extends Expr<any, any>>(expr: E): DagQL<E> { | |
| return { | |
| mapWhere(pred, fn) { | |
| return dagql(mapWhere(expr as any, pred, fn as any) as any); | |
| }, | |
| wrapWhere(pred, kind) { | |
| return dagql(wrapWhere(expr as any, pred, kind) as any); | |
| }, | |
| replaceWhere(pred, kind) { | |
| return dagql(replaceWhere(expr as any, pred, kind) as any); | |
| }, | |
| spliceWhere(pred) { | |
| return dagql(spliceWhere(expr as any, pred) as any); | |
| }, | |
| result() { | |
| return expr; | |
| }, | |
| }; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { | |
| addEntry, | |
| commitTyped, | |
| commit, | |
| detachChildRef, | |
| type RevOf, | |
| gc, | |
| dirty, | |
| removeEntry, | |
| rewireChildren, | |
| setRoot, | |
| swapEntry, | |
| type AdjOf, | |
| type CtrOf, | |
| type Desc, | |
| type DirtyExpr, | |
| type Expr, | |
| type IdOf, | |
| type NodeEntry, | |
| type Pred, | |
| type RuntimeAdj, | |
| type RuntimeNode, | |
| } from "./spike-ur-core"; | |
| import type { BuildRev, GCAdjBidir, MapAdj, MapOut, MatchingEntries, SelectKeys, WrapOneResult } from "./spike-ur-types"; | |
| type RuntimeAlias = { kind: "@alias"; target: string; name: string }; | |
| function isAlias(v: RuntimeNode | RuntimeAlias): v is RuntimeAlias { | |
| return (v as RuntimeAlias).kind === "@alias"; | |
| } | |
| function nextIdRuntime(s: string): string { | |
| const chars = s.split(""); | |
| for (let i = chars.length - 1; i >= 0; i--) { | |
| if (chars[i] !== "z") { | |
| chars[i] = String.fromCharCode(chars[i].charCodeAt(0) + 1); | |
| return chars.join(""); | |
| } | |
| chars[i] = "a"; | |
| } | |
| return `a${chars.join("")}`; | |
| } | |
| export interface DagView { | |
| get(id: string): RuntimeNode | RuntimeAlias | undefined; | |
| nodeIds(): string[]; | |
| parents(id: string): string[]; | |
| resolve(name: string): string; | |
| } | |
| function view(adj: RuntimeAdj): DagView { | |
| const parents: Record<string, string[]> = {}; | |
| for (const [id, e] of Object.entries(adj)) { | |
| if (isAlias(e)) continue; | |
| for (const c of e.children) (parents[c] ||= []).push(id); | |
| } | |
| return { | |
| get: (id) => adj[id], | |
| nodeIds: () => Object.keys(adj).filter((k) => !k.startsWith("@")), | |
| parents: (id) => parents[id] || [], | |
| resolve: (name) => ((adj[`@${name}`] as RuntimeAlias | undefined)?.target ?? ""), | |
| }; | |
| } | |
| export function selectWhere<O, R extends Desc<O, string, Record<string, any>, string, true>, P extends Pred>( | |
| expr: Expr<O, R>, | |
| pred: P, | |
| ): Set<SelectKeys<AdjOf<R>, P> & string> { | |
| const ids = new Set<string>(); | |
| for (const [id, e] of Object.entries(expr.__adj)) { | |
| if (!id.startsWith("@") && !isAlias(e) && pred.eval(e, id, expr.__adj)) ids.add(id); | |
| } | |
| return ids as Set<SelectKeys<AdjOf<R>, P> & string>; | |
| } | |
| export function mapWhere<O, R extends Desc<O, string, Record<string, any>, string, true>, P extends Pred, NE extends NodeEntry<string, readonly string[], any>>( | |
| expr: Expr<O, R>, pred: P, fn: (entry: MatchingEntries<AdjOf<R>, P> extends never ? RuntimeNode : MatchingEntries<AdjOf<R>, P>, id: SelectKeys<AdjOf<R>, P> & string, dag: DagView) => NE, | |
| ): Expr<MapOut<R, P, NE>, Desc<MapOut<R, P, NE>, IdOf<R>, GCAdjBidir<MapAdj<AdjOf<R>, P, NE>, BuildRev<MapAdj<AdjOf<R>, P, NE>>, IdOf<R>>, CtrOf<R>, true>> { | |
| const dag = view(expr.__adj); | |
| let out = dirty(expr) as DirtyExpr<O, Desc<O, string, Record<string, any>, string, false>>; | |
| for (const [id, e] of Object.entries(expr.__adj)) { | |
| if (id.startsWith("@") || isAlias(e) || !pred.eval(e, id, expr.__adj)) continue; | |
| const r = fn(e as any, id as any, dag); | |
| const merged = { | |
| kind: (r as any).kind, | |
| children: [...(r as any).children], | |
| out: (r as any).out, | |
| fields: (r as any).fields ?? e.fields, | |
| data: (r as any).data ?? e.data, | |
| }; | |
| out = swapEntry(out, id, merged as any); | |
| } | |
| return commitTyped(out as any) as Expr<MapOut<R, P, NE>, Desc<MapOut<R, P, NE>, IdOf<R>, GCAdjBidir<MapAdj<AdjOf<R>, P, NE>, BuildRev<MapAdj<AdjOf<R>, P, NE>>, IdOf<R>>, CtrOf<R>, true>>; | |
| } | |
| export function replaceWhere<O, R extends Desc<O, string, Record<string, any>, string, true>, P extends Pred>( | |
| expr: Expr<O, R>, pred: P, kind: string, | |
| ): Expr<MapOut<R, P, NodeEntry<string, readonly string[], unknown>>, Desc<MapOut<R, P, NodeEntry<string, readonly string[], unknown>>, IdOf<R>, GCAdjBidir<MapAdj<AdjOf<R>, P, NodeEntry<string, readonly string[], unknown>>, BuildRev<MapAdj<AdjOf<R>, P, NodeEntry<string, readonly string[], unknown>>>, IdOf<R>>, CtrOf<R>, true>> { | |
| return mapWhere(expr, pred, (e: any) => ({ | |
| kind, | |
| children: e.children, | |
| out: e.out, | |
| fields: e.fields, | |
| data: e.data, | |
| } as any)); | |
| } | |
| export function wrapWhere<O, R extends Desc<O, string, Record<string, any>, string, true>>( | |
| expr: Expr<O, R>, pred: Pred, wrapperKind: string, | |
| ): Expr<O, Desc<O, string, Record<string, any>, string, true>> { | |
| let out = dirty(expr) as DirtyExpr<O, Desc<O, string, Record<string, any>, string, false>>; | |
| let counter = expr.__counter; | |
| const wrapped: Record<string, string> = {}; | |
| for (const id of Array.from(selectWhere(expr, pred))) { | |
| const wid = counter; | |
| counter = nextIdRuntime(counter); | |
| const node = expr.__adj[id] as RuntimeNode; | |
| out = addEntry(out, wid, { kind: wrapperKind, children: [id], out: node.out, fields: { inner: id }, data: {} } as any); | |
| out = rewireChildren(out, id, wid); | |
| out = swapEntry(out, wid, { kind: wrapperKind, children: [id], out: node.out, fields: { inner: id }, data: {} } as any); | |
| wrapped[id] = wid; | |
| } | |
| out = setRoot(out, wrapped[expr.__id] ?? expr.__id); | |
| const committed = commitTyped(out as any) as any; | |
| return { ...committed, __counter: counter } as Expr<O, Desc<O, string, Record<string, any>, string, true>>; | |
| } | |
| export function wrapByName< | |
| O, | |
| R extends Desc<O, string, Record<string, any>, string, true>, | |
| Name extends string, | |
| WrapperKind extends string, | |
| >( | |
| expr: Expr<O, R>, | |
| name: Name, | |
| wrapperKind: WrapperKind, | |
| ): Expr<O, WrapOneResult<R, SelectKeys<AdjOf<R>, import("./spike-ur-core").NamePred<Name>> & string, WrapperKind>> { | |
| const target = ((expr.__adj[`@${name}`] as RuntimeAlias | undefined)?.target ?? "") as string; | |
| if (!target) throw new Error(`wrapByName: missing alias @${name}`); | |
| return wrapWhere(expr, { eval: (_e, id) => id === target } as Pred, wrapperKind) as any; | |
| } | |
| export function spliceWhere<O, R extends Desc<O, string, Record<string, any>, string, true>>( | |
| expr: Expr<O, R>, pred: Pred, | |
| ): Expr<O, Desc<O, string, Record<string, any>, string, true>> { | |
| let out = dirty(expr) as DirtyExpr<O, Desc<O, string, Record<string, any>, string, false>>; | |
| for (const id of Array.from(selectWhere(expr, pred))) { | |
| const e = out.__adj[id]; | |
| if (!e || isAlias(e)) continue; | |
| if (e.children.length > 1) throw new Error(`spliceWhere: '${id}' has ${e.children.length} children`); | |
| const replacement = e.children[0] ?? null; | |
| if (replacement) out = rewireChildren(out, id, replacement); | |
| else out = detachChildRef(out, id); | |
| out = removeEntry(out, id); | |
| if (expr.__id === id && replacement) out = setRoot(out, replacement); | |
| } | |
| return commitTyped(out as any) as any; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import type { | |
| AndPred, | |
| CountPred, | |
| KindGlobPred, | |
| KindPred, | |
| LeafPred, | |
| NamePred, | |
| NotPred, | |
| OrPred, | |
| Pred, | |
| } from "./spike-ur-core"; | |
| export const byKind = <K extends string>(kind: K): KindPred<K> => ({ | |
| tag: "kind", | |
| kind, | |
| eval: (e, _id) => e.kind === kind, | |
| }); | |
| export const byKindGlob = <P extends string>(prefix: P): KindGlobPred<P> => ({ | |
| tag: "glob", | |
| prefix, | |
| eval: (e, _id) => e.kind.startsWith(prefix), | |
| }); | |
| export const byName = <N extends string>(name: N): NamePred<N> => ({ | |
| tag: "name", | |
| name, | |
| eval: (_e, id, adj) => (adj[`@${name}`] as any)?.target === id, | |
| }); | |
| export const isLeaf = (): LeafPred => ({ | |
| tag: "leaf", | |
| eval: (e, _id) => e.children.length === 0, | |
| }); | |
| export const hasChildCount = <N extends number>(count: N): CountPred<N> => ({ | |
| tag: "count", | |
| count, | |
| eval: (e, _id) => e.children.length === count, | |
| }); | |
| export const not = <P extends Pred>(inner: P): NotPred<P> => ({ | |
| tag: "not", | |
| inner, | |
| eval: (e, id, adj) => !inner.eval(e, id, adj), | |
| }); | |
| export const and = <A extends Pred, B extends Pred>(left: A, right: B): AndPred<A, B> => ({ | |
| tag: "and", | |
| left, | |
| right, | |
| eval: (e, id, adj) => left.eval(e, id, adj) && right.eval(e, id, adj), | |
| }); | |
| export const or = <A extends Pred, B extends Pred>(left: A, right: B): OrPred<A, B> => ({ | |
| tag: "or", | |
| left, | |
| right, | |
| eval: (e, id, adj) => left.eval(e, id, adj) || right.eval(e, id, adj), | |
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import type { | |
| AdjOf, | |
| AndPred, | |
| CountPred, | |
| Desc, | |
| IdOf, | |
| KindGlobPred, | |
| KindPred, | |
| LeafPred, | |
| NameAlias, | |
| NamePred, | |
| NodeEntry, | |
| NotPred, | |
| OrPred, | |
| OutOf, | |
| CtrOf, | |
| } from "./spike-ur-core"; | |
| type NodeLike = NodeEntry<string, readonly string[], unknown>; | |
| type NameTarget<Adj, N extends string> = Adj extends Record<`@${N}`, infer A> | |
| ? A extends NameAlias<any, infer T, any> | |
| ? T & string | |
| : never | |
| : never; | |
| export type EvalPred<P, Entry, ID extends string, Adj> = | |
| P extends KindPred<infer K> | |
| ? Entry extends NodeEntry<K, any, any> | |
| ? true | |
| : false | |
| : P extends KindGlobPred<infer Prefix> | |
| ? Entry extends NodeEntry<`${Prefix}${string}`, any, any> | |
| ? true | |
| : false | |
| : P extends LeafPred | |
| ? Entry extends NodeEntry<any, infer C, any> | |
| ? C extends readonly [] | |
| ? true | |
| : false | |
| : false | |
| : P extends CountPred<infer N extends number> | |
| ? Entry extends NodeEntry<any, infer C, any> | |
| ? C extends { length: N } | |
| ? true | |
| : false | |
| : false | |
| : P extends NamePred<infer N extends string> | |
| ? ID extends NameTarget<Adj, N> | |
| ? true | |
| : false | |
| : P extends NotPred<infer A> | |
| ? EvalPred<A, Entry, ID, Adj> extends true | |
| ? false | |
| : true | |
| : P extends AndPred<infer A, infer B> | |
| ? EvalPred<A, Entry, ID, Adj> extends true | |
| ? EvalPred<B, Entry, ID, Adj> | |
| : false | |
| : P extends OrPred<infer A, infer B> | |
| ? EvalPred<A, Entry, ID, Adj> extends true | |
| ? true | |
| : EvalPred<B, Entry, ID, Adj> | |
| : false; | |
| export type SelectKeys<Adj, P> = keyof { | |
| [K in keyof Adj as K extends string | |
| ? Adj[K] extends NodeLike | |
| ? EvalPred<P, Adj[K], K, Adj> extends true | |
| ? K | |
| : never | |
| : never | |
| : never]: 1; | |
| }; | |
| export type MatchingEntries<Adj, P> = { | |
| [K in keyof Adj]: K extends string | |
| ? Adj[K] extends NodeLike | |
| ? EvalPred<P, Adj[K], K, Adj> extends true | |
| ? Adj[K] | |
| : never | |
| : never | |
| : never; | |
| }[keyof Adj]; | |
| export type MapAdj<Adj, P, NewEntry> = { | |
| [K in keyof Adj]: K extends string | |
| ? Adj[K] extends NodeLike | |
| ? EvalPred<P, Adj[K], K, Adj> extends true | |
| ? NewEntry | |
| : Adj[K] | |
| : Adj[K] | |
| : Adj[K]; | |
| }; | |
| export type MapOut<R extends Desc<any, string, Record<string, any>, string, true>, P, NewEntry> = | |
| IdOf<R> extends keyof AdjOf<R> | |
| ? EvalPred<P, AdjOf<R>[IdOf<R>], IdOf<R> & string, AdjOf<R>> extends true | |
| ? NewEntry extends NodeEntry<any, any, infer NO> | |
| ? NO | |
| : OutOf<R> | |
| : OutOf<R> | |
| : OutOf<R>; | |
| type ChildRefs<Adj> = { | |
| [K in keyof Adj]: Adj[K] extends NodeEntry<any, infer C, any> ? C[number] : never; | |
| }[keyof Adj]; | |
| export type MissingRefs<Adj> = Exclude<ChildRefs<Adj>, keyof Adj>; | |
| export type RootExists<R extends Desc<any, string, Record<string, any>, string, any>> = IdOf<R> extends keyof AdjOf<R> ? true : false; | |
| export type IsValidGraph<R extends Desc<any, string, Record<string, any>, string, any>> = | |
| RootExists<R> extends true | |
| ? MissingRefs<AdjOf<R>> extends never | |
| ? true | |
| : false | |
| : false; | |
| type RewireList<T extends readonly string[], Old extends string, New extends string> = | |
| T extends readonly [infer H extends string, ...infer Rest extends readonly string[]] | |
| ? readonly [H extends Old ? New : H, ...RewireList<Rest, Old, New>] | |
| : readonly []; | |
| export type RewireParents<Adj, TargetID extends string, WrapperID extends string> = { | |
| [K in keyof Adj]: Adj[K] extends NodeEntry<infer Kind, infer Children extends readonly string[], infer Out> | |
| ? NodeEntry<Kind, RewireList<Children, TargetID, WrapperID>, Out> | |
| : Adj[K]; | |
| }; | |
| export type WrapOneResult< | |
| R extends Desc<any, string, Record<string, any>, string, true>, | |
| TargetID extends string, | |
| WrapperKind extends string, | |
| > = Desc< | |
| OutOf<R>, | |
| IdOf<R> extends TargetID ? CtrOf<R> : IdOf<R>, | |
| RewireParents<AdjOf<R>, TargetID, CtrOf<R>> & Record<CtrOf<R>, NodeEntry<WrapperKind, readonly [TargetID], OutOf<R>>>, | |
| CtrOf<R>, | |
| true | |
| >; | |
| export type RevEntry<Parents extends readonly string[]> = { | |
| readonly parents: Parents; | |
| }; | |
| type ParentOf<Adj, Child extends string> = { | |
| [K in keyof Adj]: Adj[K] extends NodeEntry<any, infer C extends readonly string[], any> | |
| ? Child extends C[number] | |
| ? K | |
| : never | |
| : never; | |
| }[keyof Adj] & string; | |
| export type BuildRev<Adj extends Record<string, any>> = { | |
| [K in keyof Adj]: K extends string ? RevEntry<readonly ParentOf<Adj, K>[]> : never; | |
| }; | |
| type AnyParentLive< | |
| Rev extends Record<string, RevEntry<readonly string[]>>, | |
| Root extends string, | |
| Parents extends readonly string[], | |
| Visited extends string, | |
| > = Parents extends readonly [infer H extends string, ...infer T extends readonly string[]] | |
| ? IsLive<Rev, Root, H, Visited> extends true | |
| ? true | |
| : AnyParentLive<Rev, Root, T, Visited> | |
| : false; | |
| export type IsLive< | |
| Rev extends Record<string, RevEntry<readonly string[]>>, | |
| Root extends string, | |
| K extends string, | |
| Visited extends string = never, | |
| > = K extends Root | |
| ? true | |
| : K extends Visited | |
| ? false | |
| : K extends keyof Rev | |
| ? Rev[K] extends RevEntry<infer P extends readonly string[]> | |
| ? AnyParentLive<Rev, Root, P, Visited | K> | |
| : false | |
| : false; | |
| export type GCAdjBidir< | |
| Adj extends Record<string, any>, | |
| Rev extends Record<string, RevEntry<readonly string[]>>, | |
| Root extends string, | |
| > = { | |
| [K in keyof Adj as K extends string | |
| ? IsLive<Rev, Root, K> extends true | |
| ? K | |
| : never | |
| : never]: Adj[K]; | |
| }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment