Files
Timothy J. Aveni 2dffdbcd9f Implement shared live fields and checked query hydration
Add native register acknowledgments and idempotent mutation replay, shared optimistic field controllers, overlapping custom setters, non-suspending hooks and explicit Suspense. Carry checked @live provenance through batched queries and hydrate shared browser fields with coverage leases. Update tracker/scaffolds/guides and verify compiler, PostgreSQL, browser and immutable workspace paths.
2026-09-18 02:19:49 -07:00

136 lines
7.7 KiB
TypeScript

import assert from "node:assert/strict";
import { compileCapabilityResourceSource, compileCapabilitySource } from "../../src/capability-language/index.js";
import { compileQuery } from "../../src/query/compile.js";
import { linkQueries } from "../../src/query/link.js";
import type { InterfaceRevision, PackageRevision } from "../../src/capability-model/types.js";
export const queryFixtureSource = {
repository: "https://query-fixture.example.test/source.git",
commit: "1".repeat(40),
};
export async function queryWorkspaceFixture(options: { live?: boolean } = {}) {
const interfaces = new Map<string, InterfaceRevision>();
const sources: Record<string, string> = {};
function iface(name: string, members: string, parameters = "") {
const text = `${[...interfaces.keys()].map((name) => `import interface ${name};`).join("\n")}
interface ${name}${parameters} id "${name}" revision "${name}@1" {${members}}`;
sources[name] = text;
const result = compileCapabilityResourceSource(text, {
source: queryFixtureSource,
environment: { interfaces, interfaceClosure: [...interfaces.values()] },
});
assert.ok(result.ok, JSON.stringify(result.diagnostics));
if (result.resource.kind !== "interface") throw new Error("interface expected");
interfaces.set(name, result.resource.revision);
}
iface("PersonFacts", `queryable value name id "name" : string {get id "name:get";}`);
iface(
"TaskFacts",
`
queryable value title id "title" : string {get id "title:get"; set id "title:set";}
queryable value done id "done" : bool {get id "done:get";}
queryable value rank id "rank" : int64 {get id "rank:get"; set id "rank:set";}
queryable relation assignee id "assignee" : optional-one interface PersonFacts {resolve id "assignee:resolve";}
queryable rpc value score id "score" : int32 {get id "score:get";}
`,
);
iface(
"Collection",
`queryable relation items id "items" : many object Item ordered {resolve id "items:resolve";}`,
"<object Item implements TaskFacts>",
);
const packageSource = `${[...interfaces.keys()].map((name) => `import interface ${name};`).join("\n")}
package Queries id "queries" revision "queries@1" {
operation titleSet id "title-set" : string -> unit mode call receiver any
requires {state title id "title-port" : string [write];};
operation score id "score" : unit -> int32 mode call receiver any;
query Upcoming id "upcoming" root Collection<interface TaskFacts> document "upcoming.graphql" operation "Upcoming" {
fragments "row.graphql"; max rows 30; watch;
}
query Enriched id "enriched" root Collection<interface TaskFacts> document "enriched.graphql" operation "Enriched" {
max rows 30; allow TaskFacts.score select "Small visible page only";
}
query Ranked id "ranked" root Collection<interface TaskFacts> document "ranked.graphql" operation "Ranked" {
max rows 30; max candidates 60;
allow TaskFacts.score predicate "Bounded local collection";
allow TaskFacts.score order "Bounded local collection";
}
query Totals id "totals" root Collection<interface TaskFacts> document "totals.graphql" operation "Totals" {
max rows 30; watch;
}
query ScoreTotals id "score-totals" root Collection<interface TaskFacts> document "score-totals.graphql" operation "ScoreTotals" {
max rows 30; max candidates 60;
allow TaskFacts.score aggregate "Complete bounded collection report";
}
}`;
sources.Queries = packageSource;
const pkgResult = compileCapabilityResourceSource(packageSource, {
source: queryFixtureSource,
environment: { interfaces, interfaceClosure: [...interfaces.values()] },
});
assert.ok(pkgResult.ok, JSON.stringify(pkgResult.diagnostics));
if (pkgResult.resource.kind !== "package") throw new Error("package expected");
const pkg: PackageRevision = pkgResult.resource.revision;
const allInterfaces = [...interfaces.values(), ...(pkgResult.resource.specializations ?? [])];
const documents: Record<string, string> = {
"upcoming.graphql": `query Upcoming($first: Int!, $after: Cursor) {root {items(first: $first, after: $after, where: {done: {eq: false}}, orderBy: [{rank: ASC}]) {entries {key cursor node {_qx {ref} ...TaskRow}} pageInfo {hasNextPage endCursor}}}}`,
"row.graphql": `fragment TaskRow on TaskFacts {title rank done assignee {name}}`,
"enriched.graphql": `query Enriched {root {items(first: 3) {entries {key node {_qx {ref} title score}}}}}`,
"ranked.graphql": `query Ranked {root {items(first: 3, where: {score: {gt: 0}}, orderBy: [{score: DESC}]) {entries {key node {_qx {ref} title}}}}}`,
"totals.graphql": `query Totals {root {_qx {relations {items {aggregate {count sum {rank}} groups(by: {done: true}, first: 10) {entries {group {done} aggregate {count sum {rank}}}}}}}}}`,
"score-totals.graphql": `query ScoreTotals {root {_qx {relations {items {aggregate {count sum {score}}}}}}}`,
};
pkg.checkedQueries = await Promise.all(
pkg.queries!.map((query) =>
compileQuery(query, allInterfaces, async (name) =>
options.live
? documents[name]!.replace(/\btitle\b/g, "title @live").replace(
"title @live rank done",
"title @live rank @live done @live",
)
: documents[name]!,
),
),
);
const implementation = (atom: string) => `conform ${atom} as TaskFacts id "${atom}-facts" {
private state Title${atom} id "${atom}:title" on ${atom} : string policy crdt(string) default "Untitled";
private state Done${atom} id "${atom}:done" on ${atom} : bool policy optimistic-register default false;
private state Rank${atom} id "${atom}:rank" on ${atom} : int64 policy optimistic-register default 0;
private edge Assignee${atom} id "${atom}:assignee" {
atom ${atom} projection assignee id "${atom}:assignee:forward" optional-one;
interface PersonFacts projection tasks id "${atom}:assignee:inverse" many;
}
bind title.get to state Title${atom}.read;
${options.live ? `bind title.set to state Title${atom}.write;` : `bind title.set to package Queries.titleSet with {title to state Title${atom};};`}
bind done to state Done${atom}; bind rank to state Rank${atom};
bind assignee.resolve to edge Assignee${atom}.assignee.resolve;
bind score.get to package Queries.score query-reason "Explicit bounded score computation";
}`;
const workspaceSource = `workspace QueryFixture id "query-fixture" revision "query-fixture@1" commit "${queryFixtureSource.commit}" {
import interface PersonFacts; import interface TaskFacts; import interface Collection; import package Queries;
atom Task id "Task"; atom Reminder id "Reminder"; atom Person id "Person"; atom Tasks id "Tasks";
conform Person as PersonFacts id "person-facts" {
private state Name id "person:name" on Person : string policy optimistic-register default "Nobody";
bind name to state Name;
}
${implementation("Task")} ${implementation("Reminder")}
conform Tasks as Collection<interface TaskFacts> id "tasks-collection" {
private edge Items id "items" {
atom Tasks projection items id "items:forward" many ordered;
interface TaskFacts projection collections id "items:inverse" many;
}
bind items.resolve to edge Items.items.resolve;
}
}`;
sources.workspace = workspaceSource;
const result = compileCapabilitySource(workspaceSource, "workspace.qx", {
interfaces,
interfaceClosure: allInterfaces,
packages: new Map([["Queries", pkg]]),
packageClosure: [pkg],
});
assert.ok(result.ok, JSON.stringify(result.diagnostics));
const workspace = { ...result.workspace, linkedQueries: linkQueries(result.workspace) };
return { workspace, pkg, interfaces: allInterfaces, sources, documents };
}