import type { InterfaceRevision, InterfaceOperation, ExecutionContract, PackageRevision, } from "../capability-model/types.js"; import { canonicalJson } from "../capability-model/evolution.js"; import { portableValueSchema, tsType } from "../bindings/portable.js"; import { type ExecutionPlan, resolvePlannedOperation, hexDigest, ExecutionPlanError } from "./plan.js"; import type { BindingSchema } from "../bindings/index.js"; import type { PackageBuildContract } from "./build-contract.js"; import { preparationArtifact, type ReactPreparationArtifact } from "../react-build/declarations.js"; import { moduleTypeContext } from "./module-contracts.js"; import { queryTreeBlueprint, queryTreeType } from "./query-trees.js"; /** Public shape only: no slot inventory, implementation binding, or workspace identity. */ export interface PublicOperationContract { interface: string; operation: string; scope: "object" | "class"; execution: ExecutionContract; input: unknown; output: unknown; fieldPolicy?: "register" | "crdt"; } export function publicOperationContract( iface: InterfaceRevision, operation: InterfaceOperation, ): PublicOperationContract { if (iface.template || operation.mode !== "call" || !iface.members.some((m) => m.operations.includes(operation))) throw new ExecutionPlanError( "UNSUPPORTED_CLIENT_CONTRACT", iface.revisionId, "Expected a closed interface call operation", ); const execution = operation.execution ?? { kind: "remote" }; const member = iface.members.find((m) => m.operations.includes(operation))!; const fieldPolicy = member.kind === "value" && operation.scope !== "class" && execution.kind === "transaction" ? operation.displayName === "edit" ? "crdt" : member.nativeField && operation.displayName === "set" ? "register" : undefined : undefined; return { ...(fieldPolicy ? { fieldPolicy } : {}), interface: iface.revisionId, operation: operation.id, scope: operation.scope ?? "object", execution: execution.kind === "remote" ? execution : { ...execution, targets: [...execution.targets].sort() }, input: portableValueSchema(operation.inputType), output: portableValueSchema(operation.outputType), }; } /** Resolve a public contract to internal dispatch. This does not authorize the receiver. */ export function linkInterfaceOperation(plan: ExecutionPlan, atom: string, contract: PublicOperationContract) { if (hexDigest(plan.definition) !== plan.digest) throw new ExecutionPlanError("INVALID_PLAN", atom, "Invalid execution plan digest"); const iface = plan.definition.workspace.interfaceImports.find((i) => i.revisionId === contract.interface); const operation = iface?.members.flatMap((m) => m.operations).find((o) => o.id === contract.operation); if (!iface || !operation || canonicalJson(publicOperationContract(iface, operation)) !== canonicalJson(contract)) throw new ExecutionPlanError( "CLIENT_CONTRACT_MISMATCH", `${contract.interface}/${contract.operation}`, "Installed interface differs from the imported operation contract", ); return resolvePlannedOperation(plan, atom, contract.interface, contract.operation); } export interface PublicQueryContract { package: string; query: string; definitionDigest: string; root: { kind: "object" | "workspace-interface"; interface: string }; placement: "server" | "replica" | "local"; sourceQuery?: string; input: unknown; output: unknown; tree?: ReturnType; } export function publicQueryContract(pkg: PackageRevision, queryId: string): PublicQueryContract { const query = pkg.checkedQueries?.find((q) => q.declaration.id === queryId); if (!query) throw new ExecutionPlanError("UNCHECKED_QUERY", `${pkg.revisionId}/${queryId}`, "Expected a checked package query"); const placement = query.declaration.placement; return { package: pkg.revisionId, query: queryId, definitionDigest: query.definitionDigest, root: query.declaration.root, placement: placement?.kind ?? "server", ...(placement?.kind === "local" ? { sourceQuery: query.treeProjection?.tree ?? placement.sourceQueryId } : {}), input: portableValueSchema(query.variables), output: portableValueSchema(query.output), ...(query.tree ? { tree: queryTreeBlueprint(pkg.revisionId, query) } : {}), }; } export function linkPackageQuery(plan: ExecutionPlan, contract: PublicQueryContract) { if (hexDigest(plan.definition) !== plan.digest) throw new ExecutionPlanError("INVALID_PLAN", contract.query, "Invalid execution plan digest"); const pkg = plan.definition.workspace.packageImports.find((p) => p.revisionId === contract.package); if (!pkg || canonicalJson(publicQueryContract(pkg, contract.query)) !== canonicalJson(contract)) throw new ExecutionPlanError( "CLIENT_QUERY_MISMATCH", `${contract.package}/${contract.query}`, "Installed query differs from the imported query contract", ); const query = plan.definition.queries.find( (q) => q.query.packageRevisionId === contract.package && q.query.checked.declaration.id === contract.query, ); if (!query) throw new ExecutionPlanError("UNINSTALLED_QUERY", contract.query, "No installed query binding"); return query; } /** Generate only the explicitly imported interfaces. No concrete atom inventory is needed. */ export function generateInterfaceClientBindings( imports: Readonly>, runtimeModule = "@quixos/camino-replica-core", moduleProps?: (interfaceId: string, memberId: string) => string, ): string { const entries = Object.entries(imports).map(([alias, iface]) => { if (iface.template) throw new ExecutionPlanError( "OPEN_CLIENT_CONTRACT", alias, "Specialize the interface before generating a client", ); const members = iface.members.map((member) => { if (member.kind === "module" && member.subject?.kind === "interface") { const subject = member.subject.interfaceRevisionId; return `${JSON.stringify(member.displayName)}: defineModuleMember<${JSON.stringify(subject)}, ${moduleProps?.(iface.revisionId, member.id) ?? "{}"}>(${JSON.stringify({ interface: iface.revisionId, member: member.id, subject, contract: member.contract, ...moduleTypeContext(iface) })})`; } const operations = member.operations .filter((op) => op.mode === "call") .map((op) => { const contract = publicOperationContract(iface, op); return contract.fieldPolicy && member.kind === "value" ? `${JSON.stringify(op.displayName)}: defineFieldOperation<${tsType(member.valueType)}, ${JSON.stringify(contract.fieldPolicy)}, ${JSON.stringify(contract.execution)}>(${JSON.stringify(contract)})` : `${JSON.stringify(op.displayName)}: defineInterfaceOperation<${tsType(op.inputType)}, ${tsType(op.outputType)}, ${JSON.stringify(contract.execution)}>(${JSON.stringify(contract)})`; }); return `${JSON.stringify(member.displayName)}: {${operations.join(",\n")}}`; }); return `${JSON.stringify(alias)}: {${members.join(",\n")}}`; }); return `// Generated from imported interface contracts. Regenerate; do not edit.\nimport { defineModuleMember, defineInterfaceOperation, defineFieldOperation, type PreparedChange, type ManagedReference } from ${JSON.stringify(runtimeModule)};\nexport const interfaces = {${entries.join(",\n")}} as const;\n`; } export function generatePackageQueryClientBindings( pkg: PackageRevision, runtimeModule = "@quixos/camino-replica-core", packages: readonly PackageRevision[] = [pkg], ): string { const internal = (pkg.buildArtifacts?.[preparationArtifact] as ReactPreparationArtifact | undefined)?.internalQueries ?? []; pkg = { ...pkg, queries: pkg.queries?.filter((query) => !internal.includes(query.id)), checkedQueries: pkg.checkedQueries?.filter((query) => !internal.includes(query.declaration.id)), }; const queries = (pkg.checkedQueries ?? []) .filter((query) => !query.declaration.fragment && !query.treeProjection) .map( (query) => `${JSON.stringify(query.declaration.displayName)}: ${query.tree ? `definePackageQueryTree<${tsType(query.variables)}, ${queryTreeType(query)}, ${JSON.stringify(query.declaration.root.interface)}>` : `definePackageQuery<${tsType(query.variables)}, ${tsType(query.output)}, ${JSON.stringify(publicQueryContract(pkg, query.declaration.id).placement)}>`}(${JSON.stringify(publicQueryContract(pkg, query.declaration.id))})`, ); const emitFragment = (provider: PackageRevision, id: string) => { const query = provider.checkedQueries?.find((query) => query.declaration.id === id); if ( !query?.declaration.fragment || query.declaration.fragment.admission || query.declaration.placement?.kind !== "local" || query.output.kind !== "record" || !query.output.fields.root ) throw new ExecutionPlanError("UNCHECKED_FRAGMENT", id, "Expected a checked local fragment export"); const output = query.output.fields.root; const descriptor = { package: provider.revisionId, fragment: id, digest: query.definitionDigest, subject: query.declaration.root.interface, input: portableValueSchema(query.variables), output: portableValueSchema(output), admission: publicQueryContract(provider, query.declaration.placement.sourceQueryId), view: publicQueryContract(provider, id), }; return `definePackageFragment<${tsType(query.variables)}, ${tsType(output)}, ${JSON.stringify(query.declaration.root.interface)}>(${JSON.stringify(descriptor)})`; }; const fragments = (pkg.checkedQueries ?? []) .filter((query) => query.declaration.fragment && !query.declaration.fragment.admission) .map((query) => `${JSON.stringify(query.declaration.displayName)}: ${emitFragment(pkg, query.declaration.id)}`); const imports = new Map>(); for (const query of pkg.queries ?? []) for (const imported of query.importedFragments ?? []) { if (imported.packageRevisionId === pkg.revisionId) continue; const provider = packages.find((provider) => provider.revisionId === imported.packageRevisionId); const name = provider?.queries?.find((query) => query.id === imported.fragmentId)?.displayName; if (!provider || !name) throw new ExecutionPlanError("UNCHECKED_FRAGMENT", imported.fragmentId, "Missing imported fragment provider"); const alias = imported.packageAlias ?? provider.displayName; if (pkg.queries?.some((query) => query.fragment && query.displayName === alias)) throw new ExecutionPlanError( "FRAGMENT_ALIAS_COLLISION", alias, "Imported fragment namespace conflicts with a local fragment export", ); const entries = imports.get(alias) ?? new Map(); entries.set(name, emitFragment(provider, imported.fragmentId)); imports.set(alias, entries); } for (const [alias, entries] of imports) fragments.push( `${JSON.stringify(alias)}: {${[...entries].map(([name, value]) => `${JSON.stringify(name)}: ${value}`).join(",")}}`, ); return `// Generated from checked package query contracts. Regenerate; do not edit.\nimport { definePackageQuery, definePackageFragment, ${queries.some((query) => query.includes("definePackageQueryTree<")) ? "definePackageQueryTree, type QueryTreeShape, type QueryTreeOccurrence, " : ""}type ManagedReference } from ${JSON.stringify(runtimeModule)};\nexport const queries = {${queries.join(",\n")}} as const;\nexport const fragments = {${fragments.join(",\n")}} as const;\n`; } /** Receipt-covered bindings expose the package's imports, component subjects and * declared queries, never the workspace's complete atom/interface inventory. */ export function generateComponentClientBindings(plan: ExecutionPlan, packageRevision: string): string { const shape = plan.definition.packages.find((pkg) => pkg.revision === packageRevision); if (!shape) throw new ExecutionPlanError("NO_COMPONENT_CONTRACT", packageRevision, "Unknown package"); return generatePackageComponentBindings(plan.definition.bindingSchema, shape); } export function generatePackageComponentBindings( schema: BindingSchema, shape: PackageBuildContract["definition"]["package"], ): string { const packageRevision = shape.revision; const pkg = schema.packages.find((pkg) => pkg.revisionId === packageRevision); const components = shape.components; const modules = shape.modules ?? []; if (!pkg || !components?.length) throw new ExecutionPlanError("NO_COMPONENT_CONTRACT", packageRevision, "Package declares no component entries"); const byId = new Map(schema.interfaces.map((iface) => [iface.revisionId as string, iface])); const find = (id: string) => { const contract = byId.get(id); if (!contract || contract.template) throw new ExecutionPlanError("OPEN_CLIENT_CONTRACT", id, "Component contracts must be closed"); return contract; }; const imports = Object.fromEntries( Object.entries(pkg.clientInterfaces ?? {}).map(([alias, id]) => [alias, find(id)]), ); const subjects = Object.fromEntries(components.map((component) => [component.displayName, find(component.subject)])); const props = (iface: string, member: string) => `ExtraModuleProps<${JSON.stringify(JSON.stringify([iface, member]))}>`; // Preserve closed type arguments for the artifact consumer. Interpretation of // a particular module contract belongs to that consumer, not this compiler. const moduleArguments = (iface: InterfaceRevision) => `[${(iface.application?.arguments ?? []) .map((argument) => { if (argument.kind === "value") return tsType(argument.type); const target = argument.target; const reference = target.kind === "atom" ? `ManagedReference<"atom", ${JSON.stringify(target.atomId)}>` : `ManagedReference<"interface", ${JSON.stringify(target.interfaceRevisionId)}>`; const fields = target.kind === "interface" ? find(target.interfaceRevisionId).members.flatMap((member) => { if ((member.kind !== "value" && member.kind !== "relationship") || !member.queryRead) return []; const getter = member.operations.find( (operation) => operation.displayName === (member.kind === "value" ? "get" : "resolve"), ); return getter ? [`readonly ${JSON.stringify(member.displayName)}: ${tsType(getter.outputType)}`] : []; }) : []; return `{readonly reference: ${reference}; readonly values: {${fields.join(";")}}}`; }) .join(",")}]`; const interfaces = generateInterfaceClientBindings(imports, "@quixos/camino-react", props); const subjectBindings = generateInterfaceClientBindings(subjects, "@quixos/camino-react", props) .split("\n") .filter((line) => !line.startsWith("import ")) .join("\n") .replace("export const interfaces =", "export const subjects ="); const queries = generatePackageQueryClientBindings(pkg, "@quixos/camino-react", schema.packages) .split("\n") .filter((line) => !line.startsWith("import ")) .join("\n"); return ( interfaces + '\nimport {createObjectRenderer,definePackageQuery,definePackageFragment,definePackageQueryTree,type QueryTreeShape,type QueryTreeOccurrence,type QueryTreeHandle,type ComponentType,type ComponentProps,type PresentationHandle} from "@quixos/camino-react";\n' + subjectBindings + "\n" + queries + `\nexport const providers = {${Object.entries(pkg.clientPackages ?? {}) .map(([alias, id]) => { const imported = schema.packages.find((entry) => entry.revisionId === id); if (!imported) throw new ExecutionPlanError("UNINSTALLED_PACKAGE", id, "Missing imported module provider"); return `${JSON.stringify(alias)}:{${(imported.modules ?? []).map((module) => `${JSON.stringify(module.displayName)}:${JSON.stringify({ packageRevision: id, id: module.id })}`).join(",")}}`; }) .join(",")}} as const;\n` + `\nexport const renderers = {${Object.entries(imports) .map(([name, iface]) => `${JSON.stringify(name)}:createObjectRenderer(${JSON.stringify(iface.revisionId)})`) .join(",\n")}} as const;\n` + "\nexport interface ModuleProps {}\n" + "export type ExtraModuleProps = K extends keyof ModuleProps ? ModuleProps[K] : {};\n" + "export interface Modules {\n" + components .map((component) => { const module = modules.find((module) => module.id === component.id)!; const key = JSON.stringify([module.interfaceRevisionId, module.memberId]); const artifact = pkg.buildArtifacts?.["org.quixos.react.preparation/1"] as | import("../react-build/declarations.js").ReactPreparationArtifact | undefined; const preparation = artifact?.modules.find((entry) => entry.module === module.id); const composition = pkg.queryCompositions?.find((entry) => entry.id === preparation?.composition); if (!preparation || !composition) throw new ExecutionPlanError("MISSING_PRESENTATION_INPUTS", module.id, "Declare inputs in quixos.react.json"); const children = preparation.children.map((child) => { let extra = "{}"; if (child.role) { const iface = find(child.role.interface); const member = iface.members.find( (entry) => entry.id === child.role!.member || entry.displayName === child.role!.member, )!; extra = props(iface.revisionId, member.id); } const handle = `PresentationHandle<${JSON.stringify(child.subject)}, ${extra}>`; return `readonly ${JSON.stringify(child.name)}: ${child.each ? `readonly ${handle}[]` : child.with || child.when ? `${handle} | null` : handle}`; }); const trees = (preparation.trees ?? []).map((tree) => { const query = pkg.checkedQueries?.find((query) => query.declaration.id === tree.query); if (!query?.tree) throw new ExecutionPlanError("MISSING_QUERY_TREE", tree.query, "Expected a checked Query Tree"); return `readonly ${JSON.stringify(tree.name)}: QueryTreeHandle<${queryTreeType(query)}>`; }); const input = `${tsType(preparation.input ?? composition.output)} & {readonly children: {${children.join(";")}}${trees.length ? `;readonly trees:{${trees.join(";")}}` : ""}}`; return `${JSON.stringify(component.displayName)}: ComponentType & ExtraModuleProps<${JSON.stringify(key)}>>;`; }) .join("\n") + "\n}\n" ); } export function componentAssetDeclarations(): string { return ( "// Generated component asset modules. Regenerate; do not edit.\n" + ["svg", "png", "jpg", "jpeg", "gif", "webp", "avif", "woff", "woff2", "ttf", "ico"] .map((extension) => `declare module "*.${extension}" { const value: string; export default value; }`) .join("\n") + '\ndeclare module "*.css";\n' ); }