Implement scoped interface extents across QX, generated bindings, SDK, Orch, Camino and browser queries. Preserve native projection, aggregation, pagination and watch invalidation without synthetic persisted objects. Validate exact query grants and scope freshness on execution and replay. Add local PostgreSQL, transport, browser and code-generation coverage. C1 implementation slice only: full-text indexes and Studio search adoption (C2/C3) remain tracked in the implementation handoff. No publication or deployment.
140 lines
8.0 KiB
TypeScript
140 lines
8.0 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 Extent id "extent" root workspace TaskFacts document "extent.graphql" operation "Extent" {
|
|
max rows 30; watch;
|
|
}
|
|
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> = {
|
|
"extent.graphql": `query Extent {root {objects(first: 30, orderBy: [{rank: ASC}]) {entries {node {_qx {ref} title rank}}}}}`,
|
|
"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 };
|
|
}
|