@zap-proto/web 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -127,15 +127,32 @@ zapgen --target=ts -single hello.zap # → hello_zap.ts (views + builders)
127
127
  Method ordinals (the interface method `@n`) are the integers you pass to
128
128
  `bootstrap.call(method, …)` and match in your `rootCap` dispatch.
129
129
 
130
+ ## Entry points
131
+
132
+ The root entry (`@zap-proto/web`) is **browser-safe** — it imports no Node
133
+ built-ins and exposes only the client surface (`connect`, `browserWsTransport`,
134
+ `Conn`, errors). The Node server (`serve`, `nodeWsTransport`, the `MintCap`
135
+ auth slot) reaches `node:module` (`createRequire`) / `node:http` / `node:net`
136
+ and lives behind `@zap-proto/web/server`, so it never enters a browser bundle.
137
+
138
+ ```ts
139
+ // Browser — the client connection.
140
+ import { connect } from "@zap-proto/web/client";
141
+
142
+ // Node — attach the RPC endpoint to an http.Server.
143
+ import { serve } from "@zap-proto/web/server";
144
+ ```
145
+
130
146
  ## API
131
147
 
132
148
  | Export | Entry | Purpose |
133
149
  | --- | --- | --- |
134
- | `serve(httpServer, opts)` | `@zap-proto/web/server` | Attach a ZAP RPC endpoint to a Node `http.Server`. |
135
- | `connect(url, opts?)` | `@zap-proto/web/client` | Open a ZAP RPC WebSocket; returns `{ bootstrap, close }`. |
136
- | `nodeWsTransport(ws)` / `browserWsTransport(ws)` | `@zap-proto/web/transport` | Wrap a `ws`/native WebSocket as a `WsTransport`. |
137
- | `MintCap<Ctx>` | `@zap-proto/web/auth` | The bearer→ctx slot. |
138
- | `WebRpcError` | `@zap-proto/web` | Transport-level RPC failure (rejected upgrade, mid-call close, non-OK status). |
150
+ | `connect(url, opts?)` | `@zap-proto/web/client` (or root) | Open a ZAP RPC WebSocket; returns `{ bootstrap, close }`. |
151
+ | `browserWsTransport(ws)` | `@zap-proto/web/transport` (or root) | Wrap a native WebSocket as a `WsTransport`. |
152
+ | `Conn`, `WebRpcError` | `@zap-proto/web` (root, browser-safe) | Duplex RPC connection; transport-level RPC failure. |
153
+ | `serve(httpServer, opts)` | `@zap-proto/web/server` | **Node only.** Attach a ZAP RPC endpoint to a Node `http.Server`. |
154
+ | `nodeWsTransport(ws)` | `@zap-proto/web/server` (or `/transport`) | **Node only.** Wrap a `ws` WebSocket as a `WsTransport`. |
155
+ | `MintCap<Ctx>` | `@zap-proto/web/server` (or `/auth`) | **Node only.** The bearer→ctx slot. |
139
156
 
140
157
  ## License
141
158
 
package/dist/http.d.ts ADDED
@@ -0,0 +1,75 @@
1
+ /**
2
+ * http.ts — httpServe(httpServer, opts): mount a JSON-over-HTTP face on the
3
+ * SAME ZAP service that serve() exposes over WebSocket.
4
+ *
5
+ * Motivation: external consumers (and the OpenAPI surface emitted by
6
+ * `zapgen --emit=openapi`) speak POST /<service>/<method> with a JSON body.
7
+ * httpServe terminates that HTTP shape and dispatches each request through the
8
+ * identical {@link CallHandler} the WebSocket transport uses — so there is ONE
9
+ * service implementation, reached two ways.
10
+ *
11
+ * The JSON⇄ZAP codec is schema-specific and therefore supplied by the caller as
12
+ * a {@link HttpRoute} per method (the generated zapgen bindings know each
13
+ * struct's shape; the runtime stays schema-agnostic — orthogonal separation).
14
+ * Each route declares:
15
+ * - path: POST path, matching the OpenAPI doc (`/document-service/...`).
16
+ * - method: the .zap method ordinal (the same ordinal the WS client sends).
17
+ * - decode: JSON request body → ZAP payload bytes.
18
+ * - encode: ZAP response body bytes → JSON value (omit for void methods).
19
+ *
20
+ * Auth runs per-request via the same {@link MintCap} slot serve() uses: a null
21
+ * mint yields HTTP 401; a non-null mint becomes the `ctx` passed to rootCap,
22
+ * whose {@link CallHandler} dispatches the decoded Call.
23
+ *
24
+ * Node `http` only — no framework. The HTTP server is supplied by the caller
25
+ * (the same http.Server that serve() attaches its WebSocket upgrade to), so one
26
+ * port serves WS + HTTP.
27
+ */
28
+ import type { Server as HttpServer } from "node:http";
29
+ import type { MintCap } from "./auth.js";
30
+ import type { RootCap } from "./server.js";
31
+ /**
32
+ * HttpRoute binds one POST path to one ZAP method, with the JSON⇄ZAP codec for
33
+ * that method. `decode` turns the parsed JSON request body into the method's
34
+ * ZAP payload bytes; `encode` turns the ZAP response body bytes into the JSON
35
+ * value to serialize. A void method omits `encode` (returns HTTP 204).
36
+ */
37
+ export interface HttpRoute {
38
+ /** POST path, e.g. "/document-service/create-document". */
39
+ path: string;
40
+ /** The .zap method ordinal this path dispatches. */
41
+ method: number;
42
+ /** Parse a JSON request body into ZAP payload bytes. */
43
+ decode: (json: unknown) => Uint8Array;
44
+ /** Encode ZAP response body bytes into a JSON value. Omit for void methods. */
45
+ encode?: (body: Uint8Array) => unknown;
46
+ }
47
+ /** Options for {@link httpServe}. */
48
+ export interface HttpServeOptions<Ctx> {
49
+ /** The method routes (one per OpenAPI operation). */
50
+ routes: HttpRoute[];
51
+ /** Bearer→ctx boundary; return null to reject with HTTP 401. */
52
+ mintCap: MintCap<Ctx>;
53
+ /** Produce the dispatch handler for a minted ctx (same shape as serve()). */
54
+ rootCap: RootCap<Ctx>;
55
+ /**
56
+ * Optional path prefix mounted before each route path, e.g. "/v1" makes the
57
+ * route "/echo/echo" serve at "/v1/echo/echo". Default "" (no prefix).
58
+ */
59
+ prefix?: string;
60
+ /** Optional sink for handler errors. */
61
+ onError?: (err: unknown) => void;
62
+ }
63
+ /** A live HTTP face attached to an http.Server. */
64
+ export interface HttpServeHandle {
65
+ /** Detach the request listener. */
66
+ close(): void;
67
+ }
68
+ /**
69
+ * Attach a JSON-over-HTTP face to `httpServer`. Returns a handle whose close()
70
+ * detaches the listener. Non-matching requests are passed through untouched so
71
+ * httpServe can coexist with the app's own request handler and with serve()'s
72
+ * WebSocket upgrade on the same server.
73
+ */
74
+ export declare function httpServe<Ctx>(httpServer: HttpServer, opts: HttpServeOptions<Ctx>): HttpServeHandle;
75
+ //# sourceMappingURL=http.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EAGV,MAAM,IAAI,UAAU,EACrB,MAAM,WAAW,CAAC;AAGnB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAK3C;;;;;GAKG;AACH,MAAM,WAAW,SAAS;IACxB,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAC;IACb,oDAAoD;IACpD,MAAM,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,MAAM,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,UAAU,CAAC;IACtC,+EAA+E;IAC/E,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,OAAO,CAAC;CACxC;AAED,qCAAqC;AACrC,MAAM,WAAW,gBAAgB,CAAC,GAAG;IACnC,qDAAqD;IACrD,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,gEAAgE;IAChE,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;IACtB,6EAA6E;IAC7E,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;IACtB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wCAAwC;IACxC,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;CAClC;AAED,mDAAmD;AACnD,MAAM,WAAW,eAAe;IAC9B,mCAAmC;IACnC,KAAK,IAAI,IAAI,CAAC;CACf;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAC3B,UAAU,EAAE,UAAU,EACtB,IAAI,EAAE,gBAAgB,CAAC,GAAG,CAAC,GAC1B,eAAe,CA8DjB"}
package/dist/http.js ADDED
@@ -0,0 +1,216 @@
1
+ // Copyright (C) 2025, Lux Industries Inc. All rights reserved.
2
+ // See the file LICENSE for licensing terms.
3
+ import { NO_TARGET, Status } from "@zap-proto/zap";
4
+ const EMPTY = new Uint8Array(0);
5
+ const MAX_BODY_BYTES = 8 * 1024 * 1024; // 8 MiB request-body ceiling.
6
+ /**
7
+ * Attach a JSON-over-HTTP face to `httpServer`. Returns a handle whose close()
8
+ * detaches the listener. Non-matching requests are passed through untouched so
9
+ * httpServe can coexist with the app's own request handler and with serve()'s
10
+ * WebSocket upgrade on the same server.
11
+ */
12
+ export function httpServe(httpServer, opts) {
13
+ const onError = opts.onError ?? (() => { });
14
+ const prefix = normalizePrefix(opts.prefix ?? "");
15
+ // path → route, built once. Promise ordinal collisions are the caller's
16
+ // concern (each method has a distinct path by construction).
17
+ const table = new Map();
18
+ for (const r of opts.routes)
19
+ table.set(prefix + r.path, r);
20
+ const appListeners = httpServer.listeners("request");
21
+ httpServer.removeAllListeners("request");
22
+ const delegate = (req, res) => {
23
+ if (appListeners.length === 0) {
24
+ if (!res.writableEnded) {
25
+ writeJson(res, 404, { error: "not found" });
26
+ }
27
+ return;
28
+ }
29
+ for (const l of appListeners)
30
+ l(req, res);
31
+ };
32
+ const onRequest = (req, res) => {
33
+ let url;
34
+ try {
35
+ url = new URL(req.url ?? "/", "http://localhost");
36
+ }
37
+ catch {
38
+ delegate(req, res);
39
+ return;
40
+ }
41
+ const route = table.get(url.pathname);
42
+ if (!route) {
43
+ delegate(req, res); // not ours — hand to the app's own handler.
44
+ return;
45
+ }
46
+ if (req.method !== "POST") {
47
+ writeJson(res, 405, { error: "method not allowed; use POST" });
48
+ return;
49
+ }
50
+ void handle(req, res, route, opts, onError).catch((err) => {
51
+ onError(err);
52
+ if (!res.headersSent)
53
+ writeJson(res, 500, { error: "internal error" });
54
+ });
55
+ };
56
+ httpServer.on("request", onRequest);
57
+ return {
58
+ close() {
59
+ httpServer.removeListener("request", onRequest);
60
+ // Restore the app's original listeners.
61
+ for (const l of appListeners)
62
+ httpServer.on("request", l);
63
+ },
64
+ };
65
+ }
66
+ /** handle runs one matched POST through mint → decode → dispatch → encode. */
67
+ async function handle(req, res, route, opts, onError) {
68
+ // Auth boundary — identical slot to serve()'s upgrade mint.
69
+ let ctx;
70
+ try {
71
+ ctx = await opts.mintCap(req);
72
+ }
73
+ catch (err) {
74
+ onError(err);
75
+ writeJson(res, 500, { error: "auth error" });
76
+ return;
77
+ }
78
+ if (ctx === null) {
79
+ writeJson(res, 401, { error: "unauthorized" });
80
+ return;
81
+ }
82
+ // Read + parse the JSON request body.
83
+ let json;
84
+ try {
85
+ const raw = await readBody(req);
86
+ json = raw.length === 0 ? {} : JSON.parse(raw);
87
+ }
88
+ catch {
89
+ writeJson(res, 400, { error: "invalid JSON request body" });
90
+ return;
91
+ }
92
+ // JSON → ZAP payload bytes (schema-specific codec from the route).
93
+ let payload;
94
+ try {
95
+ payload = route.decode(json);
96
+ }
97
+ catch (err) {
98
+ writeJson(res, 400, {
99
+ error: err instanceof Error ? err.message : "request decode failed",
100
+ });
101
+ return;
102
+ }
103
+ // Build the Call and dispatch it through the SAME handler the WS path uses.
104
+ let handler;
105
+ try {
106
+ handler = opts.rootCap(ctx);
107
+ }
108
+ catch (err) {
109
+ onError(err);
110
+ writeJson(res, 500, { error: "rootCap failed" });
111
+ return;
112
+ }
113
+ const call = {
114
+ method: route.method,
115
+ promiseID: 1,
116
+ target: NO_TARGET,
117
+ cap: EMPTY,
118
+ payload,
119
+ };
120
+ let resp;
121
+ try {
122
+ resp = await handler(call);
123
+ }
124
+ catch (err) {
125
+ onError(err);
126
+ writeJson(res, 500, { error: "handler error" });
127
+ return;
128
+ }
129
+ if (resp.status !== Status.OK) {
130
+ writeJson(res, httpStatusFor(resp.status), {
131
+ error: errorBodyText(resp.body) ?? `status ${resp.status}`,
132
+ });
133
+ return;
134
+ }
135
+ // Void method: 204, no body.
136
+ if (!route.encode) {
137
+ res.writeHead(204);
138
+ res.end();
139
+ return;
140
+ }
141
+ // ZAP response body → JSON value.
142
+ let out;
143
+ try {
144
+ out = route.encode(resp.body);
145
+ }
146
+ catch (err) {
147
+ onError(err);
148
+ writeJson(res, 500, { error: "response encode failed" });
149
+ return;
150
+ }
151
+ writeJson(res, 200, out);
152
+ }
153
+ /** readBody collects the request body as a UTF-8 string, capped at the ceiling. */
154
+ function readBody(req) {
155
+ return new Promise((resolve, reject) => {
156
+ const chunks = [];
157
+ let total = 0;
158
+ req.on("data", (chunk) => {
159
+ total += chunk.length;
160
+ if (total > MAX_BODY_BYTES) {
161
+ reject(new Error("request body too large"));
162
+ req.destroy();
163
+ return;
164
+ }
165
+ chunks.push(chunk);
166
+ });
167
+ req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
168
+ req.on("error", reject);
169
+ });
170
+ }
171
+ /** writeJson sends a JSON response with the given HTTP status. */
172
+ function writeJson(res, status, value) {
173
+ const body = Buffer.from(JSON.stringify(value), "utf8");
174
+ res.writeHead(status, {
175
+ "content-type": "application/json; charset=utf-8",
176
+ "content-length": String(body.byteLength),
177
+ });
178
+ res.end(body);
179
+ }
180
+ /** Map a ZAP Status to the closest HTTP status (they share the 4xx/5xx space). */
181
+ function httpStatusFor(zapStatus) {
182
+ switch (zapStatus) {
183
+ case Status.BadRequest:
184
+ case Status.Unauthorized:
185
+ case Status.Forbidden:
186
+ case Status.NotFound:
187
+ case Status.Internal:
188
+ return zapStatus;
189
+ default:
190
+ return zapStatus >= 400 && zapStatus <= 599 ? zapStatus : 500;
191
+ }
192
+ }
193
+ /** errorBodyText extracts {"error": "..."} text from a ZAP error body, if any. */
194
+ function errorBodyText(body) {
195
+ if (body.byteLength === 0)
196
+ return null;
197
+ try {
198
+ const parsed = JSON.parse(new TextDecoder().decode(body));
199
+ if (parsed && typeof parsed.error === "string")
200
+ return parsed.error;
201
+ }
202
+ catch {
203
+ // not JSON — fall through.
204
+ }
205
+ return null;
206
+ }
207
+ /** normalizePrefix trims a trailing slash and ensures a leading slash (or ""). */
208
+ function normalizePrefix(prefix) {
209
+ if (prefix === "" || prefix === "/")
210
+ return "";
211
+ let p = prefix.startsWith("/") ? prefix : "/" + prefix;
212
+ if (p.endsWith("/"))
213
+ p = p.slice(0, -1);
214
+ return p;
215
+ }
216
+ //# sourceMappingURL=http.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -6,19 +6,24 @@
6
6
  * runtime). Service shape comes from .zap schemas via `zapgen --target=ts`, not
7
7
  * a Zod/procedure DSL. Binary only — no JSON fallback, no Cap'n Proto.
8
8
  *
9
- * - serve(httpServer, opts) — Node: attach a ZAP RPC endpoint to http.Server.
10
- * - connect(url, opts) — browser/Node: open a typed RPC connection.
11
- * - {node,browser}WsTransport isomorphic WS transport factories.
12
- * - MintCap — the bearer→ctx auth slot at the upgrade.
9
+ * This root entry is BROWSER-SAFE it imports no Node built-ins. The Node
10
+ * server (`serve`, `nodeWsTransport`, the `MintCap` auth slot) lives behind the
11
+ * `@zap-proto/web/server` sub-path so `node:module` / `createRequire` never
12
+ * leaks into a browser bundle.
13
+ *
14
+ * - connect(url, opts) — open a typed RPC connection (browser or Node).
15
+ * - browserWsTransport — wrap the browser's native WebSocket.
16
+ * - Conn — the duplex ZAP RPC connection.
17
+ *
18
+ * For the Node server side, import from `@zap-proto/web/server`:
19
+ *
20
+ * import { serve } from "@zap-proto/web/server";
13
21
  */
14
- export { serve } from "./server.js";
15
- export type { ServeOptions, ServeHandle, RootCap } from "./server.js";
16
22
  export { connect } from "./client.js";
17
23
  export type { ConnectOptions, Connection } from "./client.js";
18
- export { nodeWsTransport, browserWsTransport, WS_CLOSE_UNSUPPORTED, } from "./transport.js";
24
+ export { browserWsTransport, WS_CLOSE_UNSUPPORTED } from "./transport.js";
19
25
  export type { WsTransport } from "./transport.js";
20
26
  export { Conn } from "./conn.js";
21
27
  export type { CallHandler, CallOptions } from "./conn.js";
22
- export type { MintCap } from "./auth.js";
23
28
  export { WebRpcError, ZapParseError } from "./errors.js";
24
29
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACpC,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAEtE,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACtC,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9D,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAE1D,YAAY,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACtC,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9D,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAC1E,YAAY,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAE1D,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC"}
package/dist/index.js CHANGED
@@ -8,14 +8,21 @@
8
8
  * runtime). Service shape comes from .zap schemas via `zapgen --target=ts`, not
9
9
  * a Zod/procedure DSL. Binary only — no JSON fallback, no Cap'n Proto.
10
10
  *
11
- * - serve(httpServer, opts) — Node: attach a ZAP RPC endpoint to http.Server.
12
- * - connect(url, opts) — browser/Node: open a typed RPC connection.
13
- * - {node,browser}WsTransport isomorphic WS transport factories.
14
- * - MintCap — the bearer→ctx auth slot at the upgrade.
11
+ * This root entry is BROWSER-SAFE it imports no Node built-ins. The Node
12
+ * server (`serve`, `nodeWsTransport`, the `MintCap` auth slot) lives behind the
13
+ * `@zap-proto/web/server` sub-path so `node:module` / `createRequire` never
14
+ * leaks into a browser bundle.
15
+ *
16
+ * - connect(url, opts) — open a typed RPC connection (browser or Node).
17
+ * - browserWsTransport — wrap the browser's native WebSocket.
18
+ * - Conn — the duplex ZAP RPC connection.
19
+ *
20
+ * For the Node server side, import from `@zap-proto/web/server`:
21
+ *
22
+ * import { serve } from "@zap-proto/web/server";
15
23
  */
16
- export { serve } from "./server.js";
17
24
  export { connect } from "./client.js";
18
- export { nodeWsTransport, browserWsTransport, WS_CLOSE_UNSUPPORTED, } from "./transport.js";
25
+ export { browserWsTransport, WS_CLOSE_UNSUPPORTED } from "./transport.js";
19
26
  export { Conn } from "./conn.js";
20
27
  export { WebRpcError, ZapParseError } from "./errors.js";
21
28
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,4CAA4C;AAE5C;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAGpC,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAGtC,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAKjC,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,4CAA4C;AAE5C;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAGtC,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAG1E,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAGjC,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC"}
package/dist/server.d.ts CHANGED
@@ -1,6 +1,10 @@
1
1
  import type { Server as HttpServer } from "node:http";
2
2
  import type { CallHandler } from "./conn.js";
3
3
  import type { MintCap } from "./auth.js";
4
+ export { nodeWsTransport } from "./transport.js";
5
+ export type { MintCap } from "./auth.js";
6
+ export { httpServe } from "./http.js";
7
+ export type { HttpRoute, HttpServeOptions, HttpServeHandle, } from "./http.js";
4
8
  /**
5
9
  * rootCap produces the per-connection dispatch root for a minted ctx.
6
10
  *
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAmB,MAAM,IAAI,UAAU,EAAE,MAAM,WAAW,CAAC;AAEvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAG7C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC;;;;;;;;GAQG;AACH,MAAM,MAAM,OAAO,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,WAAW,CAAC;AAErD,iCAAiC;AACjC,MAAM,WAAW,YAAY,CAAC,GAAG;IAC/B,4DAA4D;IAC5D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,4EAA4E;IAC5E,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;IACtB,iEAAiE;IACjE,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;IACtB,oDAAoD;IACpD,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;CAClC;AAED,qEAAqE;AACrE,MAAM,WAAW,WAAW;IAC1B,oDAAoD;IACpD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED;;;GAGG;AACH,wBAAgB,KAAK,CAAC,GAAG,EACvB,UAAU,EAAE,UAAU,EACtB,IAAI,EAAE,YAAY,CAAC,GAAG,CAAC,GACtB,WAAW,CAmEb"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAmB,MAAM,IAAI,UAAU,EAAE,MAAM,WAAW,CAAC;AAEvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAG7C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAKzC,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,YAAY,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAMzC,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,YAAY,EACV,SAAS,EACT,gBAAgB,EAChB,eAAe,GAChB,MAAM,WAAW,CAAC;AAEnB;;;;;;;;GAQG;AACH,MAAM,MAAM,OAAO,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,WAAW,CAAC;AAErD,iCAAiC;AACjC,MAAM,WAAW,YAAY,CAAC,GAAG;IAC/B,4DAA4D;IAC5D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,4EAA4E;IAC5E,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;IACtB,iEAAiE;IACjE,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;IACtB,oDAAoD;IACpD,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;CAClC;AAED,qEAAqE;AACrE,MAAM,WAAW,WAAW;IAC1B,oDAAoD;IACpD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED;;;GAGG;AACH,wBAAgB,KAAK,CAAC,GAAG,EACvB,UAAU,EAAE,UAAU,EACtB,IAAI,EAAE,YAAY,CAAC,GAAG,CAAC,GACtB,WAAW,CAmEb"}
package/dist/server.js CHANGED
@@ -22,6 +22,15 @@
22
22
  import { createRequire } from "node:module";
23
23
  import { Conn } from "./conn.js";
24
24
  import { nodeWsTransport } from "./transport.js";
25
+ // `@zap-proto/web/server` is the complete Node surface. Re-export the
26
+ // server-side transport factory and the auth slot so consumers import the whole
27
+ // server API from one sub-path (the root entry is browser-safe and omits these).
28
+ export { nodeWsTransport } from "./transport.js";
29
+ // httpServe mounts a JSON-over-HTTP face on the SAME ZAP service, dispatching
30
+ // each POST /<service>/<method> through the identical CallHandler serve() uses
31
+ // over WebSocket. The OpenAPI doc emitted by `zapgen --emit=openapi` describes
32
+ // exactly these routes.
33
+ export { httpServe } from "./http.js";
25
34
  /**
26
35
  * Attach a ZAP RPC endpoint to `httpServer`. Returns a handle whose close()
27
36
  * tears down the WebSocket server and all open connections.
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@zap-proto/web",
3
- "version": "0.1.0",
4
- "description": "Browser frontend RPC over ZAP — a drop-in tRPC replacement for Next.js / Remix / SvelteKit. Native ZAP envelopes over WebSocket binary frames; layered on @zap-proto/zap. No Cap'n Proto, no JSON.",
3
+ "version": "0.2.0",
4
+ "description": "Browser frontend RPC over ZAP — a drop-in tRPC replacement for Next.js / Remix / SvelteKit. Native ZAP envelopes over WebSocket binary frames, plus an optional JSON-over-HTTP face (httpServe) for the OpenAPI 3.1 surface emitted by zapgen. Layered on @zap-proto/zap. No Cap'n Proto.",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "sideEffects": false,
@@ -40,7 +40,7 @@
40
40
  "src"
41
41
  ],
42
42
  "dependencies": {
43
- "@zap-proto/zap": "^1.1.0"
43
+ "@zap-proto/zap": "^1.3.0"
44
44
  },
45
45
  "peerDependencies": {
46
46
  "ws": ">=8"
@@ -53,7 +53,9 @@
53
53
  "devDependencies": {
54
54
  "@types/node": "^22.0.0",
55
55
  "@types/ws": "^8.5.12",
56
+ "ajv": "^8.20.0",
56
57
  "eslint": "^9.0.0",
58
+ "happy-dom": "^20.10.4",
57
59
  "prettier": "^3.3.0",
58
60
  "typescript": "^5.9.3",
59
61
  "vitest": "^4.1.4",
package/src/http.ts ADDED
@@ -0,0 +1,315 @@
1
+ // Copyright (C) 2025, Lux Industries Inc. All rights reserved.
2
+ // See the file LICENSE for licensing terms.
3
+
4
+ /**
5
+ * http.ts — httpServe(httpServer, opts): mount a JSON-over-HTTP face on the
6
+ * SAME ZAP service that serve() exposes over WebSocket.
7
+ *
8
+ * Motivation: external consumers (and the OpenAPI surface emitted by
9
+ * `zapgen --emit=openapi`) speak POST /<service>/<method> with a JSON body.
10
+ * httpServe terminates that HTTP shape and dispatches each request through the
11
+ * identical {@link CallHandler} the WebSocket transport uses — so there is ONE
12
+ * service implementation, reached two ways.
13
+ *
14
+ * The JSON⇄ZAP codec is schema-specific and therefore supplied by the caller as
15
+ * a {@link HttpRoute} per method (the generated zapgen bindings know each
16
+ * struct's shape; the runtime stays schema-agnostic — orthogonal separation).
17
+ * Each route declares:
18
+ * - path: POST path, matching the OpenAPI doc (`/document-service/...`).
19
+ * - method: the .zap method ordinal (the same ordinal the WS client sends).
20
+ * - decode: JSON request body → ZAP payload bytes.
21
+ * - encode: ZAP response body bytes → JSON value (omit for void methods).
22
+ *
23
+ * Auth runs per-request via the same {@link MintCap} slot serve() uses: a null
24
+ * mint yields HTTP 401; a non-null mint becomes the `ctx` passed to rootCap,
25
+ * whose {@link CallHandler} dispatches the decoded Call.
26
+ *
27
+ * Node `http` only — no framework. The HTTP server is supplied by the caller
28
+ * (the same http.Server that serve() attaches its WebSocket upgrade to), so one
29
+ * port serves WS + HTTP.
30
+ */
31
+
32
+ import type {
33
+ IncomingMessage,
34
+ ServerResponse,
35
+ Server as HttpServer,
36
+ } from "node:http";
37
+ import { NO_TARGET, Status, type Call, type Response } from "@zap-proto/zap";
38
+ import type { CallHandler } from "./conn.js";
39
+ import type { MintCap } from "./auth.js";
40
+ import type { RootCap } from "./server.js";
41
+
42
+ const EMPTY = new Uint8Array(0);
43
+ const MAX_BODY_BYTES = 8 * 1024 * 1024; // 8 MiB request-body ceiling.
44
+
45
+ /**
46
+ * HttpRoute binds one POST path to one ZAP method, with the JSON⇄ZAP codec for
47
+ * that method. `decode` turns the parsed JSON request body into the method's
48
+ * ZAP payload bytes; `encode` turns the ZAP response body bytes into the JSON
49
+ * value to serialize. A void method omits `encode` (returns HTTP 204).
50
+ */
51
+ export interface HttpRoute {
52
+ /** POST path, e.g. "/document-service/create-document". */
53
+ path: string;
54
+ /** The .zap method ordinal this path dispatches. */
55
+ method: number;
56
+ /** Parse a JSON request body into ZAP payload bytes. */
57
+ decode: (json: unknown) => Uint8Array;
58
+ /** Encode ZAP response body bytes into a JSON value. Omit for void methods. */
59
+ encode?: (body: Uint8Array) => unknown;
60
+ }
61
+
62
+ /** Options for {@link httpServe}. */
63
+ export interface HttpServeOptions<Ctx> {
64
+ /** The method routes (one per OpenAPI operation). */
65
+ routes: HttpRoute[];
66
+ /** Bearer→ctx boundary; return null to reject with HTTP 401. */
67
+ mintCap: MintCap<Ctx>;
68
+ /** Produce the dispatch handler for a minted ctx (same shape as serve()). */
69
+ rootCap: RootCap<Ctx>;
70
+ /**
71
+ * Optional path prefix mounted before each route path, e.g. "/v1" makes the
72
+ * route "/echo/echo" serve at "/v1/echo/echo". Default "" (no prefix).
73
+ */
74
+ prefix?: string;
75
+ /** Optional sink for handler errors. */
76
+ onError?: (err: unknown) => void;
77
+ }
78
+
79
+ /** A live HTTP face attached to an http.Server. */
80
+ export interface HttpServeHandle {
81
+ /** Detach the request listener. */
82
+ close(): void;
83
+ }
84
+
85
+ /**
86
+ * Attach a JSON-over-HTTP face to `httpServer`. Returns a handle whose close()
87
+ * detaches the listener. Non-matching requests are passed through untouched so
88
+ * httpServe can coexist with the app's own request handler and with serve()'s
89
+ * WebSocket upgrade on the same server.
90
+ */
91
+ export function httpServe<Ctx>(
92
+ httpServer: HttpServer,
93
+ opts: HttpServeOptions<Ctx>,
94
+ ): HttpServeHandle {
95
+ const onError = opts.onError ?? (() => {});
96
+ const prefix = normalizePrefix(opts.prefix ?? "");
97
+
98
+ // path → route, built once. Promise ordinal collisions are the caller's
99
+ // concern (each method has a distinct path by construction).
100
+ const table = new Map<string, HttpRoute>();
101
+ for (const r of opts.routes) table.set(prefix + r.path, r);
102
+
103
+ // Take over `request` dispatch: capture the app's existing listeners, detach
104
+ // them, and install a single dispatcher that either handles a matched route
105
+ // fully (so the response is written exactly once) or delegates to the
106
+ // captured listeners. Because route handling is async (mintCap, body read),
107
+ // a plain extra listener would race the app's synchronous catch-all; owning
108
+ // dispatch is the only way to guarantee a single writer per request.
109
+ type RequestListener = (req: IncomingMessage, res: ServerResponse) => void;
110
+ const appListeners = httpServer.listeners("request") as RequestListener[];
111
+ httpServer.removeAllListeners("request");
112
+
113
+ const delegate = (req: IncomingMessage, res: ServerResponse): void => {
114
+ if (appListeners.length === 0) {
115
+ if (!res.writableEnded) {
116
+ writeJson(res, 404, { error: "not found" });
117
+ }
118
+ return;
119
+ }
120
+ for (const l of appListeners) l(req, res);
121
+ };
122
+
123
+ const onRequest = (req: IncomingMessage, res: ServerResponse): void => {
124
+ let url: URL;
125
+ try {
126
+ url = new URL(req.url ?? "/", "http://localhost");
127
+ } catch {
128
+ delegate(req, res);
129
+ return;
130
+ }
131
+ const route = table.get(url.pathname);
132
+ if (!route) {
133
+ delegate(req, res); // not ours — hand to the app's own handler.
134
+ return;
135
+ }
136
+
137
+ if (req.method !== "POST") {
138
+ writeJson(res, 405, { error: "method not allowed; use POST" });
139
+ return;
140
+ }
141
+
142
+ void handle(req, res, route, opts, onError).catch((err) => {
143
+ onError(err);
144
+ if (!res.headersSent) writeJson(res, 500, { error: "internal error" });
145
+ });
146
+ };
147
+
148
+ httpServer.on("request", onRequest);
149
+ return {
150
+ close(): void {
151
+ httpServer.removeListener("request", onRequest);
152
+ // Restore the app's original listeners.
153
+ for (const l of appListeners) httpServer.on("request", l);
154
+ },
155
+ };
156
+ }
157
+
158
+ /** handle runs one matched POST through mint → decode → dispatch → encode. */
159
+ async function handle<Ctx>(
160
+ req: IncomingMessage,
161
+ res: ServerResponse,
162
+ route: HttpRoute,
163
+ opts: HttpServeOptions<Ctx>,
164
+ onError: (err: unknown) => void,
165
+ ): Promise<void> {
166
+ // Auth boundary — identical slot to serve()'s upgrade mint.
167
+ let ctx: Ctx | null;
168
+ try {
169
+ ctx = await opts.mintCap(req);
170
+ } catch (err) {
171
+ onError(err);
172
+ writeJson(res, 500, { error: "auth error" });
173
+ return;
174
+ }
175
+ if (ctx === null) {
176
+ writeJson(res, 401, { error: "unauthorized" });
177
+ return;
178
+ }
179
+
180
+ // Read + parse the JSON request body.
181
+ let json: unknown;
182
+ try {
183
+ const raw = await readBody(req);
184
+ json = raw.length === 0 ? {} : JSON.parse(raw);
185
+ } catch {
186
+ writeJson(res, 400, { error: "invalid JSON request body" });
187
+ return;
188
+ }
189
+
190
+ // JSON → ZAP payload bytes (schema-specific codec from the route).
191
+ let payload: Uint8Array;
192
+ try {
193
+ payload = route.decode(json);
194
+ } catch (err) {
195
+ writeJson(res, 400, {
196
+ error: err instanceof Error ? err.message : "request decode failed",
197
+ });
198
+ return;
199
+ }
200
+
201
+ // Build the Call and dispatch it through the SAME handler the WS path uses.
202
+ let handler: CallHandler;
203
+ try {
204
+ handler = opts.rootCap(ctx);
205
+ } catch (err) {
206
+ onError(err);
207
+ writeJson(res, 500, { error: "rootCap failed" });
208
+ return;
209
+ }
210
+
211
+ const call: Call = {
212
+ method: route.method,
213
+ promiseID: 1,
214
+ target: NO_TARGET,
215
+ cap: EMPTY,
216
+ payload,
217
+ };
218
+
219
+ let resp: Response;
220
+ try {
221
+ resp = await handler(call);
222
+ } catch (err) {
223
+ onError(err);
224
+ writeJson(res, 500, { error: "handler error" });
225
+ return;
226
+ }
227
+
228
+ if (resp.status !== Status.OK) {
229
+ writeJson(res, httpStatusFor(resp.status), {
230
+ error: errorBodyText(resp.body) ?? `status ${resp.status}`,
231
+ });
232
+ return;
233
+ }
234
+
235
+ // Void method: 204, no body.
236
+ if (!route.encode) {
237
+ res.writeHead(204);
238
+ res.end();
239
+ return;
240
+ }
241
+
242
+ // ZAP response body → JSON value.
243
+ let out: unknown;
244
+ try {
245
+ out = route.encode(resp.body);
246
+ } catch (err) {
247
+ onError(err);
248
+ writeJson(res, 500, { error: "response encode failed" });
249
+ return;
250
+ }
251
+ writeJson(res, 200, out);
252
+ }
253
+
254
+ /** readBody collects the request body as a UTF-8 string, capped at the ceiling. */
255
+ function readBody(req: IncomingMessage): Promise<string> {
256
+ return new Promise((resolve, reject) => {
257
+ const chunks: Buffer[] = [];
258
+ let total = 0;
259
+ req.on("data", (chunk: Buffer) => {
260
+ total += chunk.length;
261
+ if (total > MAX_BODY_BYTES) {
262
+ reject(new Error("request body too large"));
263
+ req.destroy();
264
+ return;
265
+ }
266
+ chunks.push(chunk);
267
+ });
268
+ req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
269
+ req.on("error", reject);
270
+ });
271
+ }
272
+
273
+ /** writeJson sends a JSON response with the given HTTP status. */
274
+ function writeJson(res: ServerResponse, status: number, value: unknown): void {
275
+ const body = Buffer.from(JSON.stringify(value), "utf8");
276
+ res.writeHead(status, {
277
+ "content-type": "application/json; charset=utf-8",
278
+ "content-length": String(body.byteLength),
279
+ });
280
+ res.end(body);
281
+ }
282
+
283
+ /** Map a ZAP Status to the closest HTTP status (they share the 4xx/5xx space). */
284
+ function httpStatusFor(zapStatus: number): number {
285
+ switch (zapStatus) {
286
+ case Status.BadRequest:
287
+ case Status.Unauthorized:
288
+ case Status.Forbidden:
289
+ case Status.NotFound:
290
+ case Status.Internal:
291
+ return zapStatus;
292
+ default:
293
+ return zapStatus >= 400 && zapStatus <= 599 ? zapStatus : 500;
294
+ }
295
+ }
296
+
297
+ /** errorBodyText extracts {"error": "..."} text from a ZAP error body, if any. */
298
+ function errorBodyText(body: Uint8Array): string | null {
299
+ if (body.byteLength === 0) return null;
300
+ try {
301
+ const parsed = JSON.parse(new TextDecoder().decode(body));
302
+ if (parsed && typeof parsed.error === "string") return parsed.error;
303
+ } catch {
304
+ // not JSON — fall through.
305
+ }
306
+ return null;
307
+ }
308
+
309
+ /** normalizePrefix trims a trailing slash and ensures a leading slash (or ""). */
310
+ function normalizePrefix(prefix: string): string {
311
+ if (prefix === "" || prefix === "/") return "";
312
+ let p = prefix.startsWith("/") ? prefix : "/" + prefix;
313
+ if (p.endsWith("/")) p = p.slice(0, -1);
314
+ return p;
315
+ }
package/src/index.ts CHANGED
@@ -9,28 +9,27 @@
9
9
  * runtime). Service shape comes from .zap schemas via `zapgen --target=ts`, not
10
10
  * a Zod/procedure DSL. Binary only — no JSON fallback, no Cap'n Proto.
11
11
  *
12
- * - serve(httpServer, opts) — Node: attach a ZAP RPC endpoint to http.Server.
13
- * - connect(url, opts) — browser/Node: open a typed RPC connection.
14
- * - {node,browser}WsTransport isomorphic WS transport factories.
15
- * - MintCap — the bearer→ctx auth slot at the upgrade.
12
+ * This root entry is BROWSER-SAFE it imports no Node built-ins. The Node
13
+ * server (`serve`, `nodeWsTransport`, the `MintCap` auth slot) lives behind the
14
+ * `@zap-proto/web/server` sub-path so `node:module` / `createRequire` never
15
+ * leaks into a browser bundle.
16
+ *
17
+ * - connect(url, opts) — open a typed RPC connection (browser or Node).
18
+ * - browserWsTransport — wrap the browser's native WebSocket.
19
+ * - Conn — the duplex ZAP RPC connection.
20
+ *
21
+ * For the Node server side, import from `@zap-proto/web/server`:
22
+ *
23
+ * import { serve } from "@zap-proto/web/server";
16
24
  */
17
25
 
18
- export { serve } from "./server.js";
19
- export type { ServeOptions, ServeHandle, RootCap } from "./server.js";
20
-
21
26
  export { connect } from "./client.js";
22
27
  export type { ConnectOptions, Connection } from "./client.js";
23
28
 
24
- export {
25
- nodeWsTransport,
26
- browserWsTransport,
27
- WS_CLOSE_UNSUPPORTED,
28
- } from "./transport.js";
29
+ export { browserWsTransport, WS_CLOSE_UNSUPPORTED } from "./transport.js";
29
30
  export type { WsTransport } from "./transport.js";
30
31
 
31
32
  export { Conn } from "./conn.js";
32
33
  export type { CallHandler, CallOptions } from "./conn.js";
33
34
 
34
- export type { MintCap } from "./auth.js";
35
-
36
35
  export { WebRpcError, ZapParseError } from "./errors.js";
package/src/server.ts CHANGED
@@ -29,6 +29,23 @@ import { Conn } from "./conn.js";
29
29
  import { nodeWsTransport } from "./transport.js";
30
30
  import type { MintCap } from "./auth.js";
31
31
 
32
+ // `@zap-proto/web/server` is the complete Node surface. Re-export the
33
+ // server-side transport factory and the auth slot so consumers import the whole
34
+ // server API from one sub-path (the root entry is browser-safe and omits these).
35
+ export { nodeWsTransport } from "./transport.js";
36
+ export type { MintCap } from "./auth.js";
37
+
38
+ // httpServe mounts a JSON-over-HTTP face on the SAME ZAP service, dispatching
39
+ // each POST /<service>/<method> through the identical CallHandler serve() uses
40
+ // over WebSocket. The OpenAPI doc emitted by `zapgen --emit=openapi` describes
41
+ // exactly these routes.
42
+ export { httpServe } from "./http.js";
43
+ export type {
44
+ HttpRoute,
45
+ HttpServeOptions,
46
+ HttpServeHandle,
47
+ } from "./http.js";
48
+
32
49
  /**
33
50
  * rootCap produces the per-connection dispatch root for a minted ctx.
34
51
  *