From 76d5cff6fb1bd18984f1998cf8629b2acecd54f2 Mon Sep 17 00:00:00 2001 From: "Timothy J. Aveni" Date: Fri, 25 Sep 2026 12:02:05 -0700 Subject: [PATCH] Support complete public relationship observations for optimistic presentation hosting --- src/execution-world/inspection.ts | 38 +++++++++++++++++++++---------- test/inspection.test.ts | 5 ++-- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/src/execution-world/inspection.ts b/src/execution-world/inspection.ts index d2b879e..7667b2f 100644 --- a/src/execution-world/inspection.ts +++ b/src/execution-world/inspection.ts @@ -22,6 +22,8 @@ export interface InspectionTemplate { inputs?: PublicQueryContract; view?: PublicQueryContract; server?: PublicQueryContract; + completeInputs?: PublicQueryContract; + completeView?: PublicQueryContract; } /** A finite installed template per interface/lane, not a per-client query compiler. * Flags select public cache boundaries; normal scope identity bounds specialization. */ @@ -54,7 +56,9 @@ export function compileInspectionTemplates(plan: ExecutionPlan) { .sort((a, b) => (a.id < b.id ? -1 : 1)); if (!members.length || members.length > 128) continue; const template: InspectionTemplate = { interface: iface.revisionId, members: [] }; - for (const lane of ["local", "server"] as const) { + for (const mode of ["local", "complete", "server"] as const) { + const lane = mode === "server" ? "server" : "local"; + const complete = mode === "complete"; const fields: unknown[] = [], selections: unknown[] = [], inputFields: Record = {}, @@ -76,7 +80,7 @@ export function compileInspectionTemplates(plan: ExecutionPlan) { const many = member.kind === "relationship" && ["many", "many-unique"].includes(member.cardinality); // Only native edges promise the relationship traversal used by the interpreter. if (member.kind === "relationship" && providers.some((op) => op!.binding.kind !== "edge")) continue; - if (lane === "local" || !portable) + if (!complete && (lane === "local" || !portable)) template.members.push({ id: member.id, name: member.displayName, key, flag, lane, many }); inputFields[flag] = scalar("bool"); const field = selection(member.displayName, { @@ -95,18 +99,27 @@ export function compileInspectionTemplates(plan: ExecutionPlan) { if (member.target.kind === "interface") Object.assign(field, { targetInterfaceRevisionId: member.target.interfaceRevisionId }); if (many) { - inputFields[key + "First"] = scalar("int32"); - inputFields[key + "After"] = optional(scalar("string")); + const bound = key + (complete ? "All" : "First"); + inputFields[bound] = scalar("int32"); + if (!complete) inputFields[key + "After"] = optional(scalar("string")); + const keyFields = { key: scalar("string"), ...(member.keyType ? { mapKey: scalar(member.keyType) } : {}) }; + const keySelections = [selection("key"), ...(member.keyType ? [selection("mapKey")] : [])]; Object.assign(field, { - arguments: { first: variable(key + "First"), after: variable(key + "After") }, + arguments: complete + ? { all: variable(bound) } + : { first: variable(bound), after: variable(key + "After") }, selection: [ - selection("entries", { selection: [selection("key"), selection("node", { selection: [meta()] })] }), - selection("pageInfo", { selection: [selection("hasNextPage"), selection("endCursor")] }), + selection("entries", { selection: [...keySelections, selection("node", { selection: [meta()] })] }), + ...(complete + ? [] + : [selection("pageInfo", { selection: [selection("hasNextPage"), selection("endCursor")] })]), ], }); output = record({ - entries: { kind: "list", value: record({ key: scalar("string"), node }) }, - pageInfo: record({ hasNextPage: scalar("bool"), endCursor: optional(scalar("string")) }), + entries: { kind: "list", value: record({ ...keyFields, node }) }, + ...(complete + ? {} + : { pageInfo: record({ hasNextPage: scalar("bool"), endCursor: optional(scalar("string")) }) }), }); } else { Object.assign(field, { selection: [meta()] }); @@ -147,8 +160,9 @@ export function compileInspectionTemplates(plan: ExecutionPlan) { }, }; for (const kind of lane === "local" ? (["inputs", "view"] as const) : (["server"] as const)) { - const query = JSON.stringify([iface.revisionId, kind]); - const sourceQuery = JSON.stringify([iface.revisionId, "inputs"]); + const templateKey = complete ? (kind === "inputs" ? "completeInputs" : "completeView") : kind; + const query = JSON.stringify([iface.revisionId, templateKey]); + const sourceQuery = JSON.stringify([iface.revisionId, complete ? "completeInputs" : "inputs"]); const program = { ...base, id: JSON.stringify([inspectionPackage, query]), @@ -167,7 +181,7 @@ export function compileInspectionTemplates(plan: ExecutionPlan) { }; queries.push(program); contracts.push(contract); - template[kind] = contract; + template[templateKey] = contract; } } if (template.members.length) templates.push(template); diff --git a/test/inspection.test.ts b/test/inspection.test.ts index 4f416d7..062f9d8 100644 --- a/test/inspection.test.ts +++ b/test/inspection.test.ts @@ -12,7 +12,8 @@ test("public member inspection installs a finite set of typed lazy templates", a template.members.some((member) => member.name === "content") && template.members.some((member) => member.name === "highlights"), )!; - assert.ok(document.inputs && document.view && document.server); + assert.ok(document.inputs && document.view && document.server && document.completeInputs && document.completeView); + assert.equal(document.completeView.sourceQuery, document.completeInputs.query); assert.equal(document.view.sourceQuery, document.inputs.query); assert.ok(document.members.find((member) => member.name === "highlights")?.many); assert.ok(!document.members.some((member) => member.name === "revise")); @@ -31,5 +32,5 @@ test("public member inspection installs a finite set of typed lazy templates", a } assert.ok(!JSON.stringify(result.templates).includes("slot:")); assert.equal(result.queries.length, result.contracts.length); - assert.ok(result.queries.length <= plan.definition.workspace.interfaceImports.length * 3); + assert.ok(result.queries.length <= plan.definition.workspace.interfaceImports.length * 5); });