import assert from "node:assert/strict"; import test from "node:test"; import { referenceFromWire } from "../dist/references.js"; import { bindQxHandler, decodeQxValue, encodeQxValue, jsToProtoValue, liveValue, protoValueToJs } from "../dist/index.js"; const scalar = (name) => ({ kind: "scalar", name }); const unit = { kind: "builtin", name: "unit" }; test("typed sessions rebind ports and cancellation to each acquired invocation", async () => { const first = new AbortController(), second = new AbortController(); let closed = false; const context = (value, signal) => ({objectId: referenceFromWire("obj:owner"), inputProto: {}, signal, state: () => ({live: async () => liveValue(jsToProtoValue(value))})}); const raw = {...context(1n, first.signal), openSession: async () => ({id: "session:test", run: (work) => work(context(2n, second.signal)), close: async () => {closed = true;}})}; const handler = bindQxHandler({inputType: unit, outputType: unit, ports: {counter: {kind: "state", id: "counter", valueType: scalar("int64"), primitives: ["read"]}}}, async (bound) => { assert.equal(bound.signal, first.signal); const session = await bound.openSession(); await session.run(async (next) => { assert.equal(next.signal, second.signal); assert.equal(await next.ports.counter.get(), 2n); assert.equal(next.openSession, undefined); }); await session.close(); }, {}); await handler(raw); assert.equal(closed, true); }); test("binding codecs round trip nested bytes, 64-bit integers, nulls, and references", () => { const values = [[scalar("int64"), -(2n ** 63n)], [scalar("uint64"), 2n ** 64n - 1n], [scalar("bytes"), new Uint8Array([0, 255])], [{ kind: "list", value: { kind: "optional", value: scalar("int64") } }, [null, 2n ** 60n]], [{ kind: "object-ref", expectation: { kind: "atom", atomId: "thing" } }, referenceFromWire("obj:thing")]]; for (const [type, value] of values) assert.deepEqual(decodeQxValue(type, encodeQxValue(type, value, {}), {}), value); }); test("opaque references pass declared RPC boundaries but cannot enter ordinary data", () => { const reference = referenceFromWire("obj:thing"); const type = {kind: "object-ref", expectation: {kind: "atom", atomId: "thing"}}; const roundtrip = decodeQxValue(type, encodeQxValue(type, reference, {}), {}); assert.equal(reference.equals(roundtrip), true); assert.equal(reference.equals(referenceFromWire("obj:other")), false); assert.throws(() => JSON.stringify({nested: [reference]}), /cannot be serialized/); assert.throws(() => String(reference), /cannot be coerced/); assert.throws(() => encodeQxValue(type, "obj:thing", {}), /opaque/); assert.throws(() => encodeQxValue(scalar("string"), reference, {}), /Managed references/); const message = {kind: "message", descriptorId: "Payload"}; assert.throws(() => encodeQxValue(message, {nested: reference}, {Payload: {encode: jsToProtoValue}}), /Managed references/); assert.throws(() => encodeQxValue(message, {}, {Payload: {encode: () => jsToProtoValue(reference)}}), /Managed references/); assert.throws(() => decodeQxValue(message, jsToProtoValue(reference), {Payload: {decode: () => ({})}}), /Managed references/); }); test("typed state and interface ports preserve declared values and exact operation IDs", async () => { const spec = { inputType: scalar("int64"), outputType: scalar("bytes"), ports: { data: { kind: "state", id: "state-id", valueType: scalar("int64"), primitives: ["read", "write"] }, reader: { kind: "interface", id: "interface-id", operations: { "payload.get": { id: "get-id", inputType: unit, outputType: scalar("bytes") } } }, } }; let written; const handler = bindQxHandler(spec, async ({ input, ports }) => { assert.equal(input, 2n ** 60n); assert.equal(await ports.data.get(), 9n); await ports.data.set(input); return ports.reader["payload.get"](); }, {}); const result = await handler({ inputProto: { value: jsToProtoValue(2n ** 60n) }, objectId: "obj", state: (id) => { assert.equal(id, "state-id"); return { live: async () => liveValue(jsToProtoValue(9n)), set: async (value) => { written = value; }, }; }, interface: (id) => { assert.equal(id, "interface-id"); return { live: async (operation, input) => { assert.equal(operation, "get-id"); assert.deepEqual(input, {}); return liveValue(jsToProtoValue(new Uint8Array([7]))); } }; }, }); assert.equal(written.$quixosValue.kind.value, String(2n ** 60n)); assert.deepEqual(result.$quixosValue.kind.value, new Uint8Array([7])); }); test("external message bindings and derived event types are used at the boundary", async () => { const message = { kind: "message", descriptorId: "Payload" }; const messages = { Payload: { encode: jsToProtoValue, decode: protoValueToJs } }; const handler = bindQxHandler({ inputType: message, outputType: { kind: "builtin", name: "watch-handle" }, eventType: message, ports: {} }, { kind: "derived", get: ({ input }) => ({ value: input.title }) }, messages); assert.equal(handler.kind, "derived"); const result = await handler.get({ objectId: "obj", inputProto: { title: jsToProtoValue("hello") } }); assert.deepEqual(protoValueToJs(result.$quixosValue), { value: "hello" }); assert.throws(() => encodeQxValue(message, {}, {}), /Missing message binding/); });