import { createHash } from "node:crypto"; import type { InterfaceRevision, PackageRevision, ValueType } from "../capability-model/types.js"; import { defaultQueryBudgets, type QueryDeclaration } from "../query/types.js"; /** React contract tooling generates ordinary queries before normal checking. * Neither the query language nor its execution planner knows presentation IDs. */ export function presentationQueries(pkg: PackageRevision, interfaces: readonly InterfaceRevision[]) { const documents = new Map(); const generated: QueryDeclaration[] = []; const contracts = new Map(interfaces.map((contract) => [contract.revisionId, contract])); const navigation = new Set(); for (const module of pkg.modules ?? []) { const contract = contracts.get(module.interfaceRevisionId); const member = contract?.members.find((member) => member.id === module.memberId); if (member?.kind !== "module" || member.contract !== "org.quixos.react.component/2") continue; const argument = contract?.application?.arguments[1]; if (argument?.kind !== "object" || argument.target.kind !== "interface") throw Error("Stateful React presentations require a closed navigation interface"); navigation.add(argument.target.interfaceRevisionId); } const valueSelection = (type: ValueType): string => { if (type.kind === "optional" || type.kind === "list") return valueSelection(type.value); return type.kind === "record" ? `{${Object.entries(type.fields) .map(([name, type]) => `${name} ${valueSelection(type)}`) .join(" ")}}` : ""; }; for (const id of [...navigation].sort()) { const contract = contracts.get(id as InterfaceRevision["revisionId"]); if (!contract || contract.template) throw Error(`Missing closed presentation navigation ${id}`); const fields = contract.members.flatMap((member) => { if ((member.kind !== "value" && member.kind !== "relationship") || !member.queryRead) return []; if (member.kind === "value") return [`${member.displayName} ${valueSelection(member.valueType)}`]; if (member.target.kind !== "interface") throw Error(`Navigation relationship ${member.displayName} must declare an interface view`); return [ member.cardinality === "many" || member.cardinality === "many-unique" ? `${member.displayName}(all:512) {entries {node {_camino {ref}}}}` : `${member.displayName} {_camino {ref}}`, ]; }); const stem = `PresentationNavigation${createHash("sha256").update(id).digest("hex").slice(0, 12)}`; const inputs = `presentation:navigation:${id}:inputs`; for (const local of [false, true]) { const name = `${stem}${local ? "View" : "Inputs"}`; const document = `generated/${name}.graphql`; documents.set( document, `query ${name}($presentationMount: QxRef_PresentationMount!) { root { ${local ? "availableObjects" : "objects"}(all:512, where:{_camino:{as:{PresentationState:{presentationMount:{eq:$presentationMount}}}}}) { entries { node { ${fields.join("\n")} ... on PresentationState { _presentationOccurrence: occurrence _presentationSource: source {_camino {ref}} _presentationParent: parent {_camino {ref}} _presentationMount: presentationMount {_camino {ref}} } } } } } }`, ); generated.push({ id: local ? `presentation:navigation:${id}:view` : inputs, displayName: name, root: { kind: "workspace-interface", interface: contract.revisionId }, document, operation: name, fragments: [], views: [], allowances: [], budgets: { ...defaultQueryBudgets, rows: 5000, candidates: 5000, resultBytes: 8388608 }, watch: local, placement: local ? { kind: "local", sourceQueryId: inputs } : { kind: "replica" }, }); } } for (const query of generated) if (pkg.queries?.some((existing) => existing.id === query.id || existing.displayName === query.displayName)) throw Error(`Presentation query conflicts with an authored query: ${query.id}`); pkg.queries = [...(pkg.queries ?? []), ...generated]; return documents; }