import { createHash } from "node:crypto"; import { parse, print, visit, Kind, type SelectionSetNode, type FragmentDefinitionNode, type SelectionNode, type DocumentNode, } from "graphql"; import type { PackageRevision, WorkspaceRevision } from "../capability-model/types.js"; import { compileQuery } from "../query/compile.js"; import { compileQueryComposition, type QueryBinding } from "../query/composition.js"; import type { CheckedQuery, QueryDeclaration } from "../query/types.js"; import { preparationArtifact, type ReactModulePreparation, type ReactPreparationArtifact } from "./declarations.js"; interface Branch { packageRevision: string; id: string; } type Child = ReactModulePreparation["children"][number] & { branches: Branch[] }; interface Module { pkg: PackageRevision; preparation: ReactModulePreparation; } const identity = (pkg: string, id: string) => JSON.stringify([pkg, id]); const suffix = (value: string) => createHash("sha256").update(value).digest("hex").slice(0, 20); /** Lower bounded, statically bound subject projections into ordinary fragments. * The generated query still passes the normal GraphQL/QX checker. Stateful * lookups remain independent ordinary root-scoped queries owned by the adapter. */ export async function expandPresentationQueries(workspace: WorkspaceRevision): Promise { const modules = new Map(); for (const pkg of workspace.packageImports) { const artifact = pkg.buildArtifacts?.[preparationArtifact] as ReactPreparationArtifact | undefined; for (const preparation of artifact?.modules ?? []) modules.set(identity(pkg.revisionId, preparation.module), { pkg, preparation }); } const query = (pkg: string, id: string) => { const result = workspace.packageImports .find((entry) => entry.revisionId === pkg) ?.checkedQueries?.find((entry) => entry.declaration.id === id); if (!result) throw Error(`Missing checked preparation query ${pkg}/${id}`); return result; }; const resolver = async (pkg: string, id: string) => query(pkg, id); const install = async (pkg: PackageRevision, declaration: QueryDeclaration, document: string) => { const views = new Map(declaration.views.map((view) => [view.atomId, view])); const allowances = [...declaration.allowances]; for (const imported of declaration.importedFragments ?? []) { const dependency = query(imported.packageRevisionId, imported.fragmentId).declaration; for (const view of dependency.views) { const previous = views.get(view.atomId); if (previous && previous.interfaceRevisionId !== view.interfaceRevisionId) throw Error(`${declaration.displayName}: conflicting interface views for ${view.atomId}`); views.set(view.atomId, view); } for (const allowance of dependency.allowances) if (!allowances.some((entry) => JSON.stringify(entry) === JSON.stringify(allowance))) allowances.push(allowance); } declaration = { ...declaration, views: [...views.values()], allowances }; const artifact = pkg.buildArtifacts?.[preparationArtifact] as ReactPreparationArtifact | undefined; if (artifact) artifact.internalQueries = [...new Set([...(artifact.internalQueries ?? []), declaration.id])].sort(); const checked = await compileQuery(declaration, workspace.interfaceImports, async () => document, resolver); pkg.queries = [...(pkg.queries ?? []).filter((entry) => entry.id !== declaration.id), declaration]; pkg.checkedQueries = [ ...(pkg.checkedQueries ?? []).filter((entry) => entry.declaration.id !== declaration.id), checked, ]; return checked; }; function expanded(source: CheckedQuery): SelectionSetNode { const document = parse(source.document); const fragments = new Map( document.definitions .filter((entry): entry is FragmentDefinitionNode => entry.kind === Kind.FRAGMENT_DEFINITION) .map((entry) => [entry.name.value, entry]), ); const operation = document.definitions.find((entry) => entry.kind === Kind.OPERATION_DEFINITION); if (!operation || operation.kind !== Kind.OPERATION_DEFINITION) throw Error(`Missing query operation ${source.declaration.id}`); const expand = (selection: SelectionSetNode, parents: string[] = []): SelectionSetNode => ({ ...selection, selections: selection.selections.flatMap((entry): SelectionNode[] => { if (entry.kind === Kind.FRAGMENT_SPREAD) { if (parents.includes(entry.name.value)) throw Error("Cyclic query fragment"); const fragment = fragments.get(entry.name.value); if (!fragment) throw Error(`Missing query fragment ${entry.name.value}`); return [ { kind: Kind.INLINE_FRAGMENT, typeCondition: fragment.typeCondition, directives: entry.directives ?? [], selectionSet: expand(fragment.selectionSet, [...parents, entry.name.value]), }, ]; } return [{ ...entry, ...(entry.selectionSet ? { selectionSet: expand(entry.selectionSet, parents) } : {}) }]; }), }); return expand(operation.selectionSet); } function insert(selection: SelectionSetNode, path: readonly string[], spread: string): SelectionSetNode { if (!path.length) return { ...selection, selections: [...selection.selections, { kind: Kind.FRAGMENT_SPREAD, name: { kind: Kind.NAME, value: spread } }], }; let found = false; const selections = selection.selections.map((entry): SelectionNode => { if (entry.kind === Kind.INLINE_FRAGMENT) { try { const nested = insert(entry.selectionSet, path, spread); found = true; return { ...entry, selectionSet: nested }; } catch (error) { if (!(error instanceof Error) || error.message !== "REACT_SELECTION_PATH") throw error; } } if (entry.kind === Kind.FIELD && (entry.alias?.value ?? entry.name.value) === path[0] && entry.selectionSet) { found = true; return { ...entry, selectionSet: insert(entry.selectionSet, path.slice(1), spread) }; } return entry; }); if (!found) throw Error("REACT_SELECTION_PATH"); return { ...selection, selections }; } function location(binding: QueryBinding): { name: string; path: string[] } | undefined { if ("from" in binding && binding.from === "result") return { name: binding.name, path: [...binding.path] }; if ("flatMap" in binding) { const root = location(binding.flatMap.source); return root ? { name: root.name, path: [...root.path, ...binding.flatMap.path] } : undefined; } return undefined; } const done = new Set(); const eager = new Map(); const admissionFragments = new Map(); const stateInputs = new Map(); async function expandModule(key: string): Promise { if (done.has(key)) return; const module = modules.get(key); if (!module) throw Error(`Missing preparation module ${key}`); const { pkg, preparation } = module; const children = preparation.children as Child[]; for (const child of children) for (const branch of child.branches) await expandModule(identity(branch.packageRevision, branch.id)); const composition = pkg.queryCompositions!.find((entry) => entry.id === preparation.composition)!; const steps = structuredClone(composition.steps); const exported: { step: string; query: CheckedQuery }[] = []; for (const step of steps) { const original = query(pkg.revisionId, step.query); // Variable roots/arguments remain checked dependent steps. Constants can // be substituted without requesting new runtime parameters from a parent. if (!("record" in step.arguments) || Object.values(step.arguments.record).some((value) => !("literal" in value))) continue; const constants = step.arguments.record; let selection = expanded(original); if (original.declaration.placement?.kind !== "local") throw Error(key + "." + step.name + ": expected checked local input"); const source = query(pkg.revisionId, original.declaration.placement.sourceQueryId); let admissionSelection = expanded(source); const admissionImports: NonNullable = []; const imports: NonNullable = []; for (const child of children) { const selected = child.with ? location(child.with) : child.each ? location(child.each.source) : location(child.root); if (!selected || selected.name !== step.name) continue; const rootPath = "from" in child.root && child.root.from === "item" ? child.root.path : []; const path = [...selected.path, ...rootPath]; if (path.slice(-2).join(".") !== "_camino.ref") { if (!rootPath.length && (child.with || child.each)) throw Error(`${key}.${child.name}: child root must select the item's reference`); } else path.splice(-2); for (const branch of child.branches) { for (const input of eager.get(identity(branch.packageRevision, branch.id)) ?? []) { const declaration = input.query.declaration; if ( !imports.some( (entry) => entry.packageRevisionId === branch.packageRevision && entry.fragmentId === declaration.id, ) ) { imports.push({ packageRevisionId: branch.packageRevision, fragmentId: declaration.id, variables: {}, mode: "reference", }); } selection = insert(selection, path, declaration.fragment!.name); const admission = admissionFragments.get(identity(branch.packageRevision, declaration.id))!; if ( !admissionImports.some( (entry) => entry.packageRevisionId === branch.packageRevision && entry.fragmentId === admission.declaration.id, ) ) admissionImports.push({ packageRevisionId: branch.packageRevision, fragmentId: admission.declaration.id, variables: {}, }); admissionSelection = insert(admissionSelection, path, admission.declaration.fragment!.name); if ( !admissionImports.some( (entry) => entry.packageRevisionId === branch.packageRevision && entry.fragmentId === declaration.id, ) ) admissionImports.push({ packageRevisionId: branch.packageRevision, fragmentId: declaration.id, variables: {}, mode: "reference", }); admissionSelection = insert(admissionSelection, path, declaration.fragment!.name); } } } const token = suffix(identity(pkg.revisionId, `${preparation.module}/${step.name}`)); const name = `ReactInput${token}`, id = `react:input:${preparation.module}:${step.name}`; const base: QueryDeclaration = { ...original.declaration, id, displayName: name, document: `generated/react/${name}.graphql`, operation: name, fragments: [], importedFragments: imports, watch: true, budgets: { ...original.declaration.budgets, depth: Math.max(original.declaration.budgets.depth, 32), rows: Math.max(original.declaration.budgets.rows, 5000), }, }; delete base.fragment; function documentFor(selection: SelectionSetNode, fragmentName: string): DocumentNode { const substituted = visit(selection, { Variable(node) { const constant = constants[node.name.value]; if (!constant || !("literal" in constant)) throw Error(key + "." + step.name + ": unbound query variable " + node.name.value); return typeof constant.literal === "boolean" ? { kind: Kind.BOOLEAN, value: constant.literal } : { kind: Kind.STRING, value: constant.literal }; }, }); if (original.declaration.root.kind === "object") { const root = substituted.selections.find((entry) => entry.kind === Kind.FIELD && entry.name.value === "root"); if (!root || root.kind !== Kind.FIELD || !root.selectionSet) throw Error(key + ": expected object-rooted query"); const contract = workspace.interfaceImports.find( (entry) => entry.revisionId === original.declaration.root.interface, )!; return { kind: Kind.DOCUMENT, definitions: [ { kind: Kind.FRAGMENT_DEFINITION, name: { kind: Kind.NAME, value: fragmentName }, typeCondition: { kind: Kind.NAMED_TYPE, name: { kind: Kind.NAME, value: contract.displayName } }, selectionSet: root.selectionSet, }, ], }; } return { kind: Kind.DOCUMENT, definitions: [ { kind: Kind.OPERATION_DEFINITION, operation: "query" as import("graphql").OperationTypeNode, name: { kind: Kind.NAME, value: name }, selectionSet: substituted, }, ], }; } if (original.declaration.root.kind === "object") base.fragment = { name }; const sourceId = id + ":fragment-inputs"; const admissionName = name + "Admission"; const admissionBase: QueryDeclaration = { ...base, views: source.declaration.views, allowances: source.declaration.allowances, importedFragments: admissionImports, ...(base.fragment ? { fragment: { name: admissionName } } : {}), }; const admissionDocument = documentFor(admissionSelection, admissionName); await install( pkg, { ...admissionBase, id: sourceId, displayName: name + "$inputs", placement: { kind: "replica" }, watch: false, ...(base.fragment ? { fragment: { name: admissionName, admission: true } } : {}), }, print(admissionDocument), ); const admissionFragment = await install( pkg, { ...admissionBase, id: id + ":admission-projection", displayName: name + "$admission", placement: { kind: "local", sourceQueryId: sourceId }, }, print(admissionDocument), ); const local = await install( pkg, { ...base, placement: { kind: "local", sourceQueryId: sourceId } }, print(documentFor(selection, name)), ); admissionFragments.set(identity(pkg.revisionId, local.declaration.id), admissionFragment); step.query = local.declaration.id; step.arguments = { record: {} }; if ( local.declaration.fragment && step.root && "from" in step.root && step.root.from === "input" && step.root.path.join(".") === "subject" ) exported.push({ step: step.name, query: local }); if ( local.declaration.fragment && step.root && "from" in step.root && step.root.from === "input" && step.root.path.join(".") === "navigation" ) { const iface = local.declaration.root.interface; stateInputs.set(iface, [...(stateInputs.get(iface) ?? []), { pkg, query: local }]); } } const available = new Map( workspace.packageImports.flatMap((pkg) => (pkg.checkedQueries ?? []).map((query) => [identity(pkg.revisionId, query.declaration.id), query] as const), ), ); for (const query of pkg.checkedQueries ?? []) available.set(query.declaration.id, query); const checked = compileQueryComposition( { id: composition.id, input: composition.input, steps, maxQueries: composition.maxQueries }, available, ); pkg.queryCompositions = pkg.queryCompositions!.map((entry) => (entry.id === composition.id ? checked : entry)); eager.set(key, exported); done.add(key); } for (const key of modules.keys()) await expandModule(key); // Retention queries are ordinary checked queries too. They carry the declared // state projections, including any subject fragments reached through state. // This is specific to this frontend; no navigation operator enters Camino. for (const pkg of workspace.packageImports) { for (const original of [...(pkg.checkedQueries ?? [])]) { if (!original.declaration.id.startsWith("presentation:navigation:")) continue; // Identity must remain independent of every occurrence's data projection. // Full retained acquisition may include those requirements together. if (/:identity-(inputs|view)$/.test(original.declaration.id)) continue; const inputs = (stateInputs.get(original.declaration.root.interface) ?? []).map((input) => ({ ...input, query: original.declaration.placement?.kind === "replica" ? admissionFragments.get(identity(input.pkg.revisionId, input.query.declaration.id))! : input.query, })); if (!inputs.length) continue; const document = parse(original.document); const collection = original.declaration.placement?.kind === "local" ? "availableObjects" : "objects"; const definitions = document.definitions.map((definition) => { if (definition.kind !== Kind.OPERATION_DEFINITION) return definition; return visit(definition, { Field(node) { if (node.name.value !== "root" || !node.selectionSet) return; const source = node.selectionSet.selections.find( (entry) => entry.kind === Kind.FIELD && entry.name.value === collection, ); if (!source || source.kind !== Kind.FIELD) throw Error("Missing navigation collection"); return { ...node, selectionSet: { ...node.selectionSet, selections: [ ...node.selectionSet.selections, ...inputs.map((input) => ({ ...source, alias: { kind: Kind.NAME as const, value: `prepared${suffix(identity(input.pkg.revisionId, input.query.declaration.id))}`, }, selectionSet: parse( `{ entries { node { ...${input.query.declaration.fragment!.name} } } }`, ).definitions.flatMap((entry) => entry.kind === Kind.OPERATION_DEFINITION ? [entry.selectionSet] : [], )[0]!, })), ], }, }; }, }); }); await install( pkg, { ...original.declaration, ...(original.declaration.placement?.kind === "local" ? { id: original.declaration.id.replace(/:view$/, ":prepared-view"), displayName: original.declaration.displayName + "Prepared", } : {}), importedFragments: inputs.map((input) => ({ packageRevisionId: input.pkg.revisionId, fragmentId: input.query.declaration.id, variables: {}, ...(original.declaration.placement?.kind === "local" ? { mode: "reference" as const } : {}), })), }, print({ ...document, definitions }), ); } } }