Add exported query fragments and scoped coverage observations

This commit is contained in:
Timothy J. Aveni
2026-09-22 16:18:57 -07:00
parent eba85ba158
commit a8869e4545
13 changed files with 2503 additions and 1715 deletions
+30 -7
View File
@@ -37,6 +37,7 @@ import { querySchema } from "./schema.js";
import { objectRow } from "./relational-schema.js";
import { workspaceQueryRootId } from "./root.js";
import { relationalCompiler } from "./relational-compile.js";
import { fragmentSources, type FragmentResolver } from "./fragments.js";
import {
QueryCompileError,
type QueryDeclaration,
@@ -67,6 +68,7 @@ export async function compileQuery(
declaration: QueryDeclaration,
interfaces: readonly InterfaceRevision[],
read: (name: string) => Promise<string>,
resolveFragment?: FragmentResolver,
): Promise<CheckedQuery> {
const paths = [declaration.document, ...declaration.fragments];
if (paths.length > 128 || new Set(paths).size !== paths.length)
@@ -86,7 +88,10 @@ export async function compileQuery(
const document = parse(new Source(text, path), { maxTokens: 10000 });
definitions.push(...document.definitions);
}
const fragmentContracts = await fragmentSources(declaration, definitions, resolveFragment);
const document: DocumentNode = { kind: "Document" as DocumentNode["kind"], definitions };
if (definitions.length > 256 || Buffer.byteLength(print(document)) > 262144)
fail("QUERY_SOURCE_LIMIT", "Expanded fragment sources exceed the query source budget");
visit(document, {
Directive(node) {
if (node.name.value === "live")
@@ -112,7 +117,7 @@ export async function compileQuery(
if (node.name.value.startsWith("__")) fail("QUERY_UNSUPPORTED_FEATURE", "Introspection is not supported", node);
},
Directive(node) {
if (!["include", "skip", "live"].includes(node.name.value))
if (!["include", "skip"].includes(node.name.value))
fail("QUERY_UNSUPPORTED_FEATURE", `Unsupported directive ${node.name.value}`, node);
},
InlineFragment(node) {
@@ -400,11 +405,27 @@ export async function compileQuery(
inherited: QuerySelection["conditions"] = [],
): QuerySelection[] =>
set.selections.flatMap((node): QuerySelection[] => {
if (node.kind === "FragmentSpread")
return selection(fragments.get(node.name.value)!.selectionSet, parent, [
...inherited,
...conditions(node.directives),
]);
if (node.kind === "FragmentSpread") {
const inheritedConditions = [...inherited, ...conditions(node.directives)];
const contract = fragmentContracts.get(node.name.value);
if (contract && generated.byName.get(parent.name)?.revisionId !== contract.root)
fail("FRAGMENT_ROOT_MISMATCH", "Imported fragment requires its exact nominal root interface", node);
return [
...(contract
? [
{
name: "$fragment",
key: "",
selection: [],
arguments: {},
conditions: inheritedConditions,
fragmentSpread: contract,
},
]
: []),
...selection(fragments.get(node.name.value)!.selectionSet, parent, inheritedConditions),
];
}
if (node.kind !== "Field") return [];
const contract = generated.byName.get(parent.name);
const member = contract?.members.find((entry) => entry.displayName === node.name.value);
@@ -471,6 +492,7 @@ export async function compileQuery(
.map((entry) => [entry.variable.name.value, argument(entry.defaultValue!)]),
),
sourceFiles: paths,
...(fragmentContracts.size ? { fragmentContracts: Object.fromEntries(fragmentContracts) } : {}),
};
}
@@ -478,10 +500,11 @@ export const compileRepositoryQuery = async (
root: string,
declaration: QueryDeclaration,
interfaces: readonly InterfaceRevision[],
resolveFragment?: FragmentResolver,
) => {
const started = performance.now();
try {
return await compileQuery(declaration, interfaces, (name) => readRepositorySource(root, name));
return await compileQuery(declaration, interfaces, (name) => readRepositorySource(root, name), resolveFragment);
} finally {
// Timing belongs in check logs, never in immutable artifact identities.
console.error(