import assert from "node:assert/strict"; import { test } from "node:test"; import { generatePortableTypeScriptBindings } from "../src/bindings/portable.js"; import { compileExecutionPlan } from "../src/execution-world/plan.js"; import { loadReaderWorkspace } from "./reader-fixture.js"; test("service bindings depend on their consumed contract, not workspace or source provenance", async () => { const schema = compileExecutionPlan(await loadReaderWorkspace()).definition.bindingSchema; const pkg = schema.packages[0]!; const before = generatePortableTypeScriptBindings(schema, pkg.revisionId, { target: "server" }); const other = structuredClone(pkg); other.revisionId = "unrelated@1" as typeof other.revisionId; other.exports = []; schema.packages.push(other); pkg.source.commit = "b".repeat(40); pkg.revisionId = "same-service-new-source@2" as typeof pkg.revisionId; assert.equal(generatePortableTypeScriptBindings(schema, pkg.revisionId, { target: "server" }), before); assert.doesNotMatch(before, /export const (bindingDigest|packageRevision)/); pkg.exports[0]!.inputType = { kind: "scalar", name: "bool" }; assert.notEqual(generatePortableTypeScriptBindings(schema, pkg.revisionId, { target: "server" }), before); }); test("reader portable bindings have selected facets, creation inputs and stable keyed calls", async () => { const schema = compileExecutionPlan(await loadReaderWorkspace()).definition.bindingSchema; const source = generatePortableTypeScriptBindings(schema, "package:reader-runtime@1", { target: "browser" }); assert.match(source, /readonly "sourceRevision.get": \(\) => Promise/); assert.match( source, /readonly "highlights.connect": \(input: ManagedReference<"atom", "atom:reader:highlight">, options: MutationKey\)/, ); assert.match(source, /readonly "createDocument"/); assert.match(source, /readonly "create":/); assert.match(source, /readonly "comments": ReadonlyArray/); assert.doesNotMatch(source, /"highlights.resolve"|context\.conform|objectId|"live"/); assert.equal(source, generatePortableTypeScriptBindings(schema, "package:reader-runtime@1", { target: "browser" })); const server = generatePortableTypeScriptBindings(schema, "package:reader-runtime@1", { target: "server" }); assert.equal(source.replace('target = "browser"', 'target = "server"'), server); }); test("generation rejects remote dependencies, wrong effects and unsupported shapes explicitly", async () => { const schema = compileExecutionPlan(await loadReaderWorkspace()).definition.bindingSchema; const generate = () => generatePortableTypeScriptBindings(schema, "package:reader-runtime@1", { target: "browser" }); const entry = schema.packages[0]!.exports.find((e) => e.displayName === "addHighlight")!; entry.execution = { kind: "snapshot", targets: ["browser"] }; assert.throws(generate, /Snapshot context cannot expose mutation ports/); entry.execution = { kind: "transaction", targets: ["browser"] }; const iface = schema.interfaces.find((i) => i.revisionId === "interface:reader:document-data@1")!; const getter = iface.members[0]!.operations[0]!; getter.execution = { kind: "remote" }; assert.throws(generate, /not an object call available in portable target browser/); getter.execution = { kind: "snapshot", targets: ["browser", "server"] }; entry.dependencyPorts[0]!.requirement = { kind: "reflection", selectors: ["active"] }; assert.throws(generate, /Only checked presentation metadata is portable/); }); test("closed generic portable handlers have distinct concrete codecs and no universal casts", async () => { const { portableGenericPlan } = await import("./portable-generic-fixture.js"); const plan = portableGenericPlan(); const schema = plan.definition.bindingSchema; const pkg = schema.packages[0]!; assert.equal(pkg.exports.length, 2); const generated = generatePortableTypeScriptBindings(schema, pkg.revisionId, { target: "browser" }); assert.match(generated, /readonly input: string/); assert.match(generated, /readonly input: ReadonlyArray/); for (const entry of pkg.exports) { assert.ok(entry.application); assert.ok(generated.includes(JSON.stringify(entry.id))); assert.ok(generated.includes(JSON.stringify(entry.displayName))); } assert.notEqual(pkg.exports[0]!.displayName, pkg.exports[1]!.displayName); assert.equal( generated.replace('target = "browser"', 'target = "server"'), generatePortableTypeScriptBindings(schema, pkg.revisionId, { target: "server" }), ); }); test("a portable subset of a mixed interface excludes remote dependencies", async () => { const schema = compileExecutionPlan(await loadReaderWorkspace()).definition.bindingSchema; const entry = schema.packages.flatMap((p) => p.exports).find((e) => e.displayName === "addHighlight")!; const iface = schema.interfaces.find((i) => i.revisionId === "interface:reader:document-data@1")!; const selected = entry.dependencyPorts.find((p) => p.requirement.kind === "interface")!.requirement; assert.equal(selected.kind, "interface"); if (selected.kind !== "interface") throw Error("interface required"); const operation = structuredClone(iface.members[0]!.operations[0]!); operation.id = "document:remote:get" as typeof operation.id; operation.execution = { kind: "remote" }; iface.members.push({ ...structuredClone(iface.members[0]!), id: "document:remote" as (typeof iface.members)[0]["id"], displayName: "remote", operations: [operation], }); const generate = () => generatePortableTypeScriptBindings(schema, "package:reader-runtime@1", { target: "browser" }); assert.doesNotMatch(generate(), /readonly "remote.get"/); selected.operations!.push(operation.id); assert.throws(generate, /select a portable subset/); }); test("server projection descriptors are distinct from canonical inputs and have result decoders", async () => { const { generatePortableClientBindings } = await import("../src/bindings/portable-client.js"); const plan = compileExecutionPlan(await loadReaderWorkspace()); const source = generatePortableClientBindings(plan, { target: "browser" }); const projections = source.slice( source.indexOf("export const serverProjections"), source.indexOf("export const results"), ); assert.match(projections, /ConfirmedColor/); assert.match(projections, /satisfies ServerProjection/); assert.match(source.slice(source.indexOf("export const results")), /ConfirmedColor/); await assert.rejects( loadReaderWorkspace((name, text) => name === "queries/confirmed-color.graphql" ? text.replace("color", "color @live") : text, ), /@live is retired/, ); }); test("static interface ports keep unit arguments explicit for generic factory composition", async () => { const schema = compileExecutionPlan(await loadReaderWorkspace()).definition.bindingSchema; const entry = schema.packages.flatMap((p) => p.exports).find((e) => e.displayName === "addHighlight")!; const port = entry.dependencyPorts.find((p) => p.requirement.kind === "interface")!; if (port.requirement.kind !== "interface") throw Error("interface required"); const requirement = port.requirement; const iface = schema.interfaces.find((i) => i.revisionId === requirement.interfaceRevisionId)!; const op = iface.members.flatMap((m) => m.operations).find((o) => o.id === requirement.operations![0])!; op.scope = "class"; op.inputType = { kind: "builtin", name: "unit" }; port.requirement.operations = [op.id]; delete port.requirement.receiver; const generated = generatePortableTypeScriptBindings(schema, "package:reader-runtime@1", { target: "browser" }); assert.match(generated, /"explicitInput": true/); assert.match(generated, /input: null/); });