mirror of
https://github.com/zap-proto/web.git
synced 2026-10-30 15:03:09 +00:00
httpServe(httpServer, opts) mounts POST /<service>/<method> on the SAME
http.Server, dispatching each request through the identical CallHandler that
serve() uses over WebSocket — one service implementation, reached two ways. The
paths match the OpenAPI 3.1 doc that `zapgen --emit=openapi` emits.
The JSON<->ZAP codec is schema-specific, so each method is supplied as an
HttpRoute {path, method ordinal, decode(json)->payloadBytes,
encode(bodyBytes)->json}; the generated zapgen bindings provide the struct
builders/views to fill those in. The runtime stays schema-agnostic. Auth reuses
the MintCap slot (null mint -> HTTP 401); ZAP error statuses map to HTTP 4xx/5xx.
httpServe owns `request` dispatch: it captures the app's existing listeners,
detaches them, and delegates unmatched paths back to them — guaranteeing exactly
one response writer per request even though route handling is async (mintCap,
body read). Node `http` only, no framework. Exported from @zap-proto/web/server.
Round-trip test: generate ts+openapi from a fixture .zap, validate every
components.schema under ajv 2020-12 (the dialect OpenAPI 3.1 mandates), spin
httpServe in-process, POST to the declared path, and assert the response
validates against the doc's 200 schema. Depends on @zap-proto/zap ^1.3.0.
129 lines
3.5 KiB
TypeScript
129 lines
3.5 KiB
TypeScript
// Code generated by zapgen --target=ts; DO NOT EDIT.
|
|
// source: openapi-echo.zap
|
|
|
|
import {
|
|
Builder,
|
|
Message,
|
|
StructView,
|
|
buildRequest,
|
|
parseRequest,
|
|
buildResponse,
|
|
NO_TARGET,
|
|
Status,
|
|
type Call,
|
|
type Response,
|
|
} from "@zap-proto/zap";
|
|
|
|
const echoInOff = {
|
|
Msg: 0,
|
|
} as const;
|
|
const echoInSize = 8;
|
|
|
|
/** Zero-copy view into a ZAP-encoded EchoIn message. */
|
|
export class EchoIn extends StructView {
|
|
/** Parse `b` and return a typed view. Throws on a wire-level check failure (magic, version, size). */
|
|
static wrap(b: Uint8Array): EchoIn {
|
|
const root = Message.parse(b).root();
|
|
return new EchoIn(root.data, root.offset);
|
|
}
|
|
|
|
get msg(): string { return this.text(echoInOff.Msg); }
|
|
}
|
|
|
|
/** Field values for newEchoIn. */
|
|
export interface EchoInInput {
|
|
msg: string;
|
|
}
|
|
|
|
/** Build a ZAP-encoded EchoIn message from `in` and return the bytes. */
|
|
export function newEchoIn(in_: EchoInInput): Uint8Array {
|
|
const b = new Builder(256);
|
|
const ob = b.startObject(echoInSize);
|
|
ob.setText(echoInOff.Msg, in_.msg);
|
|
ob.finishAsRoot();
|
|
return b.finish();
|
|
}
|
|
|
|
|
|
const echoOutOff = {
|
|
Msg: 0,
|
|
} as const;
|
|
const echoOutSize = 8;
|
|
|
|
/** Zero-copy view into a ZAP-encoded EchoOut message. */
|
|
export class EchoOut extends StructView {
|
|
/** Parse `b` and return a typed view. Throws on a wire-level check failure (magic, version, size). */
|
|
static wrap(b: Uint8Array): EchoOut {
|
|
const root = Message.parse(b).root();
|
|
return new EchoOut(root.data, root.offset);
|
|
}
|
|
|
|
get msg(): string { return this.text(echoOutOff.Msg); }
|
|
}
|
|
|
|
/** Field values for newEchoOut. */
|
|
export interface EchoOutInput {
|
|
msg: string;
|
|
}
|
|
|
|
/** Build a ZAP-encoded EchoOut message from `in` and return the bytes. */
|
|
export function newEchoOut(in_: EchoOutInput): Uint8Array {
|
|
const b = new Builder(256);
|
|
const ob = b.startObject(echoOutSize);
|
|
ob.setText(echoOutOff.Msg, in_.msg);
|
|
ob.finishAsRoot();
|
|
return b.finish();
|
|
}
|
|
|
|
|
|
/** Method ordinals for the EchoService service (stable wire ids). */
|
|
export const EchoServiceMethod = {
|
|
echo: 1,
|
|
} as const;
|
|
|
|
/** Typed RPC client for the EchoService service over a ZAP call channel. */
|
|
export interface EchoServiceChannel {
|
|
/** Ship one Call envelope and await its correlated Response. */
|
|
call(envelope: Uint8Array): Promise<Response>;
|
|
}
|
|
|
|
export class EchoServiceClient {
|
|
constructor(private readonly channel: EchoServiceChannel, private readonly cap: Uint8Array = new Uint8Array(0)) {}
|
|
|
|
async echo(req: Uint8Array): Promise<Uint8Array> {
|
|
const envelope = buildRequest({
|
|
method: EchoServiceMethod.echo,
|
|
promiseID: 0,
|
|
target: NO_TARGET,
|
|
cap: this.cap,
|
|
payload: req,
|
|
});
|
|
const resp = await this.channel.call(envelope);
|
|
if (resp.status !== Status.OK) {
|
|
throw new Error(`EchoService.echo: status ${resp.status}`);
|
|
}
|
|
return resp.body;
|
|
}
|
|
|
|
}
|
|
|
|
/** Abstract RPC server for the EchoService service. Subclass and implement each method. */
|
|
export abstract class EchoServiceServer {
|
|
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 EchoServiceMethod.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));
|
|
}
|
|
}
|
|
}
|
|
|
|
|