import { parse, Source, validate, specifiedRules, PossibleFragmentSpreadsRule, NoUnusedVariablesRule, GraphQLError, print, printSchema, visit, TypeInfo, visitWithTypeInfo, getNamedType, isNonNullType, isListType, isObjectType, isInputObjectType, isScalarType, isEnumType, typeFromAST, type GraphQLType, type SelectionSetNode, type FragmentDefinitionNode, type ValueNode, type ASTNode, type DocumentNode, type DirectiveNode, } from "graphql"; import { createHash } from "node:crypto"; import { canonicalJson } from "../capability-model/evolution.js"; import { readRepositorySource } from "../capability-language/source-loader.js"; import { valueType, type InterfaceRevision, type ValueType, type InterfaceRevisionId, } from "../capability-model/types.js"; 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 { lowerQueryTree, stripQueryBoundaries } from "./tree.js"; import { QueryCompileError, type QueryDeclaration, type CheckedQuery, type QueryFieldEffect, type QueryUse, type QueryArgument, type QuerySelection, } from "./types.js"; const fail = (code: string, message: string, node?: ASTNode): never => { const token = node?.loc?.startToken; throw new QueryCompileError( code, message, token ? { file: node!.loc!.source.name, line: token.line, column: token.column, } : undefined, ); }; /** Shared checker for authored queries and bounded public query documents. * Runtime execution receives its checked IR, never an unchecked document. */ export async function compileQuery( declaration: QueryDeclaration, interfaces: readonly InterfaceRevision[], read: (name: string) => Promise, resolveFragment?: FragmentResolver, ): Promise { const paths = [declaration.document, ...declaration.fragments]; if (paths.length > 128 || new Set(paths).size !== paths.length) fail("QUERY_SOURCE_LIMIT", "Query files must be distinct and bounded to 128"); const definitions: DocumentNode["definitions"][number][] = []; let totalBytes = 0; for (const path of paths) { if ( !path.endsWith(".graphql") || path.startsWith("/") || path.split(/[\\/]/).some((part) => !part || part === "." || part === "..") ) fail("QUERY_SOURCE_PATH", `Invalid query source path ${path}`); const text = await read(path); totalBytes += Buffer.byteLength(text); if (totalBytes > 262144) fail("QUERY_SOURCE_LIMIT", "Query sources exceed 256 KiB"); const document = parse(new Source(text, path), { maxTokens: 10000 }); definitions.push(...document.definitions); } const fragmentContracts = await fragmentSources(declaration, definitions, resolveFragment); let 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") fail( "QUERY_RETIRED_DIRECTIVE", "@live is retired. Observations watch ordinary query values; use checked operations for mutations.", node, ); }, }); const generated = querySchema(declaration, interfaces, document); // Identity belongs to the checked object result, including fragment results. // Schema membership deliberately excludes structured values and query wrappers. const identity = parse("query Identity { _camino { ref } }", { noLocation: true }).definitions[0]!; if (identity.kind !== "OperationDefinition") throw new Error("Invalid identity selection"); const identitySelection = identity.selectionSet.selections[0]!; const identityTypes = new TypeInfo(generated.schema); document = visit( document, visitWithTypeInfo(identityTypes, { SelectionSet: { leave(node, _key, parentNode) { // An inline projection is part of its containing object. Its identity // remains that object's base interface, not the projected interface. if (parentNode && !Array.isArray(parentNode) && "kind" in parentNode && parentNode.kind === "InlineFragment") return; const parent = identityTypes.getParentType(); const contract = parent && generated.byName.get(parent.name); if (!contract || contract.revisionId === workspaceQueryRootId) return; const hasIdentity = node.selections.some( (field) => field.kind === "Field" && field.name.value === "_camino" && !field.alias && !field.directives?.length && field.selectionSet?.selections.some( (child) => child.kind === "Field" && child.name.value === "ref" && !child.alias && !child.directives?.length, ), ); // GraphQL validation caches comparisons by AST identity. Each nominal // object must get its own metadata subtree, even though the spelling is // identical (its reference scalar can be different). if (!hasIdentity) return { ...node, selections: [...node.selections, structuredClone(identitySelection)] }; }, }, }), ); const operations = document.definitions.filter((entry) => entry.kind === "OperationDefinition"); if ( operations.length !== 1 || operations[0]!.operation !== "query" || operations[0]!.name?.value !== declaration.operation ) fail("QUERY_UNSUPPORTED_FEATURE", "Exactly one named query matching the declaration is required"); visit(document, { Field(node) { if (node.name.value === "_unavailable") fail("QUERY_AGGREGATE_TYPE", "No supported field exists at this operator path", node); if (node.name.value.startsWith("__")) fail("QUERY_UNSUPPORTED_FEATURE", "Introspection is not supported", node); }, Directive(node) { if (!["include", "skip", "boundary"].includes(node.name.value)) fail("QUERY_UNSUPPORTED_FEATURE", `Unsupported directive ${node.name.value}`, node); }, }); // Capability interfaces are nominal views of one graph object, not mutually // exclusive GraphQL object variants. Check overlap using declared requirements. const projectionTypes = new TypeInfo(generated.schema); const requires = (from: InterfaceRevisionId, to: InterfaceRevisionId, seen = new Set()): boolean => { if (from === to) return true; if (seen.has(from)) return false; seen.add(from); return generated.contracts.get(from)?.requiredInterfaces?.some((id) => requires(id, to, seen)) ?? false; }; visit( document, visitWithTypeInfo(projectionTypes, { Field(node) { const parent = projectionTypes.getParentType(); const member = parent && generated.byName.get(parent.name)?.members.find((member) => member.displayName === node.name.value); if (member?.kind === "relationship") generated.target(member); }, InlineFragment(node) { if (!node.typeCondition) return; const from = projectionTypes.getParentType(); const a = from && generated.byName.get(from.name); const b = generated.byName.get(node.typeCondition.name.value); if (!a || !b || !requires(a.revisionId, b.revisionId)) fail( "QUERY_UNSUPPORTED_FEATURE", "Inline interface projections currently require a declared interface requirement", node, ); }, FragmentSpread(node) { const fragment = document.definitions.find( (entry) => entry.kind === "FragmentDefinition" && entry.name.value === node.name.value, ); if ( fragment?.kind === "FragmentDefinition" && fragment.typeCondition.name.value !== projectionTypes.getParentType()?.name ) fail("QUERY_UNSUPPORTED_FEATURE", "Named fragments currently require the exact nominal root interface", node); }, }), ); const bindingVariables = new Set( [...fragmentContracts.values()] .filter((entry) => entry.mode === "reference") .flatMap((entry) => Object.values(entry.variables)), ); const errors = validate( generated.schema, document, [ ...specifiedRules.filter((rule) => rule !== PossibleFragmentSpreadsRule && rule !== NoUnusedVariablesRule), (context) => ({ OperationDefinition: { leave(operation) { const used = new Set(context.getRecursiveVariableUsages(operation).map(({ node }) => node.name.value)); for (const variable of operation.variableDefinitions ?? []) { const name = variable.variable.name.value; if (!used.has(name) && !bindingVariables.has(name)) context.reportError(new GraphQLError(`Variable "$${name}" is never used.`, { nodes: variable })); } }, }, }), ], { maxErrors: 20 }, ); if (errors.length) { const error = errors[0]!; fail("QUERY_VALIDATION", error.message, error.nodes?.[0]); } const effects = new Map(); const fragments = new Map( document.definitions .filter((entry): entry is FragmentDefinitionNode => entry.kind === "FragmentDefinition") .map((entry) => [entry.name.value, entry]), ); const mark = (id: InterfaceRevisionId, name: string, use: QueryUse, node: ASTNode) => { // The virtual extent has no provider/getter effect. Its membership is // linked separately, including when the query only selects references. if (id === workspaceQueryRootId) return; const contract = generated.contracts.get(id)!; const member = contract.members.find((entry) => entry.displayName === name); if (!member || member.kind === "operation" || member.kind === "module" || !member.queryRead) return fail("QUERY_FIELD_NOT_QUERYABLE", `${contract.displayName}.${name} is not queryable`, node); if (declaration.placement?.kind === "local") { const getter = member.operations.find( (operation) => operation.displayName === (member.kind === "value" ? "get" : "resolve"), ); if (getter?.execution?.kind !== "snapshot") fail( "QUERY_LOCAL_DEPENDENCY", `Local query requires a graph snapshot getter for ${contract.displayName}.${name}; split the remote field or move the query to the server`, node, ); if (member.queryRead.execution === "rpc-permitted") fail( "QUERY_LOCAL_DEPENDENCY", `Local query cannot include remote-compute field ${contract.displayName}.${name}`, node, ); } const key = `${id}\0${member.id}`; const effect = effects.get(key) ?? { interfaceRevisionId: id, memberId: member.id, uses: [], execution: member.queryRead.execution, }; if (!effect.uses.includes(use)) effect.uses.push(use); effects.set(key, effect); if ( effect.execution === "rpc-permitted" && !declaration.allowances.some( (allow) => allow.interfaceRevisionId === id && allow.memberId === member.id && allow.uses.includes(use) && allow.reason.trim(), ) ) fail("QUERY_RPC_CONSUMER_REASON", `${contract.displayName}.${name} needs an allowance for ${use}`, node); if ( declaration.watch && effect.execution === "rpc-permitted" && !declaration.polling && !member.operations.some((op) => op.displayName === "watch-start") ) fail( "QUERY_WATCH_UNSUPPORTED", `${contract.displayName}.${name} has no watch; explicitly acknowledge polling or use a one-shot query`, node, ); }; const relational = relationalCompiler(generated, declaration, mark, fragments); const compiledPredicates = new WeakMap(); const inputEffects = (id: InterfaceRevisionId, value: ValueNode, use: "predicate" | "order") => { if (use === "predicate") { relational.predicate(objectRow(id), value); return; } if (value.kind === "Variable") { // A dynamic filter may name any field in its declared input type. for (const member of generated.contracts.get(id)!.members) if (member.kind === "value" && member.queryRead) mark(id, member.displayName, use, value); } else if (value.kind === "ListValue") value.values.forEach((child) => inputEffects(id, child, use)); else if (value.kind === "ObjectValue") for (const field of value.fields) { mark(id, field.name.value, use, field); } }; const info = new TypeInfo(generated.schema); visit( document, visitWithTypeInfo(info, { Field(node) { const parent = info.getParentType(); const contract = parent && generated.byName.get(parent.name); if (!contract || node.name.value === "_camino") return; mark(contract.revisionId, node.name.value, "select", node); const member = contract.members.find((entry) => entry.displayName === node.name.value)!; if (member.kind !== "relationship" || (member.cardinality !== "many" && member.cardinality !== "many-unique")) return; const target = generated.target(member); const bounds = (node.arguments ?? []).filter( (argument) => argument.name.value === "first" || argument.name.value === "all", ); if (bounds.length !== 1) fail("QUERY_ROW_LIMIT", "Specify exactly one of first or all", node); if (bounds[0]!.name.value === "all" && node.arguments?.some((argument) => argument.name.value === "after")) fail("QUERY_UNSUPPORTED_FEATURE", "Bounded-all does not accept a continuation", node); for (const argument of node.arguments ?? []) { if (argument.name.value === "where") compiledPredicates.set( node, relational.predicate( objectRow(target, member.keyType ? { keyType: member.keyType } : undefined), argument.value, ), ); if (argument.name.value === "orderBy") inputEffects(target, argument.value, "order"); if ( ["first", "all"].includes(argument.name.value) && argument.value.kind !== "Variable" && (argument.value.kind !== "IntValue" || Number(argument.value.value) < 1 || Number(argument.value.value) > declaration.budgets.rows) ) fail( "QUERY_ROW_LIMIT", `${argument.name.value} must be between 1 and ${declaration.budgets.rows}`, argument, ); } if (node.arguments?.some((a) => a.name.value === "after")) { const rpcOrder = (value: ValueNode): boolean => value.kind === "Variable" ? generated.contracts .get(target)! .members.some((m) => m.kind === "value" && m.queryRead?.execution === "rpc-permitted") : value.kind === "ListValue" ? value.values.some(rpcOrder) : value.kind === "ObjectValue" && value.fields.some((f) => generated.contracts .get(target)! .members.some( (m) => m.displayName === f.name.value && m.kind === "value" && m.queryRead?.execution === "rpc-permitted", ), ); const order = node.arguments.find((a) => a.name.value === "orderBy"); if (relational.needsRpc(compiledPredicates.get(node)) || (order && rpcOrder(order.value))) fail( "QUERY_UNSUPPORTED_FEATURE", "RPC predicates/order require a bounded first/all window without continuation", node, ); } }, FragmentSpread(node) { const fragment = definitions.find( (entry) => entry.kind === "FragmentDefinition" && entry.name.value === node.name.value, ) as FragmentDefinitionNode; if (fragment.typeCondition.name.value !== info.getParentType()?.name) fail("QUERY_UNSUPPORTED_FEATURE", "Fragments must select the exact current type", node); }, }), ); let expandedFields = 0; // GraphQL merges repeated response keys. In particular, fragments may each // contribute different children of one relationship; last-write-wins would // silently remove fields from both the generated type and the wire codec. const mergeOutput = (left: ValueType, right: ValueType): ValueType => { const a = left.kind === "optional" ? left.value : left; const b = right.kind === "optional" ? right.value : right; let result = a; if (a.kind === "record" && b.kind === "record") { const fields = { ...a.fields }; for (const [key, type] of Object.entries(b.fields)) fields[key] = fields[key] ? mergeOutput(fields[key], type) : type; result = { kind: "record", fields }; } else if (a.kind === "list" && b.kind === "list") result = valueType.list(mergeOutput(a.value, b.value)); return left.kind === "optional" && right.kind === "optional" ? valueType.optional(result) : result; }; const output = (type: GraphQLType, selections?: SelectionSetNode, depth = 0, conditional = false): ValueType => { if (depth > declaration.budgets.depth * 4 + 4) fail("QUERY_DEPTH_LIMIT", "Expanded query exceeds its depth budget"); if (isNonNullType(type)) return required(type.ofType, selections, depth, conditional); return valueType.optional(required(type, selections, depth, conditional)); }; const required = (type: GraphQLType, selections?: SelectionSetNode, depth = 0, conditional = false): ValueType => { if (isListType(type)) return valueType.list(output(type.ofType, selections, depth, conditional)); if (isObjectType(type)) { const fields: Record = {}; const add = (set: SelectionSetNode, conditional = false, selectedType = type) => { for (const selection of set.selections) { if (++expandedFields > 10000) fail("QUERY_WORK_LIMIT", "Expanded query exceeds 10000 fields", selection); if (selection.kind === "FragmentSpread") { if (selection.directives?.some((directive) => directive.name.value === "boundary")) continue; if (fragmentContracts.get(selection.name.value)?.mode === "reference") continue; add( fragments.get(selection.name.value)!.selectionSet, conditional || !!selection.directives?.length, selectedType, ); continue; } if (selection.kind === "InlineFragment") { const projected = selection.typeCondition ? generated.schema.getType(selection.typeCondition.name.value) : selectedType; if (!isObjectType(projected)) return fail("QUERY_TYPE", "Expected an object projection", selection); add(selection.selectionSet, conditional || !!selection.directives?.length, projected); continue; } if (selection.kind !== "Field") continue; const key = selection.alias?.value ?? selection.name.value; const field = selectedType.getFields()[selection.name.value]!; const previous = fields[key]; fields[key] = output( field.type, selection.selectionSet, depth + 1, conditional || !!selection.directives?.some((entry) => entry.name.value !== "live"), ); if ( (conditional || selection.directives?.some((entry) => entry.name.value !== "live")) && fields[key]!.kind !== "optional" ) fields[key] = valueType.optional(fields[key]!); if (previous) fields[key] = mergeOutput(previous, fields[key]!); } }; // A condition on this object makes the object optional, not every child // of the object when it is present. Fragment conditions are handled by add. if (selections) add(selections); return { kind: "record", fields }; } return ( generated.scalarTypes.get(getNamedType(type)!.name) ?? fail("QUERY_TYPE", `Unsupported query scalar ${getNamedType(type)!.name}`) ); }; const variables: Record = {}; const inputShape = (type: GraphQLType, seen = new Set()): ValueType => { if (isNonNullType(type)) return inputRequired(type.ofType, seen); return valueType.optional(inputRequired(type, seen)); }; const inputRequired = (type: GraphQLType, seen: Set): ValueType => { if (isListType(type)) return valueType.list(inputShape(type.ofType, seen)); if (isInputObjectType(type)) { // Recursive boolean filter inputs need generated recursive TS shapes; never degrade to any. if (seen.has(type.name)) fail( "QUERY_UNSUPPORTED_FEATURE", "Pass scalar variables inside fixed filter expressions instead of a whole recursive filter", ); return { kind: "record", fields: Object.fromEntries( Object.entries(type.getFields()).map(([key, field]) => [ key, inputShape(field.type, new Set([...seen, type.name])), ]), ), }; } if (isEnumType(type)) return valueType.string; if (isScalarType(type)) return generated.scalarTypes.get(type.name) ?? fail("QUERY_TYPE", `Unsupported query scalar ${type.name}`); return fail("QUERY_VARIABLE_TYPE", "Unsupported variable type"); }; for (const variable of operations[0]!.variableDefinitions ?? []) variables[variable.variable.name.value] = inputShape(typeFromAST(generated.schema, variable.type)!); const result = required(generated.schema.getQueryType()!, operations[0]!.selectionSet); const argument = (node: ValueNode): QueryArgument => { switch (node.kind) { case "Variable": return { kind: "variable", name: node.name.value }; case "ListValue": return { kind: "list", values: node.values.map(argument) }; case "ObjectValue": return { kind: "object", fields: Object.fromEntries(node.fields.map((field) => [field.name.value, argument(field.value)])), }; case "NullValue": return { kind: "literal", value: null }; case "BooleanValue": return { kind: "literal", value: node.value }; // Int literals stay decimal until their checked field codec interprets them. default: return { kind: "literal", value: node.value }; } }; const conditions = (directives: readonly DirectiveNode[] = []) => directives .filter((directive) => ["include", "skip"].includes(directive.name.value)) .map((directive) => ({ include: directive.name.value === "include", value: argument(directive.arguments!.find((entry) => entry.name.value === "if")!.value), })); const selection = ( set: SelectionSetNode, parent: import("graphql").GraphQLObjectType, inherited: QuerySelection["conditions"] = [], ): QuerySelection[] => { const selections = set.selections.flatMap((node): QuerySelection[] => { if (node.kind === "InlineFragment") { const projected = node.typeCondition ? generated.schema.getType(node.typeCondition.name.value) : parent; if (!isObjectType(projected)) return fail("QUERY_TYPE", "Expected an object projection", node); return selection(node.selectionSet, projected, [...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); const directive = node.directives?.find((directive) => directive.name.value === "boundary"); if (directive) { const name = directive.arguments?.find((argument) => argument.name.value === "name")?.value; if (name?.kind !== "StringValue" || !/^[A-Za-z_][A-Za-z0-9_]{0,63}$/.test(name.value)) return fail( "QUERY_BOUNDARY_NAME", "A boundary needs a literal identifier of at most 64 characters", directive, ); const root = generated.byName.get(parent.name)?.revisionId; if (!root || root === workspaceQueryRootId) return fail("QUERY_BOUNDARY_ROOT", "A boundary must select a nominal Graph Object", node); if (contract?.mode === "reference") fail("QUERY_BOUNDARY_REFERENCE", "Use an ordinary checked Fragment spread for a boundary", node); const fragment = fragments.get(node.name.value)!; const token = node.loc?.startToken; return [ { name: "$boundary", key: "", arguments: {}, conditions: inheritedConditions, boundary: { name: name.value, root, output: required(parent, fragment.selectionSet), ...(token ? { location: { file: node.loc!.source.name, line: token.line, column: token.column } } : {}), }, selection: [ ...(contract ? [ { name: "$fragment", key: "", selection: [], arguments: {}, conditions: [], fragmentSpread: contract, }, ] : []), ...selection(fragment.selectionSet, parent), ], }, ]; } return [ ...(contract ? [ { name: "$fragment", key: "", selection: [], arguments: {}, conditions: inheritedConditions, fragmentSpread: contract, }, ] : []), ...(contract?.mode === "reference" ? [] : 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); const type = getNamedType(parent.getFields()[node.name.value]!.type); const referenceType = type && generated.scalarTypes.get(type.name); const role = generated.relational.roles.get(parent.name); const relationship = role?.kind === "relations" ? generated.relational.relations(role.row).find((m) => m.displayName === node.name.value) : undefined; const plan = relationship && role?.row.kind === "object" && node.selectionSet ? relational.plan(role.row.interfaceRevisionId, relationship, node.selectionSet, (set, name) => { const t = generated.schema.getType(name); if (!t || !isObjectType(t)) return fail("QUERY_TYPE", `Missing generated output type ${name}`, node); return selection(set, t); }) : undefined; return [ { name: node.name.value, key: node.alias?.value ?? node.name.value, ...(member && contract ? { interfaceRevisionId: contract.revisionId, memberId: member.id } : {}), ...(member?.kind === "relationship" ? { targetInterfaceRevisionId: generated.target(member) } : {}), ...(node.name.value === "ref" && referenceType?.kind === "object-ref" && referenceType.expectation.kind === "interface" ? { targetInterfaceRevisionId: referenceType.expectation.interfaceRevisionId } : {}), conditions: [...inherited, ...conditions(node.directives)], arguments: Object.fromEntries( (node.arguments ?? []).map((entry) => [entry.name.value, argument(entry.value)]), ), selection: node.selectionSet && isObjectType(type) ? selection(node.selectionSet, type) : [], ...(plan ? { relational: plan } : {}), ...(compiledPredicates.has(node) ? { predicate: compiledPredicates.get(node)! } : {}), }, ]; }); const contract = generated.byName.get(parent.name); const names = new Set(); for (const field of selections) if (field.boundary) { if (names.has(field.boundary.name)) fail("QUERY_BOUNDARY_DUPLICATE", `Duplicate boundary ${field.boundary.name}`); names.add(field.boundary.name); } if (!contract || contract.revisionId === workspaceQueryRootId) return selections; // Fragment expansion may repeat metadata. Combine its children so a later // implicit reference cannot overwrite an explicitly selected aggregate. const metadata = selections.filter((field) => field.key === "_camino" && field.name === "_camino"); const responseFields = new Map(); for (const field of selections.filter((field) => field.memberId)) { const previous = responseFields.get(field.key); if ( previous && (previous.interfaceRevisionId !== field.interfaceRevisionId || previous.memberId !== field.memberId) ) fail("QUERY_PROJECTION_CONFLICT", `Alias distinct interface fields selected as ${field.key}`); responseFields.set(field.key, field); } if (!metadata.length) return selections; const children = metadata.flatMap((field) => field.selection.map((child) => ({ ...child, conditions: [...field.conditions, ...child.conditions], })), ); const ref = children.find((child) => child.key === "ref" && child.name === "ref")!; return [ ...selections.filter((field) => !metadata.includes(field)), { ...metadata[0]!, conditions: [], selection: [...children.filter((child) => child.key !== "ref"), { ...ref, conditions: [] }], }, ]; }; const normalized = print(document), schema = printSchema(generated.schema); const selections = selection(operations[0]!.selectionSet, generated.schema.getQueryType()!); const tree = lowerQueryTree(declaration, selections, result); const definitionDigest = createHash("sha256") .update( canonicalJson({ semantics: tree ? 7 : 6, declaration, normalized, fragmentContracts: Object.fromEntries(fragmentContracts), // Source provenance belongs to the checked installation. Identical public // contracts keep their query identity when retained in another repository. // Closed generic identity remains explicit in revisionId/application; // its template's retention source is provenance too. byName also holds // names probed while discovering nominal projections, so only include // interfaces actually present in the generated query schema. interfaces: Object.values(generated.schema.getTypeMap()) .filter(isObjectType) .flatMap((type) => { const revision = generated.byName.get(type.name); if (!revision) return []; const { source: _source, application, ...contract } = revision; if (!application) return [contract]; const { source: _templateSource, ...identity } = application; return [{ ...contract, application: identity }]; }) .sort((a, b) => (a.revisionId < b.revisionId ? -1 : a.revisionId > b.revisionId ? 1 : 0)), }), ) .digest("hex"); return { declaration, definitionDigest, document: normalized, schema, variables: { kind: "record", fields: variables }, output: result, effects: [...effects.values()], selection: tree ? stripQueryBoundaries(selections) : selections, ...(tree ? { tree } : {}), variableDefaults: Object.fromEntries( (operations[0]!.variableDefinitions ?? []) .filter((entry) => entry.defaultValue) .map((entry) => [entry.variable.name.value, argument(entry.defaultValue!)]), ), sourceFiles: paths, ...(fragmentContracts.size ? { fragmentContracts: Object.fromEntries(fragmentContracts) } : {}), }; } 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), resolveFragment); } finally { // Timing belongs in check logs, never in immutable artifact identities. console.error( `[query-check] ${JSON.stringify(declaration.displayName)}: ${(performance.now() - started).toFixed(1)}ms`, ); } };