Add exported query fragments and scoped coverage observations
This commit is contained in:
+30
-7
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user