Files
js/test/zapgen.test.ts
zeekay 4b69488362 pipeline: real Target-based promise pipelining; fix false "mirrors Go" claim
pipeline.ts claimed to mirror "the canonical Go ZAP client's Pipeline()" —
a function that did not exist — and carried a TODO for the single-connection
variant. Now that Go has a real Target model (rpc.Session/rpc.Pipeliner),
make the TS runtime its true byte-for-byte peer and tell the truth.

New src/promise.ts (universal, no node:net — exported from the root):
  - Session: PromiseID allocator + origin/pipe Call builders (peer of
    rpc.Session; sequence starts at 1 to match Go).
  - Pipeliner: server-side promise table over a dispatch fn — resolve Target
    before dispatch, queue an unresolved dependent until it resolves, refuse
    (StatusBadRequest, no hang) a failed/finished target. finish() bounds it.

src/pipeline.ts is now ONLY the two-connection socket overlap on top of
promise.ts, with an honest doc: the MECHANISM is the Target field resolved
by a Pipeliner (the unit-tested in-process path); two connections exist only
because one ZAP connection is strictly FIFO. No false "mirrors Pipeline()",
no TODO.

zapgen (src/zapgen/emit.ts): the generated TS client now mirrors the Go one
exactly — a Session field, m() (originating) + mOn(on) (pipelined) through a
shared invoke<M>. Dropped the hardcoded `target: NO_TARGET` / `promiseID: 0`.

Tests: test/pipeline.test.ts drives the REAL Target field end-to-end
(resolution, queuing, 3-deep chain, failure propagation, finish, byte-level
wire encoding) — not a typeof check. zapgen.test asserts the canonical
pipelining client is emitted. 119 tests green; root stays browser-safe.
2026-06-21 01:50:02 -07:00

86 lines
3.2 KiB
TypeScript

// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { describe, it, expect } from "vitest";
import { parse } from "../src/zapgen/parser.js";
import { emitTS } from "../src/zapgen/emit.js";
const echoSrc = readFileSync(
fileURLToPath(new URL("./fixtures/echo.zap", import.meta.url)),
"utf8",
);
describe("zapgen interface grammar (FIX 4)", () => {
const file = parse("echo.zap", echoSrc);
it("parses structs and interfaces additively", () => {
expect(file.structs.map((s) => s.name)).toEqual(["EchoReq", "EchoResp"]);
expect(file.interfaces.map((i) => i.name)).toEqual(["Echo"]);
});
it("auto-assigns method ordinals 1.. in declaration order", () => {
const echo = file.interfaces[0];
expect(echo.methods).toHaveLength(1);
expect(echo.methods[0].name).toBe("echo");
expect(echo.methods[0].ordinal).toBe(1);
expect(echo.methods[0].request?.structName).toBe("EchoReq");
expect(echo.methods[0].response?.structName).toBe("EchoResp");
});
it("emits a typed Client and abstract Server for each interface", () => {
const [name, out] = emitTS(file);
expect(name).toBe("echo_zap.ts");
expect(out).toContain("class EchoClient");
expect(out).toContain("abstract class EchoServer");
expect(out).toContain("EchoMethod");
// ordinal table value is the auto-assigned 1.
expect(out).toContain("echo: 1,");
});
it("emits the canonical Target-based pipelining client (Session + On form)", () => {
const [, out] = emitTS(file);
// The client owns a Session (allocates PromiseIDs) — no hardcoded promiseID.
expect(out).toContain("private readonly session = new Session();");
// The originating call goes through session.origin (Target = NO_TARGET) and
// the pipelined call through session.pipe (Target = on.id) — NOT a literal
// `target: NO_TARGET` hardcoded into every method.
expect(out).toContain("this.session.origin(p, EchoMethod.echo");
expect(out).toContain("async echoOn(on: PromiseHandle)");
expect(out).toContain("this.session.pipe(p, on, EchoMethod.echo");
expect(out).not.toContain("target: NO_TARGET");
});
it("still emits struct View + Builder (struct emission unchanged)", () => {
const [, out] = emitTS(file);
expect(out).toContain("export class EchoReq extends StructView");
expect(out).toContain("export function newEchoReq(");
expect(out).toContain("export class EchoResp extends StructView");
});
});
describe("zapgen interface ordinal stability", () => {
it("numbers multiple methods 1, 2, 3 by declaration order", () => {
const src = `package svc
struct A { X u32 @0 }
struct B { Y u32 @0 }
interface S {
first(a: A) returns (b: B)
second() returns ()
third(a: A)
}`;
const file = parse("svc.zap", src);
const s = file.interfaces[0];
expect(s.methods.map((m) => [m.name, m.ordinal])).toEqual([
["first", 1],
["second", 2],
["third", 3],
]);
const [, out] = emitTS(file);
expect(out).toContain("first: 1,");
expect(out).toContain("second: 2,");
expect(out).toContain("third: 3,");
});
});