import assert from "node:assert/strict"; import test from "node:test"; import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { execFile } from "node:child_process"; import { promisify } from "node:util"; import { createRequire } from "node:module"; import { compileCapabilityResourceSource, compileCapabilitySource } from "../src/capability-language/parser.js"; import { generateTypeScriptBindings, generatePackageDescriptor } from "../src/bindings/index.js"; import { genericImplementationType } from "../src/bindings/generics.js"; import { compileWorkspaceRevision, runtimeContracts } from "../src/capability-model/index.js"; import { generateAppliedClientContracts } from "../src/bindings/client.js"; const source = { repository: "https://example.test/generic.git", commit: "a".repeat(40) }; test("generic port aliases retain their defining scope, including shadowed alias names", () => { const iface = compileCapabilityResourceSource( 'type Box = list; interface Data id "data" revision "data@1" {value payload id "payload" : Box {get id "payload:get";}}', { source }, ); assert.ok(iface.ok, JSON.stringify(iface.diagnostics)); if (iface.resource.kind !== "interface") throw new Error("expected interface"); const pkg = compileCapabilityResourceSource( 'import interface Data; type Box = optional; package P id "p" revision "p@1" {operation fetch id "data@1" : unit -> list> mode call receiver any requires {interface data id "data" : Data>;};}', { source, environment: { interfaces: new Map([["Data", iface.resource.revision]]) } }, ); assert.ok(pkg.ok, JSON.stringify(pkg.diagnostics)); if (pkg.resource.kind !== "package") throw new Error("expected package"); const generated = generateTypeScriptBindings( { format: "quixos-bindings", version: 1, interfaces: [], interfaceTemplates: [iface.resource.revision], packages: [pkg.resource.revision], }, pkg.resource.revision.revisionId, ); assert.match(generated, /"payload.get":\(\)=>Promise>/); }); const definition = () => { const result = compileCapabilityResourceSource( `package Generic id "generic" revision "generic@1" { operation echo id "echo" : T -> T mode call receiver any; }`, { source }, ); assert.ok(result.ok, JSON.stringify(result.diagnostics)); if (result.resource.kind !== "package") throw new Error("expected package"); return result.resource.revision; }; test("generic package exports specialize per binding without changing immutable source identity", () => { const iface = compileCapabilityResourceSource( `interface Echo id "echo-interface" revision "echo-interface@1" { operation echo id "echo-member" : T -> T {call id "echo-call";} }`, { source }, ); assert.ok(iface.ok); if (iface.resource.kind !== "interface") throw new Error("expected interface"); const pkg = definition(); const result = compileCapabilitySource( `workspace W id "w" revision "w@1" commit "${source.commit}" { import interface Echo; import package Generic; atom Thing id "thing"; conform Thing as Echo id "string-echo" {bind echo.call to package Generic.echo;} conform Thing as Echo> id "list-echo" {bind echo.call to package Generic.echo>;} }`, "workspace.qx", { interfaces: new Map([["Echo", iface.resource.revision]]), packages: new Map([["Generic", pkg]]) }, ); assert.ok(result.ok, JSON.stringify(result.diagnostics)); assert.equal(pkg.exports.length, 0, "source definition was not mutated"); const closed = result.workspace.packageImports[0]; assert.equal(closed.revisionId, pkg.revisionId); assert.equal(closed.exports.length, 2); assert.notEqual(closed.exports[0].id, closed.exports[1].id); const oneApplication = structuredClone(result.workspace); oneApplication.packageImports[0].exports.splice(1); oneApplication.conformances.splice(1); assert.notDeepEqual( runtimeContracts(oneApplication), runtimeContracts(result.workspace), "New specializations invalidate the executable contract even with unchanged package source", ); const schema = { format: "quixos-bindings" as const, version: 1 as const, interfaces: result.workspace.interfaceImports, packages: [closed], }; const generated = generateTypeScriptBindings(schema, pkg.revisionId); assert.match(generated, /"echo":\s*\(/); for (const entry of closed.exports) { assert.ok(generated.includes(entry.id)); assert.ok(generatePackageDescriptor(schema, pkg.revisionId).includes(entry.id)); } const forged = structuredClone(result.workspace); forged.packageImports[0].exports[0].outputType = { kind: "scalar", name: "bool" }; const rejected = compileWorkspaceRevision(forged); assert.equal(rejected.ok, false); if (!rejected.ok) assert.ok(rejected.issues.some((issue) => issue.message.includes("Specialized export differs"))); }); test("generated universal implementations compile and cannot assume a concrete value type", async () => { const pkg = definition(); const signature = genericImplementationType(pkg.genericExports![0], [], () => { throw new Error("unexpected concrete type"); }); const directory = await fs.mkdtemp(path.join(os.tmpdir(), "qx-generic-ts-")); const require = createRequire(import.meta.url); const compiler = path.join(path.dirname(require.resolve("typescript/package.json")), "bin/tsc"); const run = promisify(execFile); const preamble = `type QxObjectRef={readonly identity:T}; type QxContextLifecycle={signal?:AbortSignal}; type Handler=${signature};\n`; try { const file = path.join(directory, "generic.ts"); await fs.writeFile(file, preamble + `const handler:Handler=({input})=>input;`); await run(process.execPath, [compiler, "--ignoreConfig", "--noEmit", "--strict", "--target", "es2023", file]); await fs.writeFile(file, preamble + `const handler:Handler=({input})=>"not universally T";`); await assert.rejects( run(process.execPath, [compiler, "--ignoreConfig", "--noEmit", "--strict", "--target", "es2023", file]), (error) => { assert.match(String((error as { stdout: string }).stdout), /not assignable/); return true; }, ); } finally { await fs.rm(directory, { recursive: true, force: true }); } }); test("generic Self follows the receiver parameter, never a preceding export", () => { const compile = (declaration: string) => compileCapabilityResourceSource(`package P id "p" revision "p@1" {${declaration}}`, { source }); const valid = compile('operation identity id "identity" : unit -> ref mode call receiver object T;'); assert.ok(valid.ok, JSON.stringify(valid.diagnostics)); if (valid.resource.kind !== "package") throw new Error("expected package"); const definition = valid.resource.revision.genericExports![0]; assert.deepEqual(definition.outputType, { kind: "object-ref", expectation: { kind: "parameter", parameterId: definition.parameters[0].id }, }); assert.equal(compile('function identity id "identity" : T -> ref;').ok, false); assert.equal( compile('operation events id "events" : unit -> watch-handle mode watch-start receiver any;').ok, false, ); }); test("typed presentation and factory consumers preserve distinct closed applications and return targets", async () => { const interfaces = new Map(); for (const text of [ 'interface Presentation id "presentation" revision "presentation@1" {operation props id "props" : unit -> Props {call id "props:get";}}', 'interface Factory id "factory" revision "factory@1" {operation create id "create" : unit -> ref {call id "create:call";}}', ]) { const result = compileCapabilityResourceSource(text, { source }); assert.ok(result.ok, JSON.stringify(result.diagnostics)); interfaces.set(result.resource.revision.displayName, result.resource.revision); } const pkg = compileCapabilityResourceSource( `import interface Presentation; import interface Factory; external atom Note id "note"; external atom Notebook id "notebook"; package Views id "views" revision "views@1" { operation note id "note-view" : unit -> unit mode call receiver any requires {interface props id "props" : Presentation;}>;}; operation notebook id "notebook-view" : unit -> unit mode call receiver any requires {interface props id "props" : Presentation;}; operation factory id "factory-view" : unit -> unit mode call receiver any requires {interface factory id "factory" : Factory;}; }`, { source, environment: { interfaces } }, ); assert.ok(pkg.ok, JSON.stringify(pkg.diagnostics)); const closed = pkg.resource.specializations!; const presentation = closed.find( (contract) => contract.application?.definitionId === "presentation@1" && JSON.stringify(contract).includes('"title"'), )!; const factory = closed.find((contract) => contract.application?.definitionId === "factory@1")!; const contracts = generateAppliedClientContracts(closed); const directory = await fs.mkdtemp(path.join(os.tmpdir(), "qx-presentation-types-")); const compiler = path.join( path.dirname(createRequire(import.meta.url).resolve("typescript/package.json")), "bin/tsc", ); const run = promisify(execFile); const usage = `type Props=CapabilityOutput<${JSON.stringify(presentation.revisionId)},"props:get">; type Created=CapabilityOutput<${JSON.stringify(factory.revisionId)},"create:call">; const render=({camino,render}:{camino:Props;render:{onSelect:(note:Created)=>void;compact:boolean}})=>{render.onSelect(camino.note);return camino.title;};\n`; try { const file = path.join(directory, "consumer.ts"); await fs.writeFile(file, contracts + usage); await run(process.execPath, [compiler, "--ignoreConfig", "--noEmit", "--strict", "--target", "es2023", file]); await fs.writeFile(file, contracts + usage + "const wrong=(props:Props)=>props.count;"); await assert.rejects( run(process.execPath, [compiler, "--ignoreConfig", "--noEmit", "--strict", "--target", "es2023", file]), (error) => { assert.match(String((error as { stdout: string }).stdout), /count.*does not exist/); return true; }, ); } finally { await fs.rm(directory, { recursive: true, force: true }); } });