import type { BindingSchema } from "./index.js"; import type { ValueType, ObjectExpectation, PackageExport, DependencyPort, ExecutionContract, } from "../capability-model/types.js"; import { hexDigest } from "../execution-world/plan.js"; export class PortableBindingError extends Error { constructor( readonly path: string, message: string, ) { super(`${path}: ${message}`); this.name = "PortableBindingError"; } } const q = JSON.stringify; const unit: ValueType = { kind: "builtin", name: "unit" }; const string: ValueType = { kind: "scalar", name: "string" }; const bool: ValueType = { kind: "scalar", name: "bool" }; const isUnit = (t: ValueType) => t.kind === "builtin" && t.name === "unit"; const expectation = (e: ObjectExpectation) => ({ kind: e.kind, id: e.kind === "atom" ? e.atomId : e.interfaceRevisionId, }); /** The protocol IR and replica wire schema intentionally share structure, not reference identity. */ export function portableValueSchema(type: ValueType): unknown { switch (type.kind) { case "change": return { kind: "record", fields: { codec: { kind: "scalar", name: "string" }, profile: { kind: "scalar", name: "string" }, generation: { kind: "scalar", name: "string" }, changes: { kind: "list", value: { kind: "scalar", name: "bytes" } }, }, }; case "scalar": return type; case "builtin": if (type.name === "unit") return type; break; case "object-ref": return { kind: type.kind, expectation: expectation(type.expectation) }; case "optional": case "list": return { kind: type.kind, value: portableValueSchema(type.value) }; case "record": return { kind: type.kind, fields: Object.fromEntries( Object.entries(type.fields).map(([key, value]) => [key, portableValueSchema(value)]), ), }; } throw new PortableBindingError( type.kind, "Unsupported portable value; use a supported record/reference contract (native edits and generic adapters have separate tasks)", ); } export function tsType(type: ValueType): string { portableValueSchema(type); switch (type.kind) { case "change": return `PreparedChange<${tsType(type.value)}>`; case "scalar": return type.name === "bool" ? "boolean" : type.name === "string" ? "string" : type.name === "bytes" ? "Uint8Array" : ["int64", "uint64"].includes(type.name) ? "bigint" : "number"; case "builtin": return "null"; case "object-ref": { const e = expectation(type.expectation); return `ManagedReference<${q(e.kind)}, ${q(e.id)}>`; } case "optional": return `(${tsType(type.value)} | null)`; case "list": return `ReadonlyArray<${tsType(type.value)}>`; case "record": return `{ ${Object.entries(type.fields) .map(([key, value]) => `readonly ${q(key)}: ${tsType(value)}`) .join("; ")} }`; default: throw new PortableBindingError(type.kind, "Unsupported type"); } } interface Method { name: string; portId: string; portName: string; action: { kind: string; primitive?: string; operationId?: string }; input: ValueType; output: ValueType; keyed: boolean; } function methods(schema: BindingSchema, entry: PackageExport, port: DependencyPort, target: string): Method[] { const req = port.requirement; const path = `${entry.displayName}.ports.${port.displayName}`; const method = ( name: string, action: Method["action"], input: ValueType, output: ValueType, keyed = false, ): Method => ({ name, portId: port.id, portName: port.displayName, action, input, output, keyed }); const writable = () => { if (entry.execution?.kind !== "transaction") throw new PortableBindingError(path, "Snapshot context cannot expose mutation ports"); }; switch (req.kind) { case "creation": writable(); return [ method( "create", { kind: "creation" }, req.inputType, { kind: "object-ref", expectation: { kind: "atom", atomId: req.atomId } }, true, ), ]; case "state": return req.primitives.map((primitive) => { if (primitive === "read") return method("get", { kind: "state", primitive }, unit, req.valueType); if (primitive === "write") { writable(); return method("set", { kind: "state", primitive }, req.valueType, unit); } if (primitive === "edit") { writable(); return method("edit", { kind: "state", primitive }, { kind: "change", value: req.valueType }, unit); } throw new PortableBindingError(path, `State ${primitive} is not supported by this portable adapter yet`); }); case "edge": { const ref: ValueType = { kind: "object-ref", expectation: req.target }; const item: ValueType = { kind: "record", fields: { entry: string, target: ref } }; return req.primitives.map((primitive) => { if (primitive === "resolve") return method("resolve", { kind: "edge", primitive }, unit, { kind: "list", value: item }); writable(); if (primitive === "connect") return method("connect", { kind: "edge", primitive }, ref, string, true); if (primitive === "disconnect") return method("disconnect", { kind: "edge", primitive }, string, bool); throw new PortableBindingError(path, `Edge ${primitive} is not supported by this portable adapter yet`); }); } case "interface": { const iface = schema.interfaces.find((i) => i.revisionId === req.interfaceRevisionId); if (!iface) throw new PortableBindingError(path, `Missing interface ${req.interfaceRevisionId}`); const operations = iface.members.flatMap((member) => member.operations.map((op) => ({ member, op }))); if (req.operations?.some((id) => !operations.some(({ op }) => op.id === id))) throw new PortableBindingError(path, "Unknown selected interface operation"); return operations .filter(({ op }) => !req.operations || req.operations.includes(op.id)) .map(({ member, op }) => { const execution: ExecutionContract = op.execution ?? { kind: "remote" }; if ( execution.kind === "remote" || !execution.targets.includes(target) || op.mode !== "call" || op.scope === "class" ) throw new PortableBindingError( `${path}.${member.displayName}.${op.displayName}`, `Selected operation is not an object call available in portable target ${target}; select a portable subset or move this handler to remote execution`, ); if (execution.kind === "transaction") writable(); return method( `${member.displayName}.${op.displayName}`, { kind: "interface", operationId: op.id }, op.inputType, op.outputType, execution.kind === "transaction", ); }); } default: throw new PortableBindingError(path, `Unsupported ${req.kind} port in portable bindings`); } } export interface PortableBindingOptions { target: string; runtimeModule?: string; } /** Generate one target's handlers. The same source signatures are used in browser/server builds. */ export function generatePortableTypeScriptBindings( schema: BindingSchema, revision: string, options: PortableBindingOptions, ): string { const pkg = schema.packages.find((p) => p.revisionId === revision); if (!pkg) throw new PortableBindingError(revision, "Missing package"); const entries = pkg.exports.filter( (e) => e.execution && e.execution.kind !== "remote" && e.execution.targets.includes(options.target), ); const names = new Set(); const contexts: string[] = [], implementations: string[] = [], adapters: string[] = [], descriptors: unknown[] = []; for (const entry of entries) { // The checked binding schema already contains concrete generic applications. // Each has its own identity, value codecs and specialized dependency ports. if (entry.kind === "constructor") throw new PortableBindingError(entry.id, "Legacy constructors are not supported; declare an ordinary factory"); if (names.has(entry.displayName)) throw new PortableBindingError(entry.id, "Duplicate handler name"); names.add(entry.displayName); const portNames = new Set(); const all: Method[] = [], facets: string[] = []; for (const port of entry.dependencyPorts) { if (portNames.has(port.displayName)) throw new PortableBindingError(entry.id, `Duplicate port name ${port.displayName}`); portNames.add(port.displayName); const selected = methods(schema, entry, port, options.target); if (new Set(selected.map((m) => m.name)).size !== selected.length) throw new PortableBindingError(port.id, "Duplicate method name"); all.push(...selected); facets.push( `readonly ${q(port.displayName)}: { ${selected.map((m) => `readonly ${q(m.name)}: (${[...(isUnit(m.input) ? [] : [`input: ${tsType(m.input)}`]), ...(m.keyed ? ["options: MutationKey"] : [])].join(", ")}) => Promise<${tsType(m.output)}>`).join("; ")} }`, ); } const receiver = entry.kind === "function" ? null : entry.receiverRequirement.kind === "exact-atom" ? { kind: "atom", id: entry.receiverRequirement.atomId } : "object"; const receiverType = receiver === null ? "null" : receiver === "object" ? "ExactReference" : `ManagedReference<"atom", ${q(receiver.id)}>`; contexts.push( `readonly ${q(entry.displayName)}: { readonly effect: ${q(entry.execution!.kind)}; readonly input: ${tsType(entry.inputType)}; readonly receiver: ${receiverType}; readonly ports: { ${facets.join("; ")} } }`, ); const output = tsType(entry.outputType); implementations.push( `readonly ${q(entry.displayName)}: (context: Contexts[${q(entry.displayName)}]) => ${output} | Promise<${output}>`, ); adapters.push( `${q(entry.id)}: context => installed[${q(entry.displayName)}](context as Contexts[${q(entry.displayName)}])`, ); descriptors.push({ id: entry.id, name: entry.displayName, effect: entry.execution!.kind, receiver, input: portableValueSchema(entry.inputType), output: portableValueSchema(entry.outputType), methods: all.map((m) => ({ ...m, input: portableValueSchema(m.input), output: portableValueSchema(m.output) })), }); } return `// Generated portable bindings. Regenerate from the checked capability schema; do not edit.\nimport { definePortableRegistry, type PortableExport, type PreparedChange, type ManagedReference, type ExactReference, type MutationKey } from ${q(options.runtimeModule ?? "@quixos/camino-replica-core")};\nexport const bindingDigest = ${q(hexDigest(schema))};\nexport const packageRevision = ${q(revision)};\nexport const target = ${q(options.target)};\nexport type Contexts = { ${contexts.join(";\n")} };\nexport type PortableImplementation = { ${implementations.join(";\n")} };\nexport const declarations = ${JSON.stringify(descriptors, null, 2)} as const satisfies readonly PortableExport[];\nexport function createRegistry(handlers: PortableImplementation) { const installed = { ...handlers }; return definePortableRegistry(declarations, { ${adapters.join(",\n")} }); }\n`; }