import type { CheckedQuery, QueryTreeNode } from "../query/types.js"; import type { ValueType } from "../capability-model/types.js"; import { treeProjectionQuery } from "../query/tree.js"; import { tsType } from "../bindings/portable.js"; export function queryTreeBlueprint(packageId: string, query: CheckedQuery) { const tree = query.tree!; const projection = (id: string) => JSON.stringify([packageId, treeProjectionQuery(query.declaration.id, id, "local")]); const admission = (id: string) => JSON.stringify([packageId, treeProjectionQuery(query.declaration.id, id, "replica")]); return { root: tree.root, maxOccurrences: tree.maxOccurrences, nodes: tree.nodes.map((node) => ({ ...node, projection: projection(node.projection), admission: admission(node.projection), bindings: node.bindings.map((binding) => ({ ...binding, projection: projection(binding.projection), admission: admission(binding.projection), })), })), }; } /** Emit the shape of owned handles separately from graph result codecs. */ export function queryTreeType(query: CheckedQuery): string { const tree = query.tree!; const nodes = new Map(tree.nodes.map((node) => [node.id, node])); const projections = new Map(tree.projections.map((projection) => [projection.id, projection])); const rootOutput = (id: string) => { const shape = projections.get(id)!.output; if (shape.kind !== "record" || !shape.fields.root) throw Error("Unchecked Query Tree projection"); return shape.fields.root; }; const shape = (id: string): string => { const node = nodes.get(id)!; return `QueryTreeShape<${tsType(rootOutput(node.projection))}, {${node.boundaries .map( (boundary) => `readonly ${JSON.stringify(boundary.name)}${boundary.conditions.length ? "?" : ""}: ${shape(boundary.node)}`, ) .join(";")}}, {readonly root: {${node.bindings .map( (binding) => `readonly ${JSON.stringify(binding.name)}: ${bindingType(rootOutput(binding.projection), binding.targets, [binding.name])}`, ) .join(";")}}}>`; }; const bindingType = ( type: ValueType, targets: QueryTreeNode["bindings"][number]["targets"], prefix: string[], ): string => { const at = (type: ValueType, targets: QueryTreeNode["bindings"][number]["targets"]): string => { if (type.kind === "optional") return `(${at(type.value, targets)} | null)`; if (type.kind === "list") return `ReadonlyArray<${at( type.value, targets.map((target) => ({ ...target, path: target.path.slice(1) })), )}>`; const target = targets.find((target) => !target.path.length); if (target) { if (type.kind !== "record" || type.fields._camino?.kind !== "record" || !type.fields._camino.fields.ref) throw Error("Unchecked binding identity"); return `QueryTreeOccurrence<${tsType(type.fields._camino.fields.ref)}, ${shape(target.node)}>`; } if (type.kind !== "record") return tsType(type); return `{${Object.entries(type.fields) .map( ([key, value]) => `readonly ${JSON.stringify(key)}: ${at( value, targets .filter((target) => target.path[0] === key) .map((target) => ({ ...target, path: target.path.slice(1) })), )}`, ) .join(";")}}`; }; let selected = type; for (const key of prefix) { if (selected.kind !== "record" || !selected.fields[key]) throw Error("Unchecked binding field"); selected = selected.fields[key]; } return at( selected, targets.map((target) => ({ ...target, path: target.path.slice(prefix.length) })), ); }; return shape(tree.root); }