233 lines
9.7 KiB
TypeScript
233 lines
9.7 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
import { compileCapabilityResourceSource } from "../src/capability-language/parser.js";
|
|
import { compileQuery } from "../src/query/compile.js";
|
|
import type { PackageRevision } from "../src/capability-model/types.js";
|
|
const source = { repository: "https://example.test/fragments", commit: "a".repeat(40) };
|
|
const compiled = compileCapabilityResourceSource(
|
|
`interface Item id "item" revision "item@1" {
|
|
queryable value title id "title" : string {get id "title:get" execution snapshot targets [browser,server];}
|
|
queryable rpc value remote id "remote" : string {get id "remote:get";}
|
|
}`,
|
|
{ source },
|
|
);
|
|
if (!compiled.ok || compiled.resource.kind !== "interface") throw Error(JSON.stringify(compiled.diagnostics));
|
|
const item = compiled.resource.revision;
|
|
function pkg(body: string, imports: PackageRevision[] = [], name = "Views") {
|
|
const result = compileCapabilityResourceSource(
|
|
`import interface Item; ${imports.map((p) => `import package ${p.displayName};`).join(" ")} package ${name} id "${name}" revision "${name}@1" {${body}}`,
|
|
{
|
|
source,
|
|
environment: {
|
|
interfaces: new Map([["Item", item]]),
|
|
interfaceClosure: [item],
|
|
packages: new Map(imports.map((p) => [p.displayName, p])),
|
|
},
|
|
},
|
|
);
|
|
assert.ok(result.ok, JSON.stringify(result.diagnostics));
|
|
if (result.resource.kind !== "package") throw Error("package required");
|
|
return result.resource.revision;
|
|
}
|
|
test("exported fragments generate local projection and direct admission", async () => {
|
|
const p = pkg('query fragment Row id "row" root Item document "row.graphql" fragment "Row";');
|
|
assert.equal(p.queries!.length, 2);
|
|
const local = p.queries!.find((q) => !q.fragment?.admission)!;
|
|
assert.equal(local.placement?.kind, "local");
|
|
const checked = await compileQuery(local, [item], async () => "fragment Row on Item {title}");
|
|
assert.equal(checked.output.kind, "record");
|
|
assert.match(checked.document, /query QuixosFragment/);
|
|
await assert.rejects(
|
|
compileQuery(local, [item], async () => "fragment Row on Item {remote}"),
|
|
/QUERY_LOCAL_DEPENDENCY/,
|
|
);
|
|
});
|
|
test("imported fragments retain checked occurrences and require exact roots", async () => {
|
|
const provider = pkg('query fragment Row id "row" root Item document "row.graphql" fragment "Row";', [], "Provider");
|
|
const checked = await compileQuery(
|
|
provider.queries!.find((q) => !q.fragment?.admission)!,
|
|
[item],
|
|
async () => "fragment Row on Item {title}",
|
|
);
|
|
const consumer = pkg(
|
|
'query Screen id "screen" root Item document "screen.graphql" operation "Screen" {replica; fragments Provider.Row;}',
|
|
[provider],
|
|
);
|
|
const result = await compileQuery(
|
|
consumer.queries![0]!,
|
|
[item],
|
|
async () => "query Screen {root {...Row}}",
|
|
async () => checked,
|
|
);
|
|
assert.equal(result.selection[0]!.selection[0]!.fragmentSpread?.digest, checked.definitionDigest);
|
|
assert.equal(result.selection[0]!.selection[0]!.fragmentSpread?.root, item.revisionId);
|
|
assert.ok(result.effects.some((e) => e.memberId === "title"));
|
|
const bound = await compileQuery(
|
|
{
|
|
...consumer.queries![0]!,
|
|
importedFragments: consumer.queries![0]!.importedFragments!.map((entry) => ({ ...entry, mode: "reference" })),
|
|
},
|
|
[item],
|
|
async () => "query Screen {root {...Row}}",
|
|
async () => checked,
|
|
);
|
|
assert.equal(bound.selection[0]!.selection.find((entry) => entry.fragmentSpread)?.fragmentSpread?.mode, "reference");
|
|
assert(
|
|
!bound.selection[0]!.selection.some((entry) => entry.memberId === "title"),
|
|
"A fragment binding does not evaluate its fields in the parent",
|
|
);
|
|
assert(!JSON.stringify(bound.output).includes('"title"'), "Parent output excludes separately observed fields");
|
|
const wrong = {
|
|
...checked,
|
|
declaration: {
|
|
...checked.declaration,
|
|
root: { ...checked.declaration.root, interface: "different@1" as typeof item.revisionId },
|
|
},
|
|
};
|
|
await assert.rejects(
|
|
compileQuery(
|
|
consumer.queries![0]!,
|
|
[item],
|
|
async () => "query Screen {root {...Row}}",
|
|
async () => wrong,
|
|
),
|
|
/FRAGMENT_ROOT_MISMATCH/,
|
|
);
|
|
});
|
|
test("fragment variable mappings are explicit and checked", async () => {
|
|
const provider = pkg(
|
|
'query fragment Row id "row" root Item document "row.graphql" fragment "Row" operation "Inputs";',
|
|
[],
|
|
"Provider",
|
|
);
|
|
const checked = await compileQuery(
|
|
provider.queries!.find((q) => !q.fragment?.admission)!,
|
|
[item],
|
|
async () =>
|
|
"query Inputs($show: Boolean!) {root {...Row}} fragment Row on Item {_camino {ref} title @include(if:$show)}",
|
|
);
|
|
const build = (mapping: string) =>
|
|
pkg(
|
|
`query Screen id "screen" root Item document "screen.graphql" operation "Screen" {replica; fragments Provider.Row ${mapping};}`,
|
|
[provider],
|
|
).queries![0]!;
|
|
const document = "query Screen($visible: Boolean!) {root {...Row}}";
|
|
await assert.rejects(
|
|
compileQuery(
|
|
build(""),
|
|
[item],
|
|
async () => document,
|
|
async () => checked,
|
|
),
|
|
/FRAGMENT_VARIABLE_MAPPING/,
|
|
);
|
|
const result = await compileQuery(
|
|
build("with {show to visible;}"),
|
|
[item],
|
|
async () => document,
|
|
async () => checked,
|
|
);
|
|
assert.deepEqual(result.selection[0]!.selection[0]!.fragmentSpread?.variables, { show: "visible" });
|
|
const referenceDeclaration = build("with {show to visible;}");
|
|
referenceDeclaration.importedFragments = referenceDeclaration.importedFragments!.map((entry) => ({
|
|
...entry,
|
|
mode: "reference",
|
|
}));
|
|
const binding = await compileQuery(
|
|
referenceDeclaration,
|
|
[item],
|
|
async () => document,
|
|
async () => checked,
|
|
);
|
|
assert.deepEqual(binding.selection[0]!.selection[0]!.fragmentSpread?.variables, { show: "visible" });
|
|
assert.notEqual(binding.definitionDigest, result.definitionDigest);
|
|
await assert.rejects(
|
|
compileQuery(
|
|
referenceDeclaration,
|
|
[item],
|
|
async () => document.replace("$visible: Boolean!", "$visible: Boolean!, $unused: Boolean!"),
|
|
async () => checked,
|
|
),
|
|
/never used/,
|
|
);
|
|
const changedDigest = await compileQuery(
|
|
referenceDeclaration,
|
|
[item],
|
|
async () => document,
|
|
async () => ({ ...checked, definitionDigest: "b".repeat(64) }),
|
|
);
|
|
assert.notEqual(
|
|
changedDigest.definitionDigest,
|
|
binding.definitionDigest,
|
|
"A bound fragment's identity remains in the parent contract digest",
|
|
);
|
|
await assert.rejects(
|
|
compileQuery(
|
|
build("with {show to visible;}"),
|
|
[item],
|
|
async () => document.replace("Boolean!", "Boolean"),
|
|
async () => checked,
|
|
),
|
|
/FRAGMENT_VARIABLE_TYPE/,
|
|
);
|
|
});
|
|
|
|
test("independent fragment package survives execution lowering and typed client generation", async () => {
|
|
const { loadReaderWorkspace, readReaderFixture } = await import("./reader-fixture.js");
|
|
const { compileExecutionPlan } = await import("../src/execution-world/plan.js");
|
|
const { compileQueryProgram } = await import("../src/execution-world/queries.js");
|
|
const { generatePackageQueryClientBindings } = await import("../src/execution-world/client-contracts.js");
|
|
const workspace = await loadReaderWorkspace();
|
|
const comment = workspace.interfaceImports.find((i) => i.displayName === "CommentData")!;
|
|
const parsed = compileCapabilityResourceSource(
|
|
`import interface CommentData; package CommentRows id "comment-rows" revision "comment-rows@1" {
|
|
query fragment Row id "row" root CommentData document "row.graphql" fragment "CommentRow";
|
|
}`,
|
|
{ source, environment: { interfaces: new Map([["CommentData", comment]]), interfaceClosure: [comment] } },
|
|
);
|
|
assert.ok(parsed.ok, JSON.stringify(parsed.diagnostics));
|
|
if (parsed.resource.kind !== "package") throw Error("expected package");
|
|
const provider = parsed.resource.revision;
|
|
provider.checkedQueries = await Promise.all(
|
|
provider.queries!.map((q) =>
|
|
compileQuery(
|
|
q,
|
|
workspace.interfaceImports,
|
|
async () => "fragment CommentRow on CommentData {content {text tags}}",
|
|
),
|
|
),
|
|
);
|
|
workspace.packageImports.push(provider);
|
|
const consumer = workspace.packageImports.find((p) => p.displayName === "ReaderRuntime")!;
|
|
const declaration = consumer.queries!.find((q) => q.displayName === "ReaderScreen")!;
|
|
declaration.importedFragments = [
|
|
{ packageRevisionId: provider.revisionId, packageAlias: "CommentRows", fragmentId: "row", variables: {} },
|
|
];
|
|
const document = (await readReaderFixture("queries/reader-screen.graphql")).replace(
|
|
"_camino { ref } content { text tags }",
|
|
"...CommentRow",
|
|
);
|
|
const checked = await compileQuery(
|
|
declaration,
|
|
workspace.interfaceImports,
|
|
async () => document,
|
|
async () => provider.checkedQueries!.find((q) => q.declaration.id === "row")!,
|
|
);
|
|
consumer.checkedQueries = consumer.checkedQueries!.map((q) => (q.declaration.id === declaration.id ? checked : q));
|
|
const plan = compileExecutionPlan(workspace);
|
|
const program = compileQueryProgram(plan) as {
|
|
queries: { id: string; fragment?: { package: string; id: string }; selection: unknown }[];
|
|
};
|
|
assert.deepEqual(program.queries.find((q) => q.fragment)?.fragment, {
|
|
package: provider.revisionId,
|
|
id: "row",
|
|
digest: provider.checkedQueries!.find((q) => q.declaration.id === "row")!.definitionDigest,
|
|
});
|
|
const screen = program.queries.find((q) => q.id === JSON.stringify([consumer.revisionId, declaration.id]))!;
|
|
assert.match(JSON.stringify(screen.selection), /fragmentSpread/);
|
|
const generated = generatePackageQueryClientBindings(consumer, undefined, workspace.packageImports);
|
|
assert.match(generated, /"CommentRows": \{"Row": definePackageFragment/);
|
|
assert.ok(generated.includes(JSON.stringify(comment.revisionId)));
|
|
assert.ok(!generated.includes('"Row$inputs": definePackageQuery'));
|
|
});
|