mirror of
https://github.com/zap-proto/js.git
synced 2026-10-30 15:04:48 +00:00
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.
86 lines
3.2 KiB
TypeScript
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,");
|
|
});
|
|
});
|