From 3f1976815f25358b6bc300ad648c86b0e2c17d28 Mon Sep 17 00:00:00 2001 From: "Timothy J. Aveni" Date: Sun, 20 Sep 2026 02:42:13 -0700 Subject: [PATCH] Compile and execute bounded GraphQL replica admission --- src/execution-world/index.ts | 1 + src/execution-world/queries.ts | 60 +++++++++++++++++++ test/emit-portable-reader.ts | 2 + .../local-first-reader/ReaderRuntime.qx | 1 + .../queries/highlight-window.graphql | 8 +++ test/local-first-reader.test.ts | 2 +- 6 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 src/execution-world/queries.ts create mode 100644 test/fixtures/local-first-reader/queries/highlight-window.graphql diff --git a/src/execution-world/index.ts b/src/execution-world/index.ts index 726a4ac..6e234bc 100644 --- a/src/execution-world/index.ts +++ b/src/execution-world/index.ts @@ -9,4 +9,5 @@ export { generatePortableTypeScriptBindings } from "../bindings/portable.js"; export * from "./kernel.js"; export * from "./projections.js"; +export * from "./queries.js"; export { generatePortableClientBindings } from "../bindings/portable-client.js"; diff --git a/src/execution-world/queries.ts b/src/execution-world/queries.ts new file mode 100644 index 0000000..c737b04 --- /dev/null +++ b/src/execution-world/queries.ts @@ -0,0 +1,60 @@ +import { portableValueSchema } from "../bindings/portable.js"; +import { ExecutionPlanError, hexDigest, type ExecutionPlan } from "./plan.js"; +import type { ValueType } from "../capability-model/types.js"; + +const lowerTypes = (value: unknown): unknown => + JSON.parse( + JSON.stringify(value, (key, value: unknown) => { + if ( + key === "type" && + value && + typeof value === "object" && + "kind" in value && + ["scalar", "builtin", "optional", "list", "record", "object-ref"].includes(String(value.kind)) + ) + return portableValueSchema(value as ValueType); + return value; + }), + ); + +/** Checked query IR and installed getter identities, without GraphQL schemas/parsers or SQL adapters. + * Keep authored selection names only for output paths; field access uses resolved interface/member IDs. */ +export function compileQueryProgram(plan: ExecutionPlan): unknown { + if (hexDigest(plan.definition) !== plan.digest) + throw new ExecutionPlanError("INVALID_PLAN", "queries", "Plan digest mismatch"); + const interfaces = plan.definition.workspace.interfaceImports; + return { + codec: "camino-query-program-v1", + plan: plan.digest, + queries: plan.definition.queries.map(({ id, query, admittedOperations }) => { + const checked = query.checked; + return { + id, + placement: checked.declaration.placement?.kind ?? "remote", + source: + checked.declaration.placement?.kind === "local" + ? JSON.stringify([query.packageRevisionId, checked.declaration.placement.sourceQueryId]) + : null, + root: checked.declaration.root, + atoms: query.rootAtoms, + variables: portableValueSchema(checked.variables), + defaults: checked.variableDefaults, + selection: lowerTypes(checked.selection), + budgets: checked.declaration.budgets, + fields: query.fields.map((field) => { + const member = interfaces + .find((i) => i.revisionId === field.interfaceRevisionId)! + .members.find((m) => m.id === field.memberId)!; + return { + atom: field.atomId, + interface: field.interfaceRevisionId, + member: field.memberId, + name: member.displayName, + operation: JSON.stringify([field.atomId, field.interfaceRevisionId, field.getter]), + }; + }), + admittedOperations, + }; + }), + }; +} diff --git a/test/emit-portable-reader.ts b/test/emit-portable-reader.ts index 4867172..f60e04b 100644 --- a/test/emit-portable-reader.ts +++ b/test/emit-portable-reader.ts @@ -2,6 +2,7 @@ import { mkdir, writeFile } from "node:fs/promises"; import { loadReaderWorkspace } from "./reader-fixture.js"; import { compileKernelProgram } from "../src/execution-world/kernel.js"; +import { compileQueryProgram } from "../src/execution-world/queries.js"; import { compileExecutionPlan } from "../src/execution-world/plan.js"; import { generatePortableTypeScriptBindings } from "../src/bindings/portable.js"; import { generatePortableClientBindings } from "../src/bindings/portable-client.js"; @@ -15,6 +16,7 @@ for (const target of ["browser", "server"]) ); await writeFile("dist/portable-reader/plan.json", JSON.stringify(plan)); await writeFile("dist/portable-reader/kernel.json", JSON.stringify(compileKernelProgram(plan))); +await writeFile("dist/portable-reader/queries.json", JSON.stringify(compileQueryProgram(plan))); await writeFile("dist/portable-reader/client.ts", generatePortableClientBindings(plan, { target: "browser" })); const serviceWorkspace = structuredClone(plan.definition.workspace); serviceWorkspace.atoms = []; diff --git a/test/fixtures/local-first-reader/ReaderRuntime.qx b/test/fixtures/local-first-reader/ReaderRuntime.qx index 629a3ef..9067cbb 100644 --- a/test/fixtures/local-first-reader/ReaderRuntime.qx +++ b/test/fixtures/local-first-reader/ReaderRuntime.qx @@ -77,6 +77,7 @@ revision "package:reader-runtime@1" { } query HighlightScope id "reader:highlight-scope" root workspace HighlightData document "queries/highlight-scope.graphql" operation "HighlightScope" { view Comment as CommentData; replica; max rows 200; } + query HighlightWindow id "reader:highlight-window" root workspace HighlightData document "queries/highlight-window.graphql" operation "HighlightWindow" { view Comment as CommentData; replica; max rows 200; } query HighlightList id "reader:highlight-list" root workspace HighlightData document "queries/highlight-list.graphql" operation "HighlightList" { view Comment as CommentData; local from HighlightScope; max rows 200; watch; } query HighlightEditorInputs id "reader:editor-inputs" root HighlightData document "queries/editor-inputs.graphql" operation "HighlightEditorInputs" { view Comment as CommentData; replica; max rows 1; } query HighlightEditorView id "reader:editor-view" root HighlightData document "queries/editor-view.graphql" operation "HighlightEditorView" { view Comment as CommentData; local from HighlightEditorInputs; max rows 1; watch; } diff --git a/test/fixtures/local-first-reader/queries/highlight-window.graphql b/test/fixtures/local-first-reader/queries/highlight-window.graphql new file mode 100644 index 0000000..b19d729 --- /dev/null +++ b/test/fixtures/local-first-reader/queries/highlight-window.graphql @@ -0,0 +1,8 @@ +query HighlightWindow($color: String!) { + root { + objects(first: 2, where: {color: {eq: $color}}, orderBy: {range: {start: ASC}}) { + entries { key node { _qx {ref} color range {sourceRevision start end} } } + pageInfo { hasNextPage endCursor } + } + } +} diff --git a/test/local-first-reader.test.ts b/test/local-first-reader.test.ts index 7ac3243..4c04d5f 100644 --- a/test/local-first-reader.test.ts +++ b/test/local-first-reader.test.ts @@ -8,7 +8,7 @@ test("complete reader contracts compose factories, portable selectors, CRDT edit assert.equal(workspace.constructors.length, 0); assert.equal(workspace.atoms.length, 3); const linked = linkQueries(workspace); - assert.equal(linked.length, 6); + assert.equal(linked.length, 7); assert.equal(linked.filter((q) => q.checked.declaration.placement?.kind === "local").length, 3); const screen = linked.find((q) => q.checked.declaration.displayName === "ReaderScreen")!; assert.ok(screen.fields.some((f) => f.memberId === "highlight:excerpt" && f.binding.kind === "package"));