Files
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

139 lines
4.0 KiB
TypeScript

// Code generated by zapgen --target=ts; DO NOT EDIT.
// source: echo.zap
import {
Builder,
Message,
StructView,
buildRequest,
parseRequest,
buildResponse,
Status,
Session,
type Call,
type Response,
type PromiseHandle,
} from "@zap-proto/zap";
const echoReqOff = {
Msg: 0,
} as const;
const echoReqSize = 8;
/** Zero-copy view into a ZAP-encoded EchoReq message. */
export class EchoReq extends StructView {
/** Parse `b` and return a typed view. Throws on a wire-level check failure (magic, version, size). */
static wrap(b: Uint8Array): EchoReq {
const root = Message.parse(b).root();
return new EchoReq(root.data, root.offset);
}
get msg(): string { return this.text(echoReqOff.Msg); }
}
/** Field values for newEchoReq. */
export interface EchoReqInput {
msg: string;
}
/** Build a ZAP-encoded EchoReq message from `in` and return the bytes. */
export function newEchoReq(in_: EchoReqInput): Uint8Array {
const b = new Builder(256);
const ob = b.startObject(echoReqSize);
ob.setText(echoReqOff.Msg, in_.msg);
ob.finishAsRoot();
return b.finish();
}
const echoRespOff = {
Msg: 0,
} as const;
const echoRespSize = 8;
/** Zero-copy view into a ZAP-encoded EchoResp message. */
export class EchoResp extends StructView {
/** Parse `b` and return a typed view. Throws on a wire-level check failure (magic, version, size). */
static wrap(b: Uint8Array): EchoResp {
const root = Message.parse(b).root();
return new EchoResp(root.data, root.offset);
}
get msg(): string { return this.text(echoRespOff.Msg); }
}
/** Field values for newEchoResp. */
export interface EchoRespInput {
msg: string;
}
/** Build a ZAP-encoded EchoResp message from `in` and return the bytes. */
export function newEchoResp(in_: EchoRespInput): Uint8Array {
const b = new Builder(256);
const ob = b.startObject(echoRespSize);
ob.setText(echoRespOff.Msg, in_.msg);
ob.finishAsRoot();
return b.finish();
}
/** Method ordinals for the Echo service (stable wire ids). */
export const EchoMethod = {
echo: 1,
} as const;
/** Typed RPC client for the Echo service over a ZAP call channel. */
export interface EchoChannel {
/** Ship one Call envelope and await its correlated Response. */
call(envelope: Uint8Array): Promise<Response>;
}
/**
* Each call takes a fresh PromiseID from `session`; the pipelined "On" form
* of a method sets Target to a prior call's Promise so the server chains them.
*/
export class EchoClient {
private readonly session = new Session();
constructor(private readonly channel: EchoChannel, private readonly cap: Uint8Array = new Uint8Array(0)) {}
async echo(req: Uint8Array): Promise<[PromiseHandle, Uint8Array]> {
const p = this.session.next();
return this.invokeEcho(this.session.origin(p, EchoMethod.echo, this.cap, req), p);
}
/** Issue echo pipelined on `on`'s answer (the server substitutes it for the payload). */
async echoOn(on: PromiseHandle): Promise<[PromiseHandle, Uint8Array]> {
const p = this.session.next();
return this.invokeEcho(this.session.pipe(p, on, EchoMethod.echo, this.cap), p);
}
private async invokeEcho(call: Call, p: PromiseHandle): Promise<[PromiseHandle, Uint8Array]> {
const resp = await this.channel.call(buildRequest(call));
if (resp.status !== Status.OK) {
throw new Error(`Echo.echo: status ${resp.status}`);
}
return [p, resp.body];
}
}
/** Abstract RPC server for the Echo service. Subclass and implement each method. */
export abstract class EchoServer {
abstract echo(req: Uint8Array): Promise<Uint8Array>;
/** Decode a Call envelope, route by method ordinal, and build the Response envelope. */
async dispatch(envelope: Uint8Array): Promise<Uint8Array> {
const call: Call = parseRequest(envelope);
switch (call.method) {
case EchoMethod.echo: {
const body = await this.echo(call.payload);
return buildResponse(Status.OK, call.promiseID, body);
}
default:
return buildResponse(Status.NotFound, call.promiseID, new Uint8Array(0));
}
}
}