import type { GenericPackageExport, GenericDependencyPort } from "../capability-model/generic-packages.js"; import type { InterfaceRevision, ValueType } from "../capability-model/types.js"; import type { ValueTypeExpression, ObjectTypeExpression, TypeArgumentExpression, TypeParameter, ValueAliasDefinition, } from "../capability-model/generics.js"; /** Universal source contracts. Only closed exports get executable codecs. */ export const genericImplementationType = ( definition: GenericPackageExport, interfaces: InterfaceRevision[], concrete: (type: ValueType) => string, ): string => { const names = new Map(definition.parameters.map((parameter, index) => [parameter.id, `T${index}`])); type Scope = { arguments: Map; aliases: ValueAliasDefinition[] }; const rootScope = (): Scope => ({ arguments: new Map(), aliases: definition.aliases }); const object = (entries: [string, string][]) => `{${entries.map(([key, value]) => `${JSON.stringify(key)}:${value}`).join(";")}}`; const target = (type: ObjectTypeExpression, scope: Scope): string => { if (type.kind === "parameter") { const bound = scope.arguments.get(type.parameterId); if (bound) return bound; const name = names.get(type.parameterId); if (!name) throw new Error(`Unbound object parameter ${type.parameterId}`); return name; } if (type.kind === "atom") return JSON.stringify(`atom:${type.atomId}`); if (type.kind === "interface") return JSON.stringify(`interface:${type.interfaceRevisionId}`); if (type.kind === "application") return `QxApplied<${JSON.stringify(type.application.definitionId)}, [${type.application.arguments.map((arg) => argument(arg, scope)).join(",")}]>`; throw new Error("Generic package Self must be expressed as an explicit object parameter"); }; const argument = (arg: TypeArgumentExpression, scope: Scope): string => arg.kind === "value" ? value(arg.type, scope) : target(arg.target, scope); const bind = (parameters: TypeParameter[], args: TypeArgumentExpression[], scope: Scope): Scope => { if (parameters.length !== args.length) throw new Error("Wrong generic arity during code generation"); const result = new Map(scope.arguments); // Render arguments in their original lexical scope before introducing binders. const rendered = args.map((arg) => argument(arg, scope)); parameters.forEach((parameter, index) => result.set(parameter.id, rendered[index])); return { ...scope, arguments: result }; }; const value = (type: ValueTypeExpression, scope: Scope, depth = 0): string => { if (depth > 128) throw new Error("Generic type expansion exceeds depth limit"); switch (type.kind) { case "parameter": { const bound = scope.arguments.get(type.parameterId); if (bound) return bound; const name = names.get(type.parameterId); if (!name) throw new Error(`Unbound value parameter ${type.parameterId}`); return name; } case "object-ref": return `QxObjectRef<${target(type.expectation, scope)}>`; case "record": return object(Object.entries(type.fields).map(([name, field]) => [name, value(field, scope, depth + 1)])); case "optional": return `(${value(type.value, scope, depth + 1)} | null)`; case "list": return `Array<${value(type.value, scope, depth + 1)}>`; case "alias": { const alias = scope.aliases.find((entry) => entry.id === type.definitionId); if (!alias) throw new Error(`Missing alias ${type.definitionId}`); return value(alias.body, bind(alias.parameters, type.arguments, scope), depth + 1); } default: return concrete(type); } }; const params = (type: ValueTypeExpression, scope: Scope) => type.kind === "builtin" && type.name === "unit" ? "" : `input:${value(type, scope)}`; const port = (entry: GenericDependencyPort): string => { const requirement = entry.requirement, scope = rootScope(); switch (requirement.kind) { case "state": return object([ ...requirement.primitives.map((primitive): [string, string] => { if (primitive === "read") return ["get", `()=>Promise<${value(requirement.valueType, scope)}>`]; if (primitive === "write") return ["set", `(value:${value(requirement.valueType, scope)})=>Promise`]; throw new Error(`Unsupported generic state primitive ${primitive}`); }), ...(requirement.primitives.includes("read") ? [["live", "()=>Promise"] as [string, string]] : []), ]); case "edge": { const ref = `QxObjectRef<${target(requirement.target, scope)}>`; const methods = requirement.primitives.map((primitive): [string, string] => { if (primitive === "resolve") return ["resolve", `()=>Promise>`]; if (primitive === "connect" || primitive === "disconnect") return [primitive, `(target:${ref})=>Promise`]; throw new Error(`Unsupported generic edge primitive ${primitive}`); }); if (requirement.primitives.includes("resolve")) methods.push(["collection", `()=>Promise>`]); if ( ["resolve", "connect", "disconnect"].every((primitive) => requirement.primitives.includes(primitive as "resolve"), ) ) methods.push([ "replace", `(entries:RelationshipEntry<${ref}>[],expectedRevision:bigint)=>Promise>`, ]); return object(methods); } case "constructor": return object([ [ "construct", `(${params(requirement.inputType, scope)})=>Promise>`, ], ]); case "interface": { const contract = interfaces.find((entry) => entry.revisionId === requirement.application.definitionId); if (!contract) throw new Error(`Missing generic port contract ${requirement.application.definitionId}`); const local = { ...bind(contract.template?.parameters ?? [], requirement.application.arguments, scope), aliases: contract.template?.aliases ?? [], }; const operations = (contract.template?.members ?? contract.members).flatMap((member) => member.operations .filter((op) => op.mode === "call" && op.scope !== "class") .map((op) => ({ ...op, name: `${member.displayName}.${op.displayName}` })), ); return object([ ["objectId", `QxObjectRef<${target({ kind: "application", application: requirement.application }, scope)}>`], ["live", object(operations.map((op) => [op.name, `(${params(op.inputType, local)})=>Promise`]))], ...operations.map( (op) => [op.name, `(${params(op.inputType, local)})=>Promise<${value(op.outputType, local)}>`] as [ string, string, ], ), ]); } } }; const declarations = definition.parameters .map((parameter) => `${names.get(parameter.id)}${parameter.kind === "object" ? " extends string" : ""}`) .join(","); const receiver = definition.receiverRequirement; const context = object([ ...(definition.kind === "function" ? [] : [ [ "objectId", `QxObjectRef<${receiver.kind === "target" ? target(receiver.target, rootScope()) : "string"}>`, ] as [string, string], ]), ["input", value(definition.inputType, rootScope())], ["ports", object(definition.dependencyPorts.map((entry) => [entry.displayName, port(entry)]))], ]); const contextWithLifecycle = definition.kind === "function" ? `${context} & {signal?: AbortSignal}` : `${context} & QxContextLifecycle<${context} & {signal?: AbortSignal}>`; const result = value(definition.eventType ?? definition.outputType, rootScope()); const handler = `<${declarations}>(context:${contextWithLifecycle})=>${result}|Promise<${result}>`; const derived = `{kind:"derived";get:${handler}}`; return definition.eventType ? derived : `(${handler})${definition.kind === "operation" && definition.mode === "call" ? ` | ${derived}` : ""}`; };