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.
139 lines
4.0 KiB
TypeScript
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));
|
|
}
|
|
}
|
|
}
|
|
|
|
|