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 { QueryCompileError } from "../src/query/types.js"; import type { InterfaceRevision } from "../src/capability-model/types.js"; const source = { repository: "https://example.test/queries.git", commit: "a".repeat(40) }; const iface = (text: string, dependencies: InterfaceRevision[] = []) => { const compiled = compileCapabilityResourceSource(text, { source, environment: { interfaces: new Map(dependencies.map((entry) => [entry.displayName, entry])), interfaceClosure: dependencies, }, }); assert.ok(compiled.ok, JSON.stringify(compiled.diagnostics)); assert.equal(compiled.resource.kind, "interface"); if (compiled.resource.kind !== "interface") throw new Error("interface required"); return compiled.resource.revision; }; const facts = iface(`interface TaskFacts id "facts" revision "facts@1" { queryable value title id "title" : string { get id "title:get"; } queryable value done id "done" : bool { get id "done:get"; } queryable value due id "due" : optional { get id "due:get"; } queryable rpc value score id "score" : int32 { get id "score:get"; } value hidden id "hidden" : string { get id "hidden:get"; } }`); const collection = iface( `import interface TaskFacts; interface Tasks id "tasks" revision "tasks@1" { queryable relation items id "items" : many interface TaskFacts ordered { resolve id "items:resolve"; } }`, [facts], ); function fixture(clauses = "") { const result = compileCapabilityResourceSource( `import interface Tasks; import interface TaskFacts; package Queries id "queries" revision "queries@1" { query Upcoming id "upcoming" root Tasks document "queries/upcoming.graphql" operation "Upcoming" { fragments "queries/row.graphql"; max rows 30; ${clauses} } }`, { source, environment: { interfaces: new Map([ ["Tasks", collection], ["TaskFacts", facts], ]), interfaceClosure: [collection, facts], }, }, ); assert.ok(result.ok, JSON.stringify(result.diagnostics)); assert.equal(result.resource.kind, "package"); if (result.resource.kind !== "package") throw new Error("package required"); assert.equal(result.resource.revision.exports.length, 0); return result.resource.revision.queries![0]!; } const document = `query Upcoming($first: Int!, $before: Int64!) { root { items(first: $first, where: {done: {eq: false}, due: {lt: $before}}, orderBy: [{due: ASC}]) { entries { key node { _qx { ref } ...Row } } pageInfo { hasNextPage endCursor } } } }`; const compile = (query = document, row = "fragment Row on TaskFacts { title due }", clauses = "") => compileQuery(fixture(clauses), [collection, facts], async (name) => (name.endsWith("row.graphql") ? row : query)); test("fixed GraphQL yields exact effects, typed references and distinct query artifacts", async () => { const checked = await compile(); assert.deepEqual(checked.variables, { kind: "record", fields: { first: { kind: "scalar", name: "int32" }, before: { kind: "scalar", name: "int64" }, }, }); assert.match(JSON.stringify(checked.output), /"kind":"object-ref"/); assert.ok( checked.effects.some( (effect) => effect.memberId === "due" && effect.uses.includes("order") && effect.uses.includes("predicate"), ), ); assert.equal(checked.definitionDigest, (await compile()).definitionDigest); assert.notEqual( checked.definitionDigest, (await compile(document, "fragment Row on TaskFacts { title }")).definitionDigest, ); }); test("query allowlist and two-sided RPC effects fail during compilation", async () => { const rejects = (promise: Promise, code: string) => assert.rejects(promise, (error: unknown) => error instanceof QueryCompileError && error.code === code); await rejects(compile(document, "fragment Row on TaskFacts { hidden }"), "QUERY_VALIDATION"); await rejects(compile(document, "fragment Row on TaskFacts { score }"), "QUERY_RPC_CONSUMER_REASON"); await compile( document, "fragment Row on TaskFacts { score }", 'allow TaskFacts.score select "Only the visible page is enriched";', ); await rejects( compile(document, "fragment Row on TaskFacts { score }", 'watch; allow TaskFacts.score select "Visible rows";'), "QUERY_WATCH_UNSUPPORTED", ); await compile( document, "fragment Row on TaskFacts { score }", 'watch; poll 5000 "External score has no push API"; allow TaskFacts.score select "Visible rows";', ); await rejects(compile(document.replace("first: $first", "first: 31")), "QUERY_VALIDATION"); await rejects(compile(document.replace("...Row", "__typename ...Row")), "QUERY_UNSUPPORTED_FEATURE"); await rejects(compile(document, "fragment Row on TaskFacts { ...Row }"), "QUERY_VALIDATION"); });