Move React navigation query generation behind an explicit build frontend. Add checked result-dependent query bindings and pure composition evaluation, including one-snapshot server execution. Preserve the existing rendering path while the React manifest, worker preparation, and replica admission integration remain pending; see docs/PREPARED_PRESENTATIONS_PROGRESS.md.
160 lines
5.7 KiB
TypeScript
160 lines
5.7 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { mkdtemp, mkdir, writeFile, rm } from "node:fs/promises";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import test from "node:test";
|
|
import {
|
|
compileCapabilityResourceRepository,
|
|
type PackageQueryGenerator,
|
|
} from "../src/capability-language/assembly.js";
|
|
import { defaultQueryBudgets } from "../src/query/types.js";
|
|
|
|
test("an explicit non-React frontend emits checked queries for its own selection model", async (context) => {
|
|
const directory = await mkdtemp(path.join(os.tmpdir(), "qx-query-generator-"));
|
|
context.after(() => rm(directory, { recursive: true, force: true }));
|
|
const commit = "a".repeat(40);
|
|
const source = (name: string) => ({
|
|
resolver: "git" as const,
|
|
repository: `https://example.test/${name}.git`,
|
|
commit,
|
|
});
|
|
const lock = (imports: string) => `quixos-lock version 1 {
|
|
quixos source { repository "https://example.test/quixos.git"; commit "${commit}"; }
|
|
${imports}
|
|
}`;
|
|
const dependency = (name: string) =>
|
|
`interface ${name} source { repository "${source(name).repository}"; commit "${commit}"; }`;
|
|
for (const name of ["Sensor", "DeviceSelection", "Device"]) await mkdir(path.join(directory, name));
|
|
await writeFile(path.join(directory, "Sensor", "quixos.lock"), lock(""));
|
|
await writeFile(
|
|
path.join(directory, "Sensor", "interface.qx"),
|
|
`
|
|
interface Sensor id "sensor" revision "sensor@1" {
|
|
queryable value reading id "reading" : int64 { get id "reading:get"; }
|
|
}
|
|
`,
|
|
);
|
|
await writeFile(path.join(directory, "DeviceSelection", "quixos.lock"), lock(dependency("Sensor")));
|
|
await writeFile(
|
|
path.join(directory, "DeviceSelection", "interface.qx"),
|
|
`
|
|
import interface Sensor;
|
|
interface DeviceSelection id "device-selection" revision "device-selection@1" {
|
|
queryable relation selectedSensor id "selected-sensor" : optional-one interface Sensor {
|
|
resolve id "selected-sensor:resolve";
|
|
}
|
|
}
|
|
`,
|
|
);
|
|
await writeFile(path.join(directory, "Device", "quixos.lock"), lock(dependency("DeviceSelection")));
|
|
await writeFile(
|
|
path.join(directory, "Device", "package.qx"),
|
|
`
|
|
import interface DeviceSelection;
|
|
package Device id "device" revision "device@1" {}
|
|
`,
|
|
);
|
|
await writeFile(
|
|
path.join(directory, "Device", "inputs.graphql"),
|
|
`query Selected { root { selectedSensor { reading } } }`,
|
|
);
|
|
const options = {
|
|
rootDirectory: path.join(directory, "Device"),
|
|
kind: "package" as const,
|
|
source: source("Device"),
|
|
resolveResource: async (ref: { repository: string }) => ({
|
|
directory: path.join(directory, new URL(ref.repository).pathname.slice(1, -4)),
|
|
}),
|
|
};
|
|
const plain = await compileCapabilityResourceRepository(options);
|
|
assert.equal(plain.resource.kind, "package");
|
|
if (plain.resource.kind !== "package") throw Error("Expected package");
|
|
assert.equal(plain.resource.revision.queries?.length ?? 0, 0);
|
|
|
|
const generateQueries: PackageQueryGenerator = async ({ interfaces, readSource }) => ({
|
|
queries: [
|
|
{
|
|
id: "selected-inputs",
|
|
displayName: "SelectedInputs",
|
|
root: {
|
|
kind: "object",
|
|
interface: interfaces.find((iface) => iface.displayName === "DeviceSelection")!.revisionId,
|
|
},
|
|
document: "generated/selected.graphql",
|
|
operation: "Selected",
|
|
fragments: [],
|
|
views: [],
|
|
allowances: [],
|
|
budgets: { ...defaultQueryBudgets },
|
|
watch: false,
|
|
placement: { kind: "replica" },
|
|
},
|
|
],
|
|
documents: new Map([["generated/selected.graphql", await readSource("inputs.graphql")]]),
|
|
compositions: [
|
|
{
|
|
id: "display",
|
|
maxQueries: 1,
|
|
input: {
|
|
kind: "record",
|
|
fields: {
|
|
device: {
|
|
kind: "object-ref",
|
|
expectation: {
|
|
kind: "interface",
|
|
interfaceRevisionId: interfaces.find((iface) => iface.displayName === "DeviceSelection")!.revisionId,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
steps: [
|
|
{
|
|
name: "selection",
|
|
query: "selected-inputs",
|
|
root: { from: "input", path: ["device"] },
|
|
arguments: { record: {} },
|
|
},
|
|
],
|
|
},
|
|
],
|
|
});
|
|
const built = await compileCapabilityResourceRepository({ ...options, generateQueries });
|
|
assert.equal(built.resource.kind, "package");
|
|
if (built.resource.kind !== "package") throw Error("Expected package");
|
|
assert.equal(built.resource.revision.checkedQueries?.length, 1);
|
|
const checked = built.resource.revision.checkedQueries![0]!;
|
|
assert.equal(checked.declaration.root.interface, "device-selection@1");
|
|
assert.ok(checked.effects.some((effect) => effect.memberId === "reading"));
|
|
assert.ok(checked.effects.some((effect) => effect.memberId === "selected-sensor"));
|
|
assert.ok(checked.definitionDigest);
|
|
assert.equal(built.resource.revision.queryCompositions?.[0]?.dependencies[0]?.digest, checked.definitionDigest);
|
|
assert.equal(
|
|
built.resources.some((resource) => /React|PresentationMount/.test(resource.key)),
|
|
false,
|
|
);
|
|
|
|
await assert.rejects(
|
|
compileCapabilityResourceRepository({
|
|
...options,
|
|
generateQueries: async (input) => {
|
|
const result = await generateQueries(input);
|
|
return {
|
|
...result,
|
|
documents: new Map([["generated/selected.graphql", "query Selected { root { invented } }"]]),
|
|
};
|
|
},
|
|
}),
|
|
/invented/,
|
|
);
|
|
await assert.rejects(
|
|
compileCapabilityResourceRepository({
|
|
...options,
|
|
generateQueries: async (input) => {
|
|
const result = await generateQueries(input);
|
|
return { ...result, queries: [...result.queries, ...result.queries] };
|
|
},
|
|
}),
|
|
/Generated query conflicts/,
|
|
);
|
|
});
|