Implement progressive query admission, session boundaries and event-driven Studio observations
This commit is contained in:
@@ -179,3 +179,11 @@ await writeFile(
|
||||
"dist/portable-reader/variants/entries.ts",
|
||||
"export type Entries = " + tsType(entries.valueType) + ";\n",
|
||||
);
|
||||
|
||||
const { queryTreeFixture } = await import("./query-tree-fixture.js");
|
||||
const { generatePackageQueryClientBindings } = await import("../src/execution-world/client-contracts.js");
|
||||
const treeFixture = await queryTreeFixture();
|
||||
await mkdir("dist/portable-reader/query-tree", { recursive: true });
|
||||
await writeFile("dist/portable-reader/query-tree/queries.ts", generatePackageQueryClientBindings(treeFixture.pkg));
|
||||
await writeFile("dist/portable-reader/query-tree/queries.json", JSON.stringify(compileQueryProgram(treeFixture.plan)));
|
||||
await writeFile("dist/portable-reader/query-tree/kernel.json", JSON.stringify(compileKernelProgram(treeFixture.plan)));
|
||||
|
||||
Vendored
+10
-2
@@ -8,10 +8,18 @@ export const queryFixtureSource = {
|
||||
repository: "https://query-fixture.example.test/source.git",
|
||||
commit: "1".repeat(40),
|
||||
};
|
||||
export async function queryWorkspaceFixture(options: { live?: boolean } = {}) {
|
||||
export async function queryWorkspaceFixture(options: { live?: boolean; snapshot?: boolean } = {}) {
|
||||
const interfaces = new Map<string, InterfaceRevision>();
|
||||
const sources: Record<string, string> = {};
|
||||
function iface(name: string, members: string, parameters = "") {
|
||||
if (options.snapshot) members = members.replaceAll(" ordered", "");
|
||||
if (options.snapshot)
|
||||
for (const member of ["name", "title", "done", "rank", "assignee", "items"])
|
||||
for (const operation of ["get", "resolve"])
|
||||
members = members.replace(
|
||||
`${operation} id "${member}:${operation}";`,
|
||||
`${operation} id "${member}:${operation}" execution snapshot;`,
|
||||
);
|
||||
const text = `${[...interfaces.keys()].map((name) => `import interface ${name};`).join("\n")}
|
||||
interface ${name}${parameters} id "${name}" revision "${name}@1" {${members}}`;
|
||||
sources[name] = text;
|
||||
@@ -117,7 +125,7 @@ export async function queryWorkspaceFixture(options: { live?: boolean } = {}) {
|
||||
${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;
|
||||
atom Tasks projection items id "items:forward" many${options.snapshot ? "" : " ordered"};
|
||||
interface TaskFacts projection collections id "items:inverse" many;
|
||||
}
|
||||
bind items.resolve to edge Items.items.resolve;
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { queryWorkspaceFixture } from "./fixtures/query-workspace.js";
|
||||
import { compileQuery } from "../src/query/compile.js";
|
||||
import { queryTreeProjections } from "../src/query/tree.js";
|
||||
import { linkQueries } from "../src/query/link.js";
|
||||
import { compileExecutionPlan } from "../src/execution-world/plan.js";
|
||||
|
||||
export async function queryTreeFixture() {
|
||||
const fixture = await queryWorkspaceFixture({ live: true, snapshot: true });
|
||||
const declaration = {
|
||||
...fixture.pkg.queries!.find((query) => query.id === "upcoming")!,
|
||||
id: "tree",
|
||||
displayName: "Tree",
|
||||
operation: "Tree",
|
||||
placement: { kind: "replica" as const },
|
||||
fragments: [],
|
||||
};
|
||||
const checked = await compileQuery(
|
||||
declaration,
|
||||
fixture.interfaces,
|
||||
async () => `query Tree($first:Int!, $details:Boolean!) {
|
||||
root {items(first:$first) {entries {node {
|
||||
...Heading @boundary(name:"basic") ...Details @boundary(name:"details") @include(if:$details)
|
||||
assignee {...Person @boundary(name:"person")}
|
||||
}}}}
|
||||
} fragment Heading on TaskFacts {title} fragment Details on TaskFacts {rank done}
|
||||
fragment Person on PersonFacts {name}`,
|
||||
);
|
||||
const projections = queryTreeProjections(checked);
|
||||
fixture.pkg.queries = [declaration, ...projections.map((query) => query.declaration)];
|
||||
fixture.pkg.checkedQueries = [checked, ...projections];
|
||||
// Workspace imports retain the checked Package by value in the assembler.
|
||||
const imported = fixture.workspace.packageImports.find((pkg) => pkg.revisionId === fixture.pkg.revisionId)!;
|
||||
imported.queries = fixture.pkg.queries;
|
||||
imported.checkedQueries = fixture.pkg.checkedQueries;
|
||||
fixture.workspace.linkedQueries = linkQueries(fixture.workspace);
|
||||
return { ...fixture, plan: compileExecutionPlan(fixture.workspace) };
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
import { compileCapabilityResourceSource } from "../src/capability-language/index.js";
|
||||
import { compileQuery } from "../src/query/compile.js";
|
||||
import type { InterfaceRevision } from "../src/capability-model/types.js";
|
||||
|
||||
const source = { repository: "https://example.test/tree", commit: "a".repeat(40) };
|
||||
function iface(text: string, dependencies: InterfaceRevision[] = []) {
|
||||
const result = compileCapabilityResourceSource(text, {
|
||||
source,
|
||||
environment: {
|
||||
interfaces: new Map(dependencies.map((value) => [value.displayName, value])),
|
||||
interfaceClosure: dependencies,
|
||||
},
|
||||
});
|
||||
assert.ok(result.ok, JSON.stringify(result.diagnostics));
|
||||
if (result.resource.kind !== "interface") throw Error("Expected Interface");
|
||||
return result.resource.revision;
|
||||
}
|
||||
const item = iface(`interface Item id "item" revision "item@1" {
|
||||
queryable value title id "title" : string {get id "title:get" execution snapshot;}
|
||||
queryable value body id "body" : string {get id "body:get" execution snapshot;}
|
||||
}`);
|
||||
const navigation = iface(
|
||||
`import interface Item; interface Navigation id "nav" revision "nav@1" {
|
||||
queryable value chrome id "chrome" : string {get id "chrome:get" execution snapshot;}
|
||||
queryable relation document id "document" : optional-one interface Item {resolve id "document:resolve" execution snapshot;}
|
||||
queryable relation items id "items" : many interface Item ordered {resolve id "items:resolve" execution snapshot;}
|
||||
}`,
|
||||
[item],
|
||||
);
|
||||
function declaration(root: InterfaceRevision = item) {
|
||||
const result = compileCapabilityResourceSource(
|
||||
`import interface ${root.displayName}; package Views id "views" revision "views@1" {
|
||||
query Open id "open" root ${root.displayName} document "open.graphql" operation "Open" {replica; max rows 10;}
|
||||
}`,
|
||||
{ source, environment: { interfaces: new Map([[root.displayName, root]]), interfaceClosure: [root, item] } },
|
||||
);
|
||||
assert.ok(result.ok, JSON.stringify(result.diagnostics));
|
||||
if (result.resource.kind !== "package") throw Error("Expected Package");
|
||||
return result.resource.revision.queries![0]!;
|
||||
}
|
||||
const fragments = `fragment Heading on Item {title} fragment Body on Item {body}`;
|
||||
test("boundaries remain complete typed projections; ordinary spreads stay inline", async () => {
|
||||
const checked = await compileQuery(
|
||||
declaration(),
|
||||
[item],
|
||||
async () => `query Open {root {...Heading ...Body @boundary(name:"body")}} ${fragments}`,
|
||||
);
|
||||
assert(checked.tree);
|
||||
assert(!JSON.stringify(checked.output).includes('"body"'));
|
||||
assert(!JSON.stringify(checked.selection).includes('"body"'));
|
||||
const body = checked.tree.nodes.find((node) => node.id === "root/boundary/body")!;
|
||||
assert(body);
|
||||
assert(
|
||||
JSON.stringify(checked.tree.projections.find((projection) => projection.id === body.projection)!.output).includes(
|
||||
'"body"',
|
||||
),
|
||||
);
|
||||
assert.equal(checked.tree.nodes[0]!.boundaries[0]!.name, "body");
|
||||
const ordinary = await compileQuery(
|
||||
declaration(),
|
||||
[item],
|
||||
async () => `query Open {root {...Heading ...Body}} ${fragments}`,
|
||||
);
|
||||
assert.equal(ordinary.tree, undefined);
|
||||
assert(JSON.stringify(ordinary.output).includes('"body"'));
|
||||
});
|
||||
test("minimal nullable-root bindings exclude slow siblings and carry child handles", async () => {
|
||||
const checked = await compileQuery(
|
||||
declaration(navigation),
|
||||
[item, navigation],
|
||||
async () => `query Open {root {
|
||||
...Chrome @boundary(name:"chrome") document {...Heading @boundary(name:"basic") ...Body @boundary(name:"body")}
|
||||
}} fragment Chrome on Navigation {chrome} ${fragments}`,
|
||||
);
|
||||
const tree = checked.tree!;
|
||||
const binding = tree.nodes[0]!.bindings[0]!;
|
||||
assert.equal(binding.name, "document");
|
||||
const projection = tree.projections.find((projection) => projection.id === binding.projection)!;
|
||||
assert(!JSON.stringify(projection.selection).includes('"chrome"'));
|
||||
assert(!JSON.stringify(projection.selection).includes('"title"'));
|
||||
assert(!JSON.stringify(projection.selection).includes('"body"'));
|
||||
assert.deepEqual(binding.targets[0]!.path, ["document"]);
|
||||
assert.deepEqual(
|
||||
tree.nodes.find((node) => node.id === binding.targets[0]!.node)!.boundaries.map((boundary) => boundary.name),
|
||||
["basic", "body"],
|
||||
);
|
||||
});
|
||||
test("bounded list bindings preserve entry identity without requesting details", async () => {
|
||||
const checked = await compileQuery(
|
||||
declaration(navigation),
|
||||
[item, navigation],
|
||||
async () => `query Open {root {
|
||||
items(first: 3) {entries {node {...Body @boundary(name:"body")}}}
|
||||
}} fragment Body on Item {body}`,
|
||||
);
|
||||
const tree = checked.tree!,
|
||||
binding = tree.nodes[0]!.bindings[0]!;
|
||||
assert.deepEqual(binding.targets[0]!.path, ["items", "entries", "[]", "node"]);
|
||||
const projection = tree.projections.find((projection) => projection.id === binding.projection)!;
|
||||
assert(JSON.stringify(projection.selection).includes('"name":"key"'));
|
||||
assert(!JSON.stringify(projection.selection).includes('"name":"body"'));
|
||||
assert(tree.maxOccurrences <= 256);
|
||||
});
|
||||
test("guards control boundary participation and invalid syntax fails checking", async () => {
|
||||
const checked = await compileQuery(
|
||||
declaration(),
|
||||
[item],
|
||||
async () => `query Open($show:Boolean!) {root {
|
||||
...Body @boundary(name:"body") @include(if:$show)
|
||||
}} fragment Body on Item {body}`,
|
||||
);
|
||||
assert.deepEqual(checked.tree!.nodes[0]!.boundaries[0]!.conditions, [
|
||||
{ include: true, value: { kind: "variable", name: "show" } },
|
||||
]);
|
||||
for (const [text, error] of [
|
||||
[`query Open {root {...Body @boundary(name:"")}} fragment Body on Item {body}`, /QUERY_BOUNDARY_NAME/],
|
||||
[
|
||||
`query Open($name:String!) {root {...Body @boundary(name:$name)}} fragment Body on Item {body}`,
|
||||
/QUERY_BOUNDARY_NAME/,
|
||||
],
|
||||
[
|
||||
`query Open {root {...Body @boundary(name:"x") ...Heading @boundary(name:"x")}} ${fragments}`,
|
||||
/QUERY_BOUNDARY_DUPLICATE/,
|
||||
],
|
||||
[`query Open {root {body @boundary(name:"x")}}`, /QUERY_VALIDATION/],
|
||||
[`query Open {root {...Cycle @boundary(name:"x")}} fragment Cycle on Item {...Cycle}`, /QUERY_VALIDATION/],
|
||||
] as const)
|
||||
await assert.rejects(
|
||||
compileQuery(declaration(), [item], async () => text),
|
||||
error,
|
||||
);
|
||||
});
|
||||
test("equivalent repeated paths share bindings; aliases preserve different windows", async () => {
|
||||
const checked = await compileQuery(
|
||||
declaration(navigation),
|
||||
[item, navigation],
|
||||
async () => `query Open {root {
|
||||
document {...Heading @boundary(name:"basic")} document {...Body @boundary(name:"body")}
|
||||
recent:items(first:2) {entries {node {...Body @boundary(name:"body")}}}
|
||||
earlier:items(first:3) {entries {node {...Body @boundary(name:"body")}}}
|
||||
}} ${fragments}`,
|
||||
);
|
||||
assert.deepEqual(
|
||||
checked.tree!.nodes[0]!.bindings.map((binding) => binding.name),
|
||||
["document", "recent", "earlier"],
|
||||
);
|
||||
assert.deepEqual(
|
||||
checked
|
||||
.tree!.nodes.find((node) => node.id === "root/binding/document")!
|
||||
.boundaries.map((boundary) => boundary.name),
|
||||
["basic", "body"],
|
||||
);
|
||||
await assert.rejects(
|
||||
compileQuery(
|
||||
declaration(navigation),
|
||||
[item, navigation],
|
||||
async () => `query Open($show:Boolean!) {root {
|
||||
document @include(if:$show) {...Heading @boundary(name:"basic")} document {...Body @boundary(name:"body")}
|
||||
}} ${fragments}`,
|
||||
),
|
||||
/QUERY_TREE_BINDING_CONFLICT/,
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user