import type { InterfaceRevision, ValueType } from "../capability-model/types.js"; /** Host clients have no package receiver, but must use the same checked * interface signatures and argument framing as generated package ports. */ export const generateClientContracts = ( interfaces: InterfaceRevision[], messages: Record, qualified = false, ) => { if (interfaces.some((entry) => entry.template)) throw new Error("Host contracts require closed interface applications, not generic definitions"); const type = (value: ValueType): string => { switch (value.kind) { case "builtin": return value.name === "unit" ? "undefined" : "string"; case "scalar": return { bool: "boolean", string: "string", bytes: "Uint8Array", int32: "number", uint32: "number", double: "number", int64: "bigint", uint64: "bigint", }[value.name]; case "object-ref": return qualified ? `CapabilityReference<${JSON.stringify(value.expectation.kind === "atom" ? `atom:${value.expectation.atomId}` : `interface:${value.expectation.interfaceRevisionId}`)}>` : `{readonly $quixosRef: string}`; case "optional": return `(${type(value.value)} | null)`; case "list": return `Array<${type(value.value)}>`; case "record": return `{${Object.entries(value.fields) .map(([name, field]) => `${JSON.stringify(name)}${field.kind === "optional" ? "?" : ""}: ${type(field)}`) .join("; ")}}`; case "message": { const binding = messages[value.descriptorId]; if (!binding) throw new Error(`Missing host message type ${value.descriptorId}`); return binding; } } }; const operations = interfaces.flatMap((iface) => iface.members.flatMap((member) => member.operations .filter((operation) => operation.mode === "call") .map((operation) => ({ ...operation, interfaceRevisionId: iface.revisionId })), ), ); if (qualified) { const contracts = interfaces.map((iface) => { const members = iface.members.flatMap((member) => member.operations .filter((op) => op.mode === "call") .map((op) => ` ${JSON.stringify(op.id)}: {input: ${type(op.inputType)}; output: ${type(op.outputType)}};`), ); return `${JSON.stringify(iface.revisionId)}: {\n${members.join("\n")}\n}`; }); if (new Set(interfaces.map((iface) => iface.revisionId)).size !== interfaces.length) throw new Error("Duplicate closed interface identity"); return ( `// Generated closed capability contracts. Dispatch by interface AND operation.\n` + `declare const referenceType: unique symbol;\nexport type CapabilityReference = {readonly $quixosRef: string; readonly [referenceType]: T};\n` + `export type CapabilityContracts = {${contracts.join(";\n")}};\n` + `export type CapabilityInput = CapabilityContracts[I][O] extends {input: infer T} ? T : never;\n` + `export type CapabilityOutput = CapabilityContracts[I][O] extends {output: infer T} ? T : never;\n` + `export const capabilityApplications = ${JSON.stringify(Object.fromEntries(interfaces.map((iface) => [iface.revisionId, { definitionId: iface.application?.definitionId ?? iface.revisionId, arguments: iface.application?.arguments ?? [], ...(iface.application?.self ? { self: iface.application.self } : {}) }])))} as const;\n` ); } if (new Set(operations.map((entry) => entry.id)).size !== operations.length) throw new Error( "Host operation IDs are ambiguous across interfaces; select a closed interface application explicitly", ); return ( `// Generated from checked QX interfaces. Regenerate with scripts/generate-platform-contracts.mjs.\n` + `export type PlatformInputs = {\n${operations.map((operation) => ` ${JSON.stringify(operation.id)}: ${type(operation.inputType)};`).join("\n")}\n};\n` + `export const platformOperations = ${JSON.stringify( Object.fromEntries( operations.map((operation) => [ operation.id, { interfaceRevisionId: operation.interfaceRevisionId, input: operation.inputType.kind === "builtin" && operation.inputType.name === "unit" ? "unit" : ["record", "message"].includes(operation.inputType.kind) ? "fields" : "value", }, ]), ), null, 2, )} as const;\n` ); }; /** New consumers use exact closed interface identities; operation IDs alone are not unique. */ export const generateAppliedClientContracts = ( interfaces: InterfaceRevision[], messages: Record = {}, ) => generateClientContracts(interfaces, messages, true);