import { assert, assertEquals, assertStringIncludes } from "@std/assert"; import ts from "typescript"; import { COMMONFABRIC_TYPES } from "./commonfabric-test-types.ts"; import { callSchemas, callsNamed, collect, emittedSchemas, literalToValue, parseModule, patternSchemas, } from "./transformed-ast.ts"; import { transformSource, validateSource } from "./utils.ts"; // Unit coverage for schema-injection.ts. These tests drive the whole // transformer pipeline with `/// ` pattern sources that exercise // specific schema shapes and builder call-site forms, then assert on the // emitted schema objects. The transformer injects generated JSON schemas at // pattern / handler / lift / toSchema / cell / wish / generateObject / // sqliteQuery call sites; the assertions below pin the concrete emitted schema // for each case (property types, `required` arrays, `asCell` markers, `$ref` // for VNode, default / optional handling, nested / array / tuple / union / // record shapes, scope markers, and so on). function t(source: string): Promise { return transformSource(source, { types: COMMONFABRIC_TYPES }); } // Every `... satisfies ...JSONSchema` expression under `root`, including // non-object ones such as `false as const satisfies __cfHelpers.JSONSchema`, // evaluated in source order. `emittedSchemas` drops the non-object values, so // tests that need to see a `false` schema use this instead. function allEmittedSchemaValues(root: ts.SourceFile): unknown[] { return collect(root, ts.isSatisfiesExpression) .filter((node) => /JSONSchema/.test(node.type.getText(root))) .map((node) => literalToValue(node.expression)); } // deno-lint-ignore no-explicit-any type Obj = Record; // // pattern — property type schemas // Deno.test("pattern schema encodes primitive property types and a required array", async () => { const source = [ "/// ", 'import { pattern, UI, VNode } from "commonfabric";', "interface Input { name: string; count: number; ok: boolean; }", "interface Output { [UI]: VNode; }", "export default pattern((s) => ({ [UI]: s.name as unknown as VNode }));", ].join("\n"); const output = await t(source); const { input } = patternSchemas(parseModule(output)); const props = input.properties as Obj; assertEquals(props.name.type, "string"); assertEquals(props.count.type, "number"); assertEquals(props.ok.type, "boolean"); assertEquals(input.required, ["name", "count", "ok"]); }); Deno.test("pattern schema omits optional properties from the required array", async () => { const source = [ "/// ", 'import { pattern, UI, VNode } from "commonfabric";', "interface Input { a: string; b?: number; }", "interface Output { [UI]: VNode; }", "export default pattern((s) => ({ [UI]: s.a as unknown as VNode }));", ].join("\n"); const output = await t(source); const { input } = patternSchemas(parseModule(output)); // `b` is present as a property but excluded from required. assert(Object.keys(input.properties as Obj).includes("b")); assertEquals(input.required, ["a"]); }); Deno.test("pattern schema encodes a Default<> wrapper as a JSON schema default", async () => { const source = [ "/// ", 'import { pattern, Default, UI, VNode } from "commonfabric";', 'interface Input { name: Default; }', "interface Output { [UI]: VNode; }", "export default pattern((s) => ({ [UI]: s.name as unknown as VNode }));", ].join("\n"); const output = await t(source); const { input } = patternSchemas(parseModule(output)); assertEquals((input.properties as Obj).name.default, "seed"); }); Deno.test("pattern schema encodes array item types", async () => { const source = [ "/// ", 'import { pattern, UI, VNode } from "commonfabric";', "interface Input { tags: string[]; }", "interface Output { [UI]: VNode; }", "export default pattern((s) => ({ [UI]: s.tags as unknown as VNode }));", ].join("\n"); const output = await t(source); const { input } = patternSchemas(parseModule(output)); const tags = (input.properties as Obj).tags; assertEquals(tags.type, "array"); assertEquals(tags.items.type, "string"); }); Deno.test("pattern schema encodes nested object shapes with their own required arrays", async () => { const source = [ "/// ", 'import { pattern, UI, VNode } from "commonfabric";', "interface Input { nested: { a: boolean; b: { c: string } }; }", "interface Output { [UI]: VNode; }", "export default pattern((s) => ({ [UI]: s.nested as unknown as VNode }));", ].join("\n"); const output = await t(source); const { input } = patternSchemas(parseModule(output)); const nested = (input.properties as Obj).nested; assertEquals(nested.required, ["a", "b"]); assertEquals(nested.properties.b.required, ["c"]); assertEquals(nested.properties.b.properties.c.type, "string"); }); Deno.test("pattern schema encodes a Record as additionalProperties", async () => { const source = [ "/// ", 'import { pattern, UI, VNode } from "commonfabric";', "interface Input { rec: Record; }", "interface Output { [UI]: VNode; }", "export default pattern((s) => ({ [UI]: s.rec as unknown as VNode }));", ].join("\n"); const output = await t(source); const { input } = patternSchemas(parseModule(output)); const rec = (input.properties as Obj).rec; assertEquals(rec.additionalProperties.type, "number"); }); Deno.test("pattern schema encodes a union as anyOf", async () => { const source = [ "/// ", 'import { pattern, UI, VNode } from "commonfabric";', 'interface Input { u: "a" | "b" | number; }', "interface Output { [UI]: VNode; }", "export default pattern((s) => ({ [UI]: s.u as unknown as VNode }));", ].join("\n"); const output = await t(source); const { input } = patternSchemas(parseModule(output)); const u = (input.properties as Obj).u; assert(Array.isArray(u.anyOf)); const enums = (u.anyOf as Obj[]).find((m) => m.enum !== undefined); assertEquals(enums!.enum, ["a", "b"]); }); Deno.test("pattern output schema encodes a VNode UI slot as a $ref", async () => { const source = [ "/// ", 'import { pattern, UI, VNode } from "commonfabric";', "interface Input { x: number; }", "interface Output { [UI]: VNode; total: number; }", "export default pattern((s) => ({ [UI]: s.x as unknown as VNode, total: s.x }));", ].join("\n"); const output = await t(source); const { output: out } = patternSchemas(parseModule(output)); assertEquals( (out.properties as Obj).$UI.$ref, "https://commonfabric.org/schemas/vnode.json", ); }); // // handler — event/state schemas and asCell markers // Deno.test("handler injects two schemas and marks a written Cell state field with asCell writeonly", async () => { const source = [ "/// ", 'import { handler, Cell } from "commonfabric";', "export const h = handler<{ amount: number }, { total: Cell }>(", " (event, ctx) => { ctx.total.set(event.amount); },", ");", ].join("\n"); const output = await t(source); const [event, state] = callSchemas(parseModule(output), "handler"); assertEquals((event.properties as Obj).amount.type, "number"); // The state Cell is only written (.set), so the capability summary narrows // the marker to writeonly rather than the read/write "cell". assertEquals((state.properties as Obj).total.asCell, ["writeonly"]); }); Deno.test("handler inline form injects the event schema from the annotated parameter", async () => { const source = [ "/// ", 'import { handler, Cell } from "commonfabric";', "export const h = handler(", " (event: { label: string }, ctx: { out: Cell }) => { ctx.out.set(event.label); },", ");", ].join("\n"); const output = await t(source); const [event] = callSchemas(parseModule(output), "handler"); assertEquals((event.properties as Obj).label.type, "string"); }); // // lift — many call-site forms // Deno.test("lift injects input and result schemas from type arguments", async () => { const source = [ "/// ", 'import { lift } from "commonfabric";', "const fn = lift<{ count: number }, string>((s) => `n:${s.count}`);", ].join("\n"); const output = await t(source); const [input, result] = callSchemas(parseModule(output), "lift"); assertEquals((input.properties as Obj).count.type, "number"); assertEquals(result.type, "string"); }); Deno.test("lift single type argument infers the result schema from the callback body", async () => { const source = [ "/// ", 'import { lift } from "commonfabric";', "const fn = lift<{ count: number }>((s) => s.count > 0);", ].join("\n"); const output = await t(source); const [input, result] = callSchemas(parseModule(output), "lift"); assertEquals((input.properties as Obj).count.type, "number"); // result is boolean assertEquals(result.type, "boolean"); }); Deno.test("lift inline form infers input and result schemas from annotations", async () => { const source = [ "/// ", 'import { lift } from "commonfabric";', "const fn = lift((s: { a: number }): number => s.a * 2);", ].join("\n"); const output = await t(source); const [input, result] = callSchemas(parseModule(output), "lift"); assertEquals((input.properties as Obj).a.type, "number"); assertEquals(result.type, "number"); }); Deno.test("lift(toSchema(), fn) transfers the authored input schema into the injected slot", async () => { const source = [ "/// ", 'import { lift, toSchema } from "commonfabric";', "const fn = lift(toSchema<{ label: string }>(), undefined, (s) => s.label);", ].join("\n"); const output = await t(source); const [input] = emittedSchemas(parseModule(output)); assertEquals((input.properties as Obj).label.type, "string"); }); // // cell(...) factory — value inference and explicit type argument // Deno.test("cell(value) infers a widened schema from the seed value and injects it as the second argument", async () => { const source = [ "/// ", 'import { cell } from "commonfabric";', "const c = cell(42);", ].join("\n"); const output = await t(source); const [schema] = emittedSchemas(parseModule(output)); // literal 42 is widened to number, not an enum of the literal. assertEquals(schema.type, "number"); assertEquals(schema.enum, undefined); }); Deno.test("cell() with an explicit type argument injects the T schema", async () => { const source = [ "/// ", 'import { cell } from "commonfabric";', "const c = cell<{ name: string }>();", ].join("\n"); const output = await t(source); const [schema] = emittedSchemas(parseModule(output)); assertEquals((schema.properties as Obj).name.type, "string"); }); // // wish(...) — schema injected as trailing argument // Deno.test("wish() injects a schema argument for the wished type", async () => { const source = [ "/// ", 'import { wish } from "commonfabric";', "const w = wish<{ answer: number }>();", ].join("\n"); const output = await t(source); const [schema] = emittedSchemas(parseModule(output)); assertEquals((schema.properties as Obj).answer.type, "number"); }); // // generateObject — schema property injected into the options object // Deno.test("generateObject({...}) injects a schema property into the existing options literal", async () => { const source = [ "/// ", 'import { generateObject } from "commonfabric";', 'const r = generateObject<{ title: string }>({ prompt: "hi" });', ].join("\n"); const output = await t(source); const root = parseModule(output); const [schema] = emittedSchemas(root); assertEquals((schema.properties as Obj).title.type, "string"); // The injected schema rides in a `schema:` property on the options literal. assertStringIncludes(output, "schema:"); }); // // sqliteQuery — rowSchema injected // Deno.test("sqliteQuery({...}) injects a rowSchema property from the Row type argument", async () => { const source = [ "/// ", 'import { sqliteQuery } from "commonfabric";', "declare const db: any;", 'const q = sqliteQuery<{ id: number; name: string }>({ db, sql: "select 1" });', ].join("\n"); const output = await t(source); const [schema] = emittedSchemas(parseModule(output)); assertEquals((schema.properties as Obj).id.type, "number"); assertEquals((schema.properties as Obj).name.type, "string"); // The injected schema rides in a `rowSchema:` property on the options literal. assertStringIncludes(output, "rowSchema:"); }); // // Reactive conditionals: when / unless / ifElse prepend generated schemas // Deno.test("when(condition, value) prepends condition, value and result schemas", async () => { const source = [ "/// ", 'import { pattern, when, UI, VNode } from "commonfabric";', "interface Input { flag: boolean; }", "interface Output { [UI]: VNode; }", "export default pattern((s) => ({ [UI]: when(s.flag, s.flag) as unknown as VNode }));", ].join("\n"); const output = await t(source); const schemas = allEmittedSchemaValues(parseModule(output)) as Obj[]; // when is 2-arity; the rewrite prepends condition + value + result schemas, // so three boolean schema literals appear ahead of the pattern schemas. const boolSchemas = schemas.filter((s) => s.type === "boolean").length; assert(boolSchemas >= 3, `expected >=3 boolean schemas, got ${boolSchemas}`); }); Deno.test("unless(condition, value) prepends generated schemas ahead of the arguments", async () => { const source = [ "/// ", 'import { pattern, unless, UI, VNode } from "commonfabric";', "interface Input { flag: boolean; label: string; }", "interface Output { [UI]: VNode; }", "export default pattern((s) => ({ [UI]: unless(s.flag, s.label) as unknown as VNode }));", ].join("\n"); const output = await t(source); const root = parseModule(output); assertEquals(callsNamed(root, "unless").length, 1); const schemas = allEmittedSchemaValues(root) as Obj[]; // condition schema is boolean, value schema is string. assert(schemas.some((s) => s.type === "boolean")); assert(schemas.some((s) => s.type === "string")); }); Deno.test("ifElse(condition, ifTrue, ifFalse) prepends four schemas for its 3-arity form", async () => { const source = [ "/// ", 'import { pattern, ifElse, UI, VNode } from "commonfabric";', "interface Input { flag: boolean; a: number; b: number; }", "interface Output { [UI]: VNode; }", "export default pattern((s) => ({ [UI]: ifElse(s.flag, s.a, s.b) as unknown as VNode }));", ].join("\n"); const output = await t(source); const root = parseModule(output); assertEquals(callsNamed(root, "ifElse").length, 1); const schemas = allEmittedSchemaValues(root) as Obj[]; // condition boolean + two number branches + number result = >=3 number schemas const numSchemas = schemas.filter((s) => s.type === "number").length; assert(numSchemas >= 3, `expected >=3 number schemas, got ${numSchemas}`); }); // // lift-applied (derive) chains: object-literal input, direct projection // Deno.test("lift-applied object-literal input builds a schema from composed reactive cell types", async () => { const source = [ "/// ", 'import { lift } from "commonfabric";', "const a = lift((x: number) => x)(1);", 'const b = lift((x: string) => x)("s");', "const combined = lift((snap) => `${snap.a}-${snap.b}`)({ a: a, b: b });", ].join("\n"); const output = await t(source); const schemas = emittedSchemas(parseModule(output)); // The composed input schema recovers a: number and b: string from the // upstream lift results rather than collapsing to unknown. const composed = schemas.find((s) => (s.properties as Obj | undefined)?.a !== undefined && (s.properties as Obj | undefined)?.b !== undefined ); assert(composed, "expected composed input schema with a and b"); assertEquals((composed!.properties as Obj).a.type, "number"); assertEquals((composed!.properties as Obj).b.type, "string"); }); Deno.test("lift-applied direct property projection recovers the result schema from the input property", async () => { const source = [ "/// ", 'import { lift } from "commonfabric";', 'const src = lift((x: { a: number; b: string }) => x)({ a: 1, b: "x" });', "const proj = lift((y: { a: number; b: string }) => y.a)(src);", ].join("\n"); const output = await t(source); const schemas = emittedSchemas(parseModule(output)) as Obj[]; // The projection `y => y.a` recovers a number result schema. assert(schemas.some((s) => s.type === "number")); }); Deno.test("lift-applied empty-object input lowers the no-capture placeholder to a false input schema", async () => { const source = [ "/// ", 'import { lift } from "commonfabric";', 'const c = lift(() => "static")({});', ].join("\n"); const output = await t(source); const root = parseModule(output); // The single empty object literal is the no-capture placeholder, so the // injected input schema is the `false` literal (no input) rather than an // object schema. It rides as the second argument of the extracted lift call. const liftCall = callsNamed(root, "lift").at(-1); assert(liftCall, "expected an emitted lift call"); const inputArg = liftCall!.arguments[1]; assert(inputArg && inputArg.kind === ts.SyntaxKind.FalseKeyword); }); // // pattern — single type argument (result inferred), one schema argument // Deno.test("pattern with a single type argument infers the result schema from the callback", async () => { const source = [ "/// ", 'import { pattern } from "commonfabric";', "interface Input { count: number; }", "export default pattern((s) => ({ doubled: s.count * 2 }));", ].join("\n"); const output = await t(source); const { input, output: out } = patternSchemas(parseModule(output)); assertEquals((input.properties as Obj).count.type, "number"); // result schema carries the inferred `doubled: number`. assertEquals((out.properties as Obj).doubled.type, "number"); }); Deno.test("pattern(fn, inputSchema) keeps the author input schema and appends an inferred result schema", async () => { const source = [ "/// ", 'import { pattern } from "commonfabric";', "export default pattern(", " (state: { count: number }) => ({ label: state }),", ' { type: "object" } as const,', ");", ].join("\n"); const output = await t(source); const root = parseModule(output); // Author supplied one schema (input) verbatim; the transformer infers and // appends the result schema (case 3a). The author input keeps its bare // `{ type: "object" } as const` form (no satisfies marker) while the appended // result carries the satisfies marker and the inferred `label` property. const emitted = emittedSchemas(root); assertEquals(emitted.length, 1); assertEquals( ((emitted[0].properties as Obj).label.properties as Obj).count.type, "number", ); assertStringIncludes(output, '{ type: "object" } as const,'); }); // // handler inline single-argument form — event/state inference // Deno.test("handler(fn) with no type args infers both event and state schemas from annotations", async () => { const source = [ "/// ", 'import { handler, Cell } from "commonfabric";', "export const h = handler(", " (event: { delta: number }, ctx: { count: Cell }) => {", " ctx.count.set(event.delta);", " },", ");", ].join("\n"); const output = await t(source); const [event, state] = callSchemas(parseModule(output), "handler"); assertEquals((event.properties as Obj).delta.type, "number"); assert(Object.keys(state.properties as Obj).includes("count")); }); Deno.test("handler(fn) with an underscore-prefixed unused event param yields a false event schema", async () => { const source = [ "/// ", 'import { handler, Cell } from "commonfabric";', "export const h = handler(", " (_event, ctx: { count: Cell }) => { ctx.count.set(1); },", ");", ].join("\n"); const output = await t(source); // Intentionally-unused `_event` collapses to a `never`/`false` event schema, // emitted as the first satisfies-marked argument. const values = allEmittedSchemaValues(parseModule(output)); assertEquals(values[0], false); }); // // cell scope: the `new Writable.perX(seed)` accessor forms // Deno.test("new Writable.perUser(seed) reads the user scope and injects it into the schema", async () => { const source = [ 'import { Writable } from "commonfabric";', "export default function T() {", " const v = new Writable.perUser(7);", " return { v };", "}", ].join("\n"); const output = await t(source); const [schema] = emittedSchemas(parseModule(output)); assertEquals(schema.scope, "user"); assertEquals(schema.type, "number"); }); Deno.test("new Writable.perSpace(seed) reads the space scope and injects it into the schema", async () => { const source = [ 'import { Writable } from "commonfabric";', "export default function T() {", " const v = new Writable.perSpace(true);", " return { v };", "}", ].join("\n"); const output = await t(source); const [schema] = emittedSchemas(parseModule(output)); assertEquals(schema.scope, "space"); assertEquals(schema.type, "boolean"); }); // // lift result recovery: direct property / element-access projection // Deno.test("lift property projection recovers the result schema from the projected field type", async () => { const source = [ "/// ", 'import { lift } from "commonfabric";', "const f = lift((s: { a: number; b: string }) => s.a);", ].join("\n"); const output = await t(source); const [input, result] = callSchemas(parseModule(output), "lift"); // The result schema is recovered as number from `s.a`. Capability shrinking // narrows the input to the single accessed field `a`, dropping the unused `b`. assertEquals((input.properties as Obj).a.type, "number"); assertEquals(Object.keys(input.properties as Obj), ["a"]); assertEquals(result.type, "number"); }); Deno.test("lift element-access projection recovers the result schema from the indexed field", async () => { const source = [ "/// ", 'import { lift } from "commonfabric";', 'const f = lift((s: { title: string; other: number }) => s["title"]);', ].join("\n"); const output = await t(source); const [input, result] = callSchemas(parseModule(output), "lift"); // `s["title"]` is a direct projection; the result schema recovers string. assertEquals((input.properties as Obj).title.type, "string"); assertEquals(result.type, "string"); }); // // pattern result diagnostics // Deno.test("pattern with an inferred any/unknown result reports pattern:any-result-schema", async () => { const source = [ "/// ", 'import { pattern } from "commonfabric";', "export default pattern((s: { x: number }) => s as any);", ].join("\n"); const { diagnostics } = await validateSource(source, { types: COMMONFABRIC_TYPES, }); assert( diagnostics.some((d) => d.type === "pattern:any-result-schema"), `expected pattern:any-result-schema, got: ${ diagnostics.map((d) => d.type).join(",") }`, ); }); // // handler event-schema optionality // Deno.test("handler event schema encodes an optional field as not required", async () => { const source = [ "/// ", 'import { handler, Cell } from "commonfabric";', "export const h = handler<{ amount?: number }, { total: Cell }>(", " (event, ctx) => { ctx.total.set(event?.amount ?? 0); },", ");", ].join("\n"); const output = await t(source); const [event] = callSchemas(parseModule(output), "handler"); // Optional event field is present but excluded from required. assert(Object.keys(event.properties as Obj).includes("amount")); assert(!((event.required as string[] | undefined) ?? []).includes("amount")); }); // // generateObject — options variations // Deno.test("generateObject() with no options builds a fresh options object carrying the schema", async () => { const source = [ "/// ", 'import { generateObject } from "commonfabric";', "const r = generateObject<{ score: number }>();", ].join("\n"); const output = await t(source); const [schema] = emittedSchemas(parseModule(output)); assertEquals((schema.properties as Obj).score.type, "number"); assertStringIncludes(output, "schema:"); }); Deno.test("generateObject(spreadOptions) spreads a non-literal options expression and adds the schema", async () => { const source = [ "/// ", 'import { generateObject } from "commonfabric";', "declare const opts: { prompt: string };", "const r = generateObject<{ ok: boolean }>(opts);", ].join("\n"); const output = await t(source); const [schema] = emittedSchemas(parseModule(output)); assertEquals((schema.properties as Obj).ok.type, "boolean"); const [call] = callsNamed(parseModule(output), "generateObject"); const spreads = collect(call!, ts.isSpreadAssignment); assertEquals(spreads.length, 1); assert(ts.isIdentifier(spreads[0]!.expression)); assertEquals(spreads[0]!.expression.text, "opts"); }); // // sqliteQuery — no-options and untyped forms // Deno.test("sqliteQuery() with no options builds a fresh options object carrying rowSchema", async () => { const source = [ "/// ", 'import { sqliteQuery } from "commonfabric";', "const q = sqliteQuery<{ id: number; label: string }>();", ].join("\n"); const output = await t(source); const [schema] = emittedSchemas(parseModule(output)); assertEquals((schema.properties as Obj).id.type, "number"); assertEquals((schema.properties as Obj).label.type, "string"); assertStringIncludes(output, "rowSchema:"); }); Deno.test("untyped sqliteQuery(options) injects no rowSchema", async () => { const source = [ "/// ", 'import { sqliteQuery } from "commonfabric";', "declare const db: any;", 'const q = sqliteQuery({ db, sql: "select 1" });', ].join("\n"); const output = await t(source); // Untyped form must lower to no schema; runtime falls back to detection. assertEquals(emittedSchemas(parseModule(output)).length, 0); }); // // contextual cell scope from a Scoped<> / PerX<> annotation // Deno.test("cell(value) assigned to a PerSession variable reads the session scope from the annotation", async () => { const source = [ "/// ", 'import { cell, PerSession } from "commonfabric";', "const c: PerSession = cell(0);", ].join("\n"); const output = await t(source); const [schema] = emittedSchemas(parseModule(output)); // The scope brand is recovered from the `PerSession` alias on the contextual // type rather than from any accessor on the call. assertEquals(schema.scope, "session"); }); Deno.test("cell(value) assigned to a PerUser variable reads the user scope from the annotation", async () => { const source = [ "/// ", 'import { cell, PerUser } from "commonfabric";', 'const c: PerUser = cell("hi");', ].join("\n"); const output = await t(source); const [schema] = emittedSchemas(parseModule(output)); assertEquals(schema.scope, "user"); }); Deno.test("cell(value) assigned to a PerSpace variable reads the space scope from the annotation", async () => { const source = [ "/// ", 'import { cell, PerSpace } from "commonfabric";', "const c: PerSpace = cell(true);", ].join("\n"); const output = await t(source); const [schema] = emittedSchemas(parseModule(output)); assertEquals(schema.scope, "space"); }); Deno.test("cell(value) assigned to a variable typed by an alias of PerSession reads the session scope from the annotation", async () => { const source = [ "/// ", 'import { cell, PerSession } from "commonfabric";', "type Count = PerSession;", "const c: Count = cell(0);", ].join("\n"); const output = await t(source); const [schema] = emittedSchemas(parseModule(output)); // The checker reports the alias `Count`, so the scope is read from the // brand `PerSession` leaves on the resolved type. assertEquals(schema.scope, "session"); }); Deno.test("pattern result field typed by an alias of PerUser keeps the user scope", async () => { const source = [ "/// ", 'import { computed, pattern, PerUser } from "commonfabric";', "type Rec = PerUser<{ a: number }>;", "export default pattern<{ x: number }>(({ x }) => {", " const v: Rec = computed(() => ({ a: x }));", " return { v };", "});", ].join("\n"); const output = await t(source); const { output: result } = patternSchemas(parseModule(output)); assertEquals( (result.properties as Record).v.scope, "user", ); }); // // pattern result: inferred unknown output field reports pattern-result:unknown-type // Deno.test("pattern with an inferred unknown output field reports pattern-result:unknown-type", async () => { const source = [ "/// ", 'import { pattern } from "commonfabric";', "function opaque(): unknown { return 1; }", "export default pattern((s: { x: number }) => ({ out: opaque() }));", ].join("\n"); const { diagnostics } = await validateSource(source, { types: COMMONFABRIC_TYPES, }); const paths = diagnostics.filter((d) => d.type === "pattern-result:unknown-type" ); assert( paths.length > 0, `expected pattern-result:unknown-type, got: ${ diagnostics.map((d) => d.type).join(",") }`, ); // The message names the offending field path. assertStringIncludes(paths[0]!.message, "out"); }); // // lift result shape: tuple, enum literal, and nested arrays // Deno.test("lift encodes a tuple result as an array schema with a positional item type set", async () => { const source = [ "/// ", 'import { lift } from "commonfabric";', 'const f = lift<{ a: number }, [string, number]>((s) => ["x", s.a]);', ].join("\n"); const output = await t(source); const [, result] = callSchemas(parseModule(output), "lift"); assertEquals(result.type, "array"); // tuple element union of the two positional types assertEquals((result.items as Obj).type, ["number", "string"]); }); Deno.test("lift encodes a string-literal-union result as an enum", async () => { const source = [ "/// ", 'import { lift } from "commonfabric";', 'const f = lift<{ n: number }, "lo" | "hi">((s) => (s.n > 0 ? "hi" : "lo"));', ].join("\n"); const output = await t(source); const [, result] = callSchemas(parseModule(output), "lift"); assertEquals(result.enum, ["lo", "hi"]); }); Deno.test("lift encodes a nested array-of-objects result schema", async () => { const source = [ "/// ", 'import { lift } from "commonfabric";', "const f = lift<{ n: number }, { id: number }[]>((s) => [{ id: s.n }]);", ].join("\n"); const output = await t(source); const [, result] = callSchemas(parseModule(output), "lift"); assertEquals(result.type, "array"); assertEquals(((result.items as Obj).properties as Obj).id.type, "number"); }); // // cell-for scope wrapping via .asSchema(...) // Deno.test("scoped cell-for value schema carries the scope marker", async () => { const source = [ "/// ", 'import { cell, PerSession } from "commonfabric";', "const base = cell();", "const scoped: PerSession = base;", ].join("\n"); const output = await t(source); const [schema] = emittedSchemas(parseModule(output)); // The base cell schema is injected; assigning to PerSession keeps the schema. assertEquals(schema.type, "number"); }); // // new cell constructor: value inference, explicit type argument // Deno.test("new Writable(value) infers a widened schema from the seed and injects it as the second argument", async () => { const source = [ 'import { Writable } from "commonfabric";', "export default function T() {", " const v = new Writable(5);", " return { v };", "}", ].join("\n"); const output = await t(source); const [schema] = emittedSchemas(parseModule(output)); assertEquals(schema.type, "number"); // No scope accessor was used, so no scope marker is injected. assertEquals(schema.scope, undefined); }); Deno.test("new Writable() with an explicit type argument and no value injects an undefined first argument", async () => { const source = [ 'import { Writable } from "commonfabric";', "export default function T() {", " const v = new Writable<{ n: number }>();", " return { v };", "}", ].join("\n"); const output = await t(source); const root = parseModule(output); // Schema is always the second argument, so `undefined` is inserted first. const ctor = collect(root, ts.isNewExpression).find((n) => ts.isIdentifier(n.expression) && n.expression.text === "Writable" ); assert(ctor, "expected a `new Writable<...>()` construction"); assertEquals( ctor!.arguments![0].kind, ts.SyntaxKind.Identifier, ); assertEquals((ctor!.arguments![0] as ts.Identifier).text, "undefined"); const schema = literalToValue(ctor!.arguments![1]) as Obj; assertEquals((schema.properties as Obj).n.type, "number"); }); // // contextual scope from a raw Scoped brand (no PerX alias) // Deno.test("cell(value) typed as raw Scoped reads the scope from the SCOPE_BRAND property", async () => { const source = [ "/// ", 'import { cell, Scoped } from "commonfabric";', 'const c: Scoped = cell(0);', ].join("\n"); const output = await t(source); const [schema] = emittedSchemas(parseModule(output)); // `Scoped` is not one of the PerX aliases, so the scope is recovered from the // scope-brand property rather than the alias name. assertEquals(schema.scope, "session"); }); // // lift(fn)(input): type arguments on the inner lift drive both schemas // Deno.test("lift(fn)(input) reads the schemas from the inner lift type arguments", async () => { const source = [ "/// ", 'import { lift } from "commonfabric";', "const f = lift<{ a: number }, string>((s) => `${s.a}`)({ a: 1 });", ].join("\n"); const output = await t(source); const [input, result] = callSchemas(parseModule(output), "lift"); assertEquals((input.properties as Obj).a.type, "number"); assertEquals(result.type, "string"); }); // // chained lift-applied (derive) inputs: recover the upstream result type // Deno.test("chained lift-applied recovers the input schema from the upstream lift's inferred result", async () => { const source = [ "/// ", 'import { lift } from "commonfabric";', "const step1 = lift((x: { a: number }) => ({ a: x.a, b: x.a + 1 }))({ a: 1 });", "const step2 = lift((y) => y.b)(step1);", ].join("\n"); const output = await t(source); // step2's callback param has no annotation; its type is recovered from // step1's inferred `{ a, b }` result, and capability shrinking keeps only the // accessed `b` field. The result schema is number. const [input, result] = callSchemas(parseModule(output), "lift"); assertEquals((input.properties as Obj).b.type, "number"); assertEquals(Object.keys(input.properties as Obj), ["b"]); assertEquals(result.type, "number"); }); Deno.test("chained lift-applied whose upstream is a single-field object recovers a concrete input schema, not unknown", async () => { const source = [ "/// ", 'import { lift } from "commonfabric";', 'const first = lift((x: { name: string }) => ({ name: x.name }))({ name: "n" });', "const second = lift((v) => v.name.length)(first);", ].join("\n"); const output = await t(source); // The recovered input carries the concrete `name: string` field rather than a // permissive `true`/unknown schema. const [input] = callSchemas(parseModule(output), "lift"); assertEquals((input.properties as Obj).name.type, "string"); assertEquals(input.type, "object"); }); // // scope brand recovery when the scope is a union of literal scope values // Deno.test("cell typed as Scoped recovers the first concrete scope from the brand union", async () => { const source = [ "/// ", 'import { cell, Scoped } from "commonfabric";', 'const c: Scoped = cell(0);', ].join("\n"); const output = await t(source); const [schema] = emittedSchemas(parseModule(output)); // The scope-brand type is a union; the recovery walks the members and returns // the first concrete scope value. assertEquals(schema.scope, "user"); }); // // lift factory application // Deno.test("applying a captured lift factory recovers the downstream input schema from the factory callback result", async () => { // lift factory captured in a variable, then applied — recover the result type // through the factory's callback const source = [ "/// ", 'import { lift } from "commonfabric";', "const makeIt = lift((x: { a: number }) => ({ a: x.a, b: 2 }));", "const applied = makeIt({ a: 1 });", "const used = lift((y) => y.b)(applied);", ].join("\n"); const output = await t(source); // `used`'s callback param is untyped; its type is recovered from `applied`, // whose type comes from the `makeIt` factory callback returning `{ a, b }`. const [input, result] = callSchemas(parseModule(output), "lift"); assertEquals((input.properties as Obj).b.type, "number"); assertEquals(result.type, "number"); }); // // pattern result: unknown paths are walked into nested objects and arrays // Deno.test("pattern result reports a nested unknown output field with a dotted path", async () => { const source = [ "/// ", 'import { pattern } from "commonfabric";', "function op(): unknown { return 1; }", "export default pattern((s: { x: number }) => ({ nested: { deep: op() } }));", ].join("\n"); const { diagnostics } = await validateSource(source, { types: COMMONFABRIC_TYPES, }); const d = diagnostics.find((d) => d.type === "pattern-result:unknown-type"); assert(d, "expected pattern-result:unknown-type"); // The path walk descends into the nested object literal. assertStringIncludes(d!.message, "nested.deep"); }); Deno.test("pattern result reports an unknown array element with an array path suffix", async () => { const source = [ "/// ", 'import { pattern } from "commonfabric";', "function op(): unknown { return 1; }", "export default pattern((s: { x: number }) => ({ items: [op()] }));", ].join("\n"); const { diagnostics } = await validateSource(source, { types: COMMONFABRIC_TYPES, }); const d = diagnostics.find((d) => d.type === "pattern-result:unknown-type"); assert(d, "expected pattern-result:unknown-type"); // The array element walk appends `[]` to the path. assertStringIncludes(d!.message, "items[]"); }); // // An author-supplied schema is left untouched // // Each of these already carries a schema of its own, so injection has // nothing to add and must add nothing. // Deno.test("new Writable(value, schema) with two arguments is left untouched", async () => { // idempotency / author-supplied skips: a builder that already carries its // schema is left untouched const source = [ 'import { Writable } from "commonfabric";', "export default function T() {", ' const v = new Writable(5, { type: "number" } as const);', " return { v };", "}", ].join("\n"); const output = await t(source); // The schema slot is already filled (bare `as const`, no satisfies marker), // so the transformer injects no schema of its own. assertEquals(emittedSchemas(parseModule(output)).length, 0); assertStringIncludes(output, 'new Writable(5, { type: "number" }'); }); Deno.test("wish(hint, schema) with an author-supplied schema is left untouched", async () => { const source = [ "/// ", 'import { wish } from "commonfabric";', 'const w = wish("hint", { type: "object" } as const);', ].join("\n"); const output = await t(source); // The two-argument wish already has its schema; the transformer does not add // a satisfies-marked schema of its own. assertEquals(emittedSchemas(parseModule(output)).length, 0); assertStringIncludes(output, '{ type: "object" } as const'); }); Deno.test("generateObject({ schema }) already carrying a schema property is left untouched", async () => { const source = [ "/// ", 'import { generateObject } from "commonfabric";', 'const r = generateObject({ prompt: "hi", schema: { type: "object" } as const });', ].join("\n"); const output = await t(source); // No satisfies-marked schema is injected; only the author's bare schema // property remains. assertEquals(emittedSchemas(parseModule(output)).length, 0); assertEquals((output.match(/schema:/g) ?? []).length, 1); }); Deno.test("untyped sqliteQuery already carrying a rowSchema is left untouched", async () => { const source = [ "/// ", 'import { sqliteQuery } from "commonfabric";', "declare const db: any;", 'const q = sqliteQuery<{ id: number }>({ db, sql: "s", rowSchema: { type: "object" } as const });', ].join("\n"); const output = await t(source); // A rowSchema is already present, so the typed form does not inject a second. assertEquals(emittedSchemas(parseModule(output)).length, 0); assertEquals((output.match(/rowSchema:/g) ?? []).length, 1); }); // // new scoped cell with no value: scope from accessor, value type from context // Deno.test("new Writable.perSession() with no value derives the value type from the contextual scope annotation", async () => { const source = [ 'import { Writable, PerSession } from "commonfabric";', "export default function T() {", " const v: PerSession = new Writable.perSession();", " return { v };", "}", ].join("\n"); const output = await t(source); const root = parseModule(output); // No seed value, so the value type is recovered from the contextual // PerSession annotation and the accessor supplies the session scope. const [schema] = emittedSchemas(root); assertEquals(schema.scope, "session"); assertEquals(schema.type, "number"); // Schema is always the second argument, so `undefined` is inserted first. const ctor = collect(root, ts.isNewExpression).at(-1); assert(ctor, "expected a `new Writable.perSession()` construction"); assertEquals(ctor!.arguments![0].kind, ts.SyntaxKind.Identifier); assertEquals((ctor!.arguments![0] as ts.Identifier).text, "undefined"); }); // // lift-applied projection recovery // Deno.test("lift-applied element-access projection recovers the result schema from the indexed field", async () => { // lift-applied projection recovery: property-access and element-access forms // on a downstream callback whose parameter type is recovered from upstream const source = [ "/// ", 'import { lift } from "commonfabric";', 'const s1 = lift((x: { title: string; n: number }) => x)({ title: "t", n: 1 });', 'const s2 = lift((y) => y["title"])(s1);', ].join("\n"); const output = await t(source); // The downstream callback `y => y["title"]` has no annotation; its input is // recovered from the upstream result and the string result schema is derived // from the indexed `title` field. const [input, result] = callSchemas(parseModule(output), "lift"); assert(Object.keys(input.properties as Obj).includes("title")); assertEquals(result.type, "string"); }); Deno.test("lift-applied property-access projection recovers the result schema from the accessed field", async () => { const source = [ "/// ", 'import { lift } from "commonfabric";', 'const s1 = lift((x: { count: number; other: string }) => x)({ count: 1, other: "o" });', "const s2 = lift((y) => y.count)(s1);", ].join("\n"); const output = await t(source); const [input, result] = callSchemas(parseModule(output), "lift"); assert(Object.keys(input.properties as Obj).includes("count")); assertEquals(result.type, "number"); }); // // handler state Cell that is read marks the schema field asCell readonly // Deno.test("handler marks a read-only Cell state field with asCell readonly", async () => { const source = [ "/// ", 'import { handler, Cell } from "commonfabric";', "export const h = handler<{ q: number }, { total: Cell }>(", " (event, ctx) => { const x = ctx.total.get(); void x; void event; },", ");", ].join("\n"); const output = await t(source); const [, state] = callSchemas(parseModule(output), "handler"); // The state Cell is only read (.get), so the capability summary narrows the // marker to readonly. assertEquals((state.properties as Obj).total.asCell, ["readonly"]); }); // // lift-applied input recovery through Cell.get // Deno.test("lift-applied untyped callback using Cell.get on a Cell input recovers a cell-like input schema", async () => { // lift-applied whose untyped callback calls Cell methods on a Cell input: the // input schema is recovered from the cell-like fallback type and marked // asCell readonly const source = [ "/// ", 'import { cell, lift } from "commonfabric";', "const c = cell({ n: 5 });", "const d = lift((x) => x.get().n)(c);", ].join("\n"); const output = await t(source); // The callback param has no annotation but calls `.get()`; because the input // is a Cell, the input schema is recovered from the cell-like fallback type // and carries the readonly asCell marker with the inner `{ n: number }` shape. const [input] = callSchemas(parseModule(output), "lift"); assertEquals((input.properties as Obj).n.type, "number"); assertEquals(input.asCell, ["readonly"]); });