@statewalker/webrun-http-streams 0.1.1 → 0.2.1

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.
Files changed (55) hide show
  1. package/README.md +288 -11
  2. package/dist/bytes.d.ts +43 -0
  3. package/dist/bytes.d.ts.map +1 -0
  4. package/dist/codec-default.d.ts +7 -0
  5. package/dist/codec-default.d.ts.map +1 -0
  6. package/dist/duplex-site-builder.d.ts +3 -0
  7. package/dist/duplex-site-builder.d.ts.map +1 -1
  8. package/dist/envelope.d.ts +10 -10
  9. package/dist/envelope.d.ts.map +1 -1
  10. package/dist/fetch.d.ts +3 -2
  11. package/dist/fetch.d.ts.map +1 -1
  12. package/dist/http-data.d.ts +11 -7
  13. package/dist/http-data.d.ts.map +1 -1
  14. package/dist/http-error.d.ts.map +1 -1
  15. package/dist/http-stubs.d.ts +12 -0
  16. package/dist/http-stubs.d.ts.map +1 -1
  17. package/dist/http1/chunked.d.ts +18 -0
  18. package/dist/http1/chunked.d.ts.map +1 -0
  19. package/dist/http1/decode.d.ts +5 -0
  20. package/dist/http1/decode.d.ts.map +1 -0
  21. package/dist/http1/encode.d.ts +20 -0
  22. package/dist/http1/encode.d.ts.map +1 -0
  23. package/dist/http1/errors.d.ts +9 -0
  24. package/dist/http1/errors.d.ts.map +1 -0
  25. package/dist/http1/headers.d.ts +78 -0
  26. package/dist/http1/headers.d.ts.map +1 -0
  27. package/dist/http1/index.d.ts +18 -0
  28. package/dist/http1/index.d.ts.map +1 -0
  29. package/dist/index.d.ts +6 -2
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +1039 -137
  32. package/dist/message.d.ts +50 -0
  33. package/dist/message.d.ts.map +1 -0
  34. package/dist/request-streams.d.ts +52 -0
  35. package/dist/request-streams.d.ts.map +1 -0
  36. package/dist/sniff.d.ts +18 -0
  37. package/dist/sniff.d.ts.map +1 -0
  38. package/package.json +8 -6
  39. package/src/bytes.ts +157 -0
  40. package/src/codec-default.ts +13 -0
  41. package/src/duplex-site-builder.ts +9 -1
  42. package/src/envelope.ts +43 -34
  43. package/src/fetch.ts +130 -12
  44. package/src/http-data.ts +160 -17
  45. package/src/http-stubs.ts +72 -18
  46. package/src/http1/chunked.ts +89 -0
  47. package/src/http1/decode.ts +208 -0
  48. package/src/http1/encode.ts +181 -0
  49. package/src/http1/errors.ts +8 -0
  50. package/src/http1/headers.ts +263 -0
  51. package/src/http1/index.ts +40 -0
  52. package/src/index.ts +18 -0
  53. package/src/message.ts +62 -0
  54. package/src/request-streams.ts +75 -0
  55. package/src/sniff.ts +68 -0
package/README.md CHANGED
@@ -1,32 +1,73 @@
1
1
  # @statewalker/webrun-http-streams
2
2
 
3
- HTTP request / response over a `Duplex` from any `webrun-streams-*` adapter. Replaces `webrun-http` + `webrun-http-port`.
3
+ HTTP request / response over a `Duplex` from any `webrun-streams-*` adapter.
4
+ Replaces the retired `webrun-http` + `webrun-http-port` packages.
4
5
 
5
- ## Three layers
6
+ ## Layers
7
+
8
+ Three of them sit on the `Duplex` seam, plus one older transport-agnostic pair
9
+ that predates it and is kept for `webrun-http-browser`.
6
10
 
7
11
  ### Data layer — `httpFetch` / `httpServe`
8
12
 
13
+ Envelopes and body iterators, no `Request`/`Response` involved.
14
+
9
15
  ```ts
10
16
  import { httpFetch, httpServe } from "@statewalker/webrun-http-streams";
11
- import { connect } from "@statewalker/webrun-streams-ws";
12
17
 
13
- const { call } = await connect({ url });
18
+ // `call` is a Duplex. In-process it can be the handler itself; over a
19
+ // transport it comes from an adapter's `connect`:
20
+ // const { call } = await connect({ url }); // webrun-streams-ws
21
+ const call = httpServe(async (env, body) => {
22
+ for await (const _chunk of body) {
23
+ /* drain the request body */
24
+ }
25
+ return {
26
+ envelope: { status: 200, statusText: "OK", headers: [["content-type", "text/plain"]] },
27
+ body: [new TextEncoder().encode(`hello ${new URL(env.url).pathname}`)],
28
+ };
29
+ });
30
+
14
31
  const { envelope, body } = await httpFetch(call, {
15
- url: "/api/time",
32
+ url: "http://peer.test/api/time",
16
33
  method: "GET",
17
34
  headers: [],
18
35
  });
36
+ // envelope.status === 200; `body` is an AsyncIterable<Uint8Array>
19
37
  ```
20
38
 
21
- `httpServe(handler)` returns a `Duplex` you can hand to any adapter's `serve(...)`.
39
+ `httpServe(handler)` returns a `Duplex` you can hand to any adapter's
40
+ `serve(...)`. Because a `Duplex` is just `(input) => AsyncGenerator<Uint8Array>`,
41
+ the handler side *is* a usable `call` with no transport at all — that is what
42
+ the snippet above does, and it is the cheapest way to test a handler.
43
+
44
+ A relative `url` in the envelope works, but note it does not survive the round
45
+ trip verbatim: HTTP/1.1 origin-form carries no scheme or authority, so the
46
+ decoder rebuilds an absolute url from the codec's `scheme`/`host` options.
47
+ `url: "/api/time"` arrives at the handler as `http://localhost/api/time` unless
48
+ you configure the codec (see below).
22
49
 
23
50
  ### Fetch layer — `fetchOverDuplex` / `serveFetchOverDuplex`
24
51
 
52
+ The same thing in terms of standard `Request` / `Response`.
53
+
25
54
  ```ts
26
- const response = await fetchOverDuplex(call, new Request("/api/time"));
55
+ import { fetchOverDuplex, serveFetchOverDuplex } from "@statewalker/webrun-http-streams";
56
+
57
+ const call = serveFetchOverDuplex(
58
+ async (request) => new Response(`hello ${new URL(request.url).pathname}`),
59
+ );
60
+ const response = await fetchOverDuplex(call, new Request("http://peer.test/api/time"));
61
+ await response.text(); // "hello /api/time"
27
62
  ```
28
63
 
29
- `serveFetchOverDuplex(handler)` adapts a `(Request) => Promise<Response>` handler.
64
+ Hop-by-hop headers (`connection`, `transfer-encoding`, `host`, …) are stripped
65
+ in both directions: the codec surfaces them verbatim, but they are meaningless
66
+ to a `Request`/`Response` and re-emitting them from a relay would corrupt its
67
+ framing.
68
+
69
+ `fetchOverDuplex` plumbs `request.signal` into body iteration, so aborting the
70
+ signal terminates the call.
30
71
 
31
72
  ### Site host — `DuplexSiteBuilder`
32
73
 
@@ -35,15 +76,251 @@ import { DuplexSiteBuilder } from "@statewalker/webrun-http-streams";
35
76
  import { serve } from "@statewalker/webrun-streams-port";
36
77
 
37
78
  const stop = await new DuplexSiteBuilder()
38
- .setHandler(siteHandler)
79
+ .setHandler(siteHandler) // (Request) => Promise<Response>
39
80
  .start(serve, { port });
81
+ // later: await stop();
82
+ ```
83
+
84
+ `.setCodec(codec)` pins the wire format. `DuplexSiteBuilder` is the
85
+ cross-platform sibling of `HostedSiteBuilder` (browser + ServiceWorker) — same
86
+ `SiteHandler` seam, different transport. It holds no site configuration of its
87
+ own; endpoints, files and auth belong to the `SiteHandler` producer (typically
88
+ `SiteBuilder`).
89
+
90
+ > Not runnable as written above: it needs a live `MessagePort` and
91
+ > `@statewalker/webrun-streams-port`, which this package does not depend on.
92
+ > `start` accepts any `Serve<P>`, so an in-process one is enough to exercise
93
+ > it. Calling `start()` before `setHandler()` throws.
94
+
95
+ ### Transport-agnostic stubs — `newHttpClientStub` / `newHttpServerStub`
96
+
97
+ Predates the `Duplex` seam and does **not** use it. Instead of bytes on a wire,
98
+ these move a `SerializedHttpEnvelope` — a plain options object plus a body
99
+ iterable — over whatever `send` function you give them. `webrun-http-browser`
100
+ is built on this pair, because a MessagePort can structured-clone the envelope
101
+ directly and never needs a byte encoding.
102
+
103
+ ```ts
104
+ import { newHttpClientStub, newHttpServerStub } from "@statewalker/webrun-http-streams";
105
+
106
+ const server = newHttpServerStub(async (request) => new Response(`echo ${await request.text()}`));
107
+ const client = newHttpClientStub(server); // `send` is anything envelope-in, envelope-out
108
+
109
+ const res = await client(new Request("http://peer.test/x", { method: "POST", body: "hi" }));
110
+ await res.text(); // "echo hi"
40
111
  ```
41
112
 
42
- `DuplexSiteBuilder` is the cross-platform sibling of `HostedSiteBuilder` (browser+SW) — same `SiteHandler` seam, different transport.
113
+ A `send` that resolves `undefined` — no service registered for this call —
114
+ becomes a `404` on the client side. `SerializedHttpEnvelope.content` must be
115
+ *productive*: it has to yield or finish on its own, because a body neither stub
116
+ is allowed to read is released by draining it, not by a bare `.return()`.
117
+
118
+ Use the `Duplex` layers for anything new. These stay because the browser
119
+ package's MessagePort transport is built around them.
43
120
 
44
121
  ## Wire format
45
122
 
46
- `<JSON.stringify(envelope)>\n<body bytes…>` — newline-delimited JSON header followed by raw body bytes. Same shape as the legacy `webrun-http-port` so a future bridging adapter could interop.
123
+ Conforming **HTTP/1.1** by default — a `Duplex` carries bytes a real HTTP
124
+ implementation can parse, and accepts bytes a real HTTP implementation
125
+ produces. Verified against `node:http` in both directions.
126
+
127
+ ```
128
+ POST /api?a=1 HTTP/1.1
129
+ Host: peer.test
130
+ Connection: close
131
+ Transfer-Encoding: chunked
132
+
133
+ 5
134
+ hello
135
+ 0
136
+
137
+ ```
138
+
139
+ One message per `Duplex` call: `Connection: close` is always emitted, and bytes
140
+ after a complete message are an error. Bodies use `Content-Length` when the
141
+ caller declares one and chunked transfer coding otherwise; a message declaring
142
+ both is refused.
143
+
144
+ The codec is deliberately strict: every ambiguity is a refusal rather than a
145
+ guess. One consequence worth calling out explicitly — RFC 9112 §2.2 permits a
146
+ recipient to *tolerate* a single leading CRLF sent before the request-line
147
+ ("SHOULD ignore"). This codec declines that leniency and refuses it as a
148
+ malformed request line; see ADR-0006.
149
+
150
+ ### Bodyless messages
151
+
152
+ Responses with a null-body status — **204, 205, 304** — and responses to `HEAD`
153
+ or `OPTIONS` put no bytes on the wire, in both directions, even if the
154
+ handler's `Response` carries a body stream. The handler's stream is cancelled
155
+ rather than sent. On the reading side you get `new Response(null, init)`, whose
156
+ `.body` is `null`.
157
+
158
+ The internal null-body set also lists 101 and 103, but neither is reachable
159
+ through the fetch layer: `ResponseInit.status` must be 200–599, so
160
+ `fetchOverDuplex` throws a `TypeError` from the `Response` constructor if a
161
+ peer answers with one. Use the data layer (`httpFetch`) if you need to see an
162
+ informational status.
163
+
164
+ ### Choosing a codec
165
+
166
+ ```ts
167
+ import {
168
+ defaultCodec,
169
+ httpCodec,
170
+ jsonEnvelopeCodec,
171
+ newHttpCodec,
172
+ newSniffingCodec,
173
+ } from "@statewalker/webrun-http-streams";
174
+
175
+ // default: writes HTTP/1.1, accepts HTTP/1.1 or the legacy JSON envelope
176
+ await httpFetch(call, env);
177
+
178
+ // pinned
179
+ await httpFetch(call, env, body, { codec: httpCodec });
180
+
181
+ // the scheme and authority are not on the HTTP/1.1 wire; supply them here
182
+ const codec = newHttpCodec({ scheme: "https", host: "peer.test" });
183
+ ```
184
+
185
+ | `newHttpCodec` option | Default | Meaning |
186
+ | --- | --- | --- |
187
+ | `scheme` | `"http"` | Scheme used to rebuild an absolute url on decode. Configuration, not wire data — a peer configured `http` rebuilds an `https` url as `http`. |
188
+ | `host` | `"localhost"` | Authority used when a url carries none. Also fills the mandatory `Host` header. |
189
+ | `maxHeaderBytes` | `65536` | Bound on the whole head section, start line included. |
190
+
191
+ `httpCodec` is `newHttpCodec()` with those defaults. `defaultCodec` — used
192
+ whenever no `codec` option is supplied — is
193
+ `newSniffingCodec({ write: httpCodec, accept: [httpCodec, jsonEnvelopeCodec] })`.
194
+
195
+ The previous format — `<JSON.stringify(envelope)>` + newline + body bytes —
196
+ remains available as `jsonEnvelopeCodec` (and as the raw `encodeMessage` /
197
+ `decodeMessage` pair), and readers accept it automatically, so the two ends of
198
+ a peer pair can be upgraded in either order. A server answers in whichever
199
+ format read the request. Sniffing needs no handshake because the formats are
200
+ self-identifying: a JSON envelope always begins `{`, which is not a token
201
+ character and so can never begin an HTTP start-line. See ADR-0006.
202
+
203
+ ## Browser support
204
+
205
+ Everything here is built on `Request`, `Response`, `ReadableStream`,
206
+ `TextEncoder` and `TextDecoder`. Browsers do **not** behave uniformly, and one
207
+ difference is large enough to change what this package can do.
208
+
209
+ **Firefox does not implement `Request.prototype.body`** (checked against
210
+ Firefox 146). `Object.getOwnPropertyDescriptor(Request.prototype, "body")` is
211
+ `null` — the accessor is genuinely absent — and because a `ReadableStream` is
212
+ then not a recognised `BodyInit`, `new Request(url, { body: stream })` falls
213
+ through to the string branch and stores the literal text
214
+ `[object ReadableStream]`.
215
+
216
+ Both directions have a fallback, and both cost the same thing:
217
+
218
+ - **Sending** (`fetchOverDuplex`, `newHttpClientStub`) — with no
219
+ `request.body` to stream from, the whole payload is buffered with
220
+ `request.arrayBuffer()` before it goes on the wire.
221
+ - **Receiving** (`serveFetchOverDuplex`, `newHttpServerStub`) — the request
222
+ body is drained into one contiguous buffer before the `Request` handed to
223
+ your handler is constructed. Without this the handler would silently read the
224
+ string `[object ReadableStream]` and answer `200` with corrupt data.
225
+
226
+ So on Firefox: **request streaming does not happen**. A 1 GiB upload from a
227
+ Firefox page is a 1 GiB allocation, where Chromium and Node stream it chunk by
228
+ chunk. Response streaming is unaffected — `Response.body` exists everywhere.
229
+
230
+ The check is a capability probe, not a user-agent test, so it flips on its own
231
+ the day Firefox ships request streams. Safari is untested and is the case to
232
+ look at first if a report arrives: it has had `Request.body` since 11.1 but
233
+ only accepts a stream as `init.body` from Technology Preview 250, so a shipping
234
+ Safari has the reader half without the upload half and takes the streaming
235
+ branch here.
236
+
237
+ One further divergence, on the sending side only: buffering cannot distinguish
238
+ an absent body from an empty one. Chromium's
239
+ `new Request(url, { method: "POST", body: "" })` yields a non-null empty
240
+ stream, so an empty body is framed on the wire; on the Firefox path a
241
+ zero-length `arrayBuffer()` is indistinguishable from no body and none is sent.
242
+ Both decode to the same empty body at the far end, so only the framing differs.
243
+ This does not arise for the stubs, whose envelope always carries a `content`
244
+ iterable.
245
+
246
+ ## Errors
247
+
248
+ The codec answers on the wire wherever it can, because a real HTTP peer cannot
249
+ receive a JavaScript exception — only a response, or a connection that ends
250
+ with no status.
251
+
252
+ | Situation | On the wire | On a webrun caller |
253
+ | --- | --- | --- |
254
+ | Handler throws | `500 Internal Server Error`, message in the body | `httpFetch` rejects with the peer's error, stack and custom fields preserved |
255
+ | Request cannot be parsed | `400 Bad Request`, reason in the body | `httpFetch` rejects with the parse error |
256
+ | Transport fails | nothing — the failure is not ours to answer | the error propagates unchanged |
257
+
258
+ Both error responses carry the serialized error in an `x-webrun-error` header
259
+ (exported as `PEER_ERROR_HEADER`), which is what lets a webrun peer re-throw a
260
+ real `Error` rather than a status code. A third party just reads a conforming
261
+ response and ignores the header. The header value is bounded at 4096
262
+ characters, so a very large error message is truncated rather than blowing past
263
+ `maxHeaderBytes` at the reader.
264
+
265
+ Refusals are always `HttpParseError`, whichever codec read the message, so a
266
+ consumer can tell "the peer sent something malformed" from "the transport
267
+ broke". **Check `err.name`, not `instanceof`** — the two cases differ:
268
+
269
+ ```ts
270
+ import { httpFetch } from "@statewalker/webrun-http-streams";
271
+
272
+ try {
273
+ await httpFetch(call, env);
274
+ } catch (err) {
275
+ if ((err as Error).name === "HttpParseError") {
276
+ // malformed bytes — one side or an intermediary is at fault
277
+ } else {
278
+ throw err; // transport failure, or the peer handler's own error
279
+ }
280
+ }
281
+ ```
282
+
283
+ `instanceof HttpParseError` holds only when *this* side's decoder refused what
284
+ the peer sent. When the peer refuses **your** request, its 400 travels back
285
+ through the `x-webrun-error` header and is rehydrated by `deserializeError`,
286
+ which builds a plain `Error` carrying the original `name`, `message`, `stack`
287
+ and custom fields — not the original class. So `err.name` is
288
+ `"HttpParseError"` in both directions, and `instanceof` is true in only one.
289
+ The same applies to every error class crossing this boundary, including your
290
+ own handler's `Error` subclasses.
291
+
292
+ A refusal is answered in whatever format the peer was speaking, so a caller
293
+ pinned to the legacy envelope receives its 400 as an envelope rather than as
294
+ HTTP/1.1.
295
+
296
+ `HttpError` is a separate, unrelated helper for handlers that want to raise a
297
+ status deliberately (`HttpError.errorResourceNotFound()` and friends). It is
298
+ not wired into the wire format: throwing one produces a 500 like any other
299
+ exception.
300
+
301
+ ## Exports
302
+
303
+ | Export | What it is |
304
+ | --- | --- |
305
+ | `httpFetch`, `httpServe` | Data layer. Types: `HttpDataHandler`, `HttpDataHandlerResult`, `HttpDataOptions`, `HttpFetchResult`. |
306
+ | `fetchOverDuplex`, `serveFetchOverDuplex` | Fetch layer. |
307
+ | `DuplexSiteBuilder`, `SiteHandler` | Site host over a `Connect`/`Serve` pair. |
308
+ | `newHttpClientStub`, `newHttpServerStub` | Transport-agnostic stubs. Types: `HttpHandler`, `SerializedHttpEnvelope`, `SerializedHttpRequest`, `SerializedHttpResponse`. |
309
+ | `defaultCodec`, `httpCodec`, `newHttpCodec`, `HttpCodecOptions` | HTTP/1.1 codec and the default sniffing codec. |
310
+ | `jsonEnvelopeCodec`, `encodeMessage`, `decodeMessage` | Legacy JSON-envelope format. |
311
+ | `newSniffingCodec`, `SniffingCodecOptions` | Build your own write/accept combination. |
312
+ | `HttpParseError` | Every codec refusal. |
313
+ | `HttpError`, `HttpErrorOptions` | Status-raising helper for handlers; not wired into the wire format. |
314
+ | `PEER_ERROR_HEADER` | `"x-webrun-error"`. |
315
+ | `MessageCodec`, `ByteSource`, `RequestEnvelope`, `ResponseEnvelope`, `DecodedRequest`, `DecodedResponse`, `ResponseCodecOptions` | The codec seam's types. |
316
+
317
+ ## Scripts
318
+
319
+ ```sh
320
+ pnpm test # vitest run
321
+ pnpm run build # rolldown + tsc --emitDeclarationOnly
322
+ pnpm lint # biome check src tests
323
+ ```
47
324
 
48
325
  ## License
49
326
 
@@ -0,0 +1,43 @@
1
+ import type { ByteSource } from "./message.js";
2
+ export declare class ByteStreamError extends Error {
3
+ readonly name = "ByteStreamError";
4
+ }
5
+ export declare function toAsyncIterator(input: ByteSource): AsyncIterator<Uint8Array>;
6
+ /**
7
+ * Discard an iterable we are contractually forbidden from consuming — a body
8
+ * skipped for HEAD/204, or one abandoned because the peer reported an error.
9
+ *
10
+ * The `.next()` is not optional: `.return()` on a generator still in suspended
11
+ * start is a no-op, so the body never runs and its `try/finally` never unwinds.
12
+ * Without it a wrapped ReadableStream or socket is never cancelled.
13
+ */
14
+ export declare function discard(source: ByteSource | undefined): Promise<void>;
15
+ export declare function concatChunks(parts: Uint8Array[], totalLen: number): Uint8Array;
16
+ /**
17
+ * Pull-based reader over a byte source. Holds at most one pending buffer, and
18
+ * hands out `subarray` views rather than copies — a body never passes through
19
+ * an allocation here.
20
+ */
21
+ export declare class ByteReader {
22
+ #private;
23
+ constructor(input: ByteSource);
24
+ /** Bytes already pulled from the source but not yet consumed. */
25
+ bufferedLength(): number;
26
+ peekByte(): Promise<number | undefined>;
27
+ /** Up to `max` bytes. `undefined` means end of stream. */
28
+ readSome(max: number): Promise<Uint8Array | undefined>;
29
+ /**
30
+ * One CRLF-terminated line, without the CRLF. A bare LF is rejected: real
31
+ * peers always send CRLF, and tolerating a bare LF is precisely the lenience
32
+ * that lets request smuggling through a proxy pair.
33
+ *
34
+ * The `maxBytes` bound is best-effort: it only rejects a line if the check
35
+ * happens to run before the line is fully buffered. A line already sitting in
36
+ * the buffer bypasses the check. Callers needing a hard per-line limit or
37
+ * aggregate bounds must keep their own running total.
38
+ */
39
+ readLine(maxBytes: number): Promise<Uint8Array>;
40
+ /** Everything not yet consumed, lazily. */
41
+ rest(): AsyncGenerator<Uint8Array>;
42
+ }
43
+ //# sourceMappingURL=bytes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bytes.d.ts","sourceRoot":"","sources":["../src/bytes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAM/C,qBAAa,eAAgB,SAAQ,KAAK;IACxC,SAAkB,IAAI,qBAAqB;CAC5C;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,GAAG,aAAa,CAAC,UAAU,CAAC,CAS5E;AAED;;;;;;;GAOG;AACH,wBAAsB,OAAO,CAAC,MAAM,EAAE,UAAU,GAAG,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAS3E;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAG,UAAU,CAc9E;AAED;;;;GAIG;AACH,qBAAa,UAAU;;IAKrB,YAAY,KAAK,EAAE,UAAU,EAE5B;IAED,iEAAiE;IACjE,cAAc,IAAI,MAAM,CAEvB;IAqBK,QAAQ,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAK5C;IAED,0DAA0D;IACpD,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAQ3D;IAED;;;;;;;;;OASG;IACG,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAoBpD;IAED,2CAA2C;IACpC,IAAI,IAAI,cAAc,CAAC,UAAU,CAAC,CAUxC;CACF"}
@@ -0,0 +1,7 @@
1
+ import type { MessageCodec } from "./message.js";
2
+ /**
3
+ * Writes HTTP/1.1; accepts HTTP/1.1 or the legacy JSON envelope. Used whenever
4
+ * no `codec` option is supplied.
5
+ */
6
+ export declare const defaultCodec: MessageCodec;
7
+ //# sourceMappingURL=codec-default.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codec-default.d.ts","sourceRoot":"","sources":["../src/codec-default.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAGjD;;;GAGG;AACH,eAAO,MAAM,YAAY,EAAE,YAGzB,CAAC"}
@@ -1,4 +1,5 @@
1
1
  import type { Serve } from "@statewalker/webrun-streams";
2
+ import type { MessageCodec } from "./message.js";
2
3
  /**
3
4
  * Structural alias of `SiteHandler` from `@statewalker/webrun-site-builder`.
4
5
  * Kept local so this package doesn't depend on `webrun-site-builder`.
@@ -27,6 +28,8 @@ export type SiteHandler = (request: Request) => Promise<Response>;
27
28
  export declare class DuplexSiteBuilder {
28
29
  #private;
29
30
  setHandler(handler: SiteHandler): this;
31
+ /** Pin the wire format. Defaults to HTTP/1.1 with legacy acceptance. */
32
+ setCodec(codec: MessageCodec): this;
30
33
  start<P>(serve: Serve<P>, params: P): Promise<() => Promise<void>>;
31
34
  }
32
35
  //# sourceMappingURL=duplex-site-builder.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"duplex-site-builder.d.ts","sourceRoot":"","sources":["../src/duplex-site-builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,6BAA6B,CAAC;AAGzD;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;AAElE;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,iBAAiB;;IAG5B,UAAU,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI;IAKhC,KAAK,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAUzE"}
1
+ {"version":3,"file":"duplex-site-builder.d.ts","sourceRoot":"","sources":["../src/duplex-site-builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,6BAA6B,CAAC;AAEzD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEjD;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;AAElE;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,iBAAiB;;IAI5B,UAAU,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,CAGrC;IAED,wEAAwE;IACxE,QAAQ,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,CAGlC;IAEK,KAAK,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,CASvE;CACF"}
@@ -1,13 +1,5 @@
1
- export type RequestEnvelope = {
2
- url: string;
3
- method: string;
4
- headers: [string, string][];
5
- };
6
- export type ResponseEnvelope = {
7
- status: number;
8
- statusText: string;
9
- headers: [string, string][];
10
- };
1
+ import type { MessageCodec } from "./message.js";
2
+ export type { RequestEnvelope, ResponseEnvelope } from "./message.js";
11
3
  /**
12
4
  * Encode an HTTP envelope plus optional body as one continuous byte stream:
13
5
  *
@@ -27,4 +19,12 @@ export declare function decodeMessage<E>(input: AsyncIterable<Uint8Array> | Iter
27
19
  envelope: E;
28
20
  body: AsyncIterable<Uint8Array>;
29
21
  }>;
22
+ /**
23
+ * The original wire format — `<JSON.stringify(envelope)>\n<body bytes…>` —
24
+ * expressed as a `MessageCodec`. Direction-agnostic: requests and responses
25
+ * serialise identically.
26
+ *
27
+ * Retained so a peer pair can be upgraded in either order; see ADR-0006.
28
+ */
29
+ export declare const jsonEnvelopeCodec: MessageCodec;
30
30
  //# sourceMappingURL=envelope.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"envelope.d.ts","sourceRoot":"","sources":["../src/envelope.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GAAG;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;CAC7B,CAAC;AAMF;;;;;;;;;GASG;AACH,wBAAuB,aAAa,CAAC,CAAC,EACpC,QAAQ,EAAE,CAAC,EACX,IAAI,CAAC,EAAE,aAAa,CAAC,UAAU,CAAC,GAAG,QAAQ,CAAC,UAAU,CAAC,GACtD,cAAc,CAAC,UAAU,CAAC,CAM5B;AAED;;;GAGG;AACH,wBAAsB,aAAa,CAAC,CAAC,EACnC,KAAK,EAAE,aAAa,CAAC,UAAU,CAAC,GAAG,QAAQ,CAAC,UAAU,CAAC,GACtD,OAAO,CAAC;IAAE,QAAQ,EAAE,CAAC,CAAC;IAAC,IAAI,EAAE,aAAa,CAAC,UAAU,CAAC,CAAA;CAAE,CAAC,CAgD3D"}
1
+ {"version":3,"file":"envelope.d.ts","sourceRoot":"","sources":["../src/envelope.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAIV,YAAY,EAGb,MAAM,cAAc,CAAC;AAEtB,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAStE;;;;;;;;;GASG;AACH,wBAAuB,aAAa,CAAC,CAAC,EACpC,QAAQ,EAAE,CAAC,EACX,IAAI,CAAC,EAAE,aAAa,CAAC,UAAU,CAAC,GAAG,QAAQ,CAAC,UAAU,CAAC,GACtD,cAAc,CAAC,UAAU,CAAC,CAM5B;AAED;;;GAGG;AACH,wBAAsB,aAAa,CAAC,CAAC,EACnC,KAAK,EAAE,aAAa,CAAC,UAAU,CAAC,GAAG,QAAQ,CAAC,UAAU,CAAC,GACtD,OAAO,CAAC;IAAE,QAAQ,EAAE,CAAC,CAAC;IAAC,IAAI,EAAE,aAAa,CAAC,UAAU,CAAC,CAAA;CAAE,CAAC,CAyD3D;AAID;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,EAAE,YAS/B,CAAC"}
package/dist/fetch.d.ts CHANGED
@@ -1,13 +1,14 @@
1
1
  import type { Duplex } from "@statewalker/webrun-streams";
2
+ import { type HttpDataOptions } from "./http-data.js";
2
3
  /**
3
4
  * Run a `Request` through a `Duplex` call and reconstruct the `Response` on
4
5
  * the other side. The request's `signal` is plumbed into the body iteration —
5
6
  * abort terminates the underlying call.
6
7
  */
7
- export declare function fetchOverDuplex(call: Duplex, request: Request): Promise<Response>;
8
+ export declare function fetchOverDuplex(call: Duplex, request: Request, options?: HttpDataOptions): Promise<Response>;
8
9
  /**
9
10
  * Wrap a `(Request) => Promise<Response>` handler as a `Duplex` so it can be
10
11
  * registered with any `webrun-streams-*` adapter's `serve`.
11
12
  */
12
- export declare function serveFetchOverDuplex(handler: (request: Request) => Promise<Response>): Duplex;
13
+ export declare function serveFetchOverDuplex(handler: (request: Request) => Promise<Response>, options?: HttpDataOptions): Duplex;
13
14
  //# sourceMappingURL=fetch.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../src/fetch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,6BAA6B,CAAC;AAgE1D;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAcvF;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,GAAG,MAAM,CAsB7F"}
1
+ {"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../src/fetch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,6BAA6B,CAAC;AAE1D,OAAO,EAAE,KAAK,eAAe,EAAwB,MAAM,gBAAgB,CAAC;AAsF5E;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,OAAO,EAChB,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,QAAQ,CAAC,CA0EnB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,EAChD,OAAO,GAAE,eAAoB,GAC5B,MAAM,CAiDR"}
@@ -1,12 +1,18 @@
1
1
  import type { Duplex } from "@statewalker/webrun-streams";
2
- import { type RequestEnvelope, type ResponseEnvelope } from "./envelope.js";
2
+ import type { ByteSource, MessageCodec, RequestEnvelope, ResponseEnvelope } from "./message.js";
3
+ /** Carries the serialized JS error between two webrun peers. */
4
+ export declare const PEER_ERROR_HEADER = "x-webrun-error";
5
+ export type HttpDataOptions = {
6
+ /** Wire format. Defaults to `defaultCodec`: writes HTTP/1.1, accepts either. */
7
+ codec?: MessageCodec;
8
+ };
3
9
  export interface HttpFetchResult {
4
10
  envelope: ResponseEnvelope;
5
11
  body: AsyncIterable<Uint8Array>;
6
12
  }
7
13
  export interface HttpDataHandlerResult {
8
14
  envelope: ResponseEnvelope;
9
- body?: AsyncIterable<Uint8Array> | Iterable<Uint8Array>;
15
+ body?: ByteSource;
10
16
  }
11
17
  /**
12
18
  * Low-level HTTP-over-Duplex handler. Takes the request envelope plus body
@@ -25,12 +31,10 @@ export type HttpDataHandler = (env: RequestEnvelope, body: AsyncIterable<Uint8Ar
25
31
  * The call is one logical Duplex invocation; multiplexing of concurrent
26
32
  * calls is the adapter's concern (native or `emulateMux`).
27
33
  */
28
- export declare function httpFetch(call: Duplex, env: RequestEnvelope, body?: AsyncIterable<Uint8Array> | Iterable<Uint8Array>): Promise<HttpFetchResult>;
34
+ export declare function httpFetch(call: Duplex, env: RequestEnvelope, body?: ByteSource, options?: HttpDataOptions): Promise<HttpFetchResult>;
29
35
  /**
30
36
  * Wrap an HTTP handler as a `Duplex` so it can be registered with any
31
- * `webrun-streams-*` adapter's `serve`. The duplex `split`s the input to
32
- * recover envelope + body, dispatches to the handler, and emits the response
33
- * via `encodeMessage`.
37
+ * `webrun-streams-*` adapter's `serve`.
34
38
  */
35
- export declare function httpServe(handler: HttpDataHandler): Duplex;
39
+ export declare function httpServe(handler: HttpDataHandler, options?: HttpDataOptions): Duplex;
36
40
  //# sourceMappingURL=http-data.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"http-data.d.ts","sourceRoot":"","sources":["../src/http-data.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,6BAA6B,CAAC;AAC1D,OAAO,EAGL,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACtB,MAAM,eAAe,CAAC;AAEvB,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,IAAI,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,IAAI,CAAC,EAAE,aAAa,CAAC,UAAU,CAAC,GAAG,QAAQ,CAAC,UAAU,CAAC,CAAC;CACzD;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,eAAe,GAAG,CAC5B,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,aAAa,CAAC,UAAU,CAAC,KAC5B,OAAO,CAAC,qBAAqB,CAAC,CAAC;AAEpC;;;;;;;GAOG;AACH,wBAAsB,SAAS,CAC7B,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,eAAe,EACpB,IAAI,CAAC,EAAE,aAAa,CAAC,UAAU,CAAC,GAAG,QAAQ,CAAC,UAAU,CAAC,GACtD,OAAO,CAAC,eAAe,CAAC,CAG1B;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,eAAe,GAAG,MAAM,CAM1D"}
1
+ {"version":3,"file":"http-data.d.ts","sourceRoot":"","sources":["../src/http-data.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,6BAA6B,CAAC;AAK1D,OAAO,KAAK,EACV,UAAU,EAEV,YAAY,EACZ,eAAe,EACf,gBAAgB,EACjB,MAAM,cAAc,CAAC;AAEtB,gEAAgE;AAChE,eAAO,MAAM,iBAAiB,mBAAmB,CAAC;AAElD,MAAM,MAAM,eAAe,GAAG;IAC5B,gFAAgF;IAChF,KAAK,CAAC,EAAE,YAAY,CAAC;CACtB,CAAC;AAEF,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,IAAI,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,IAAI,CAAC,EAAE,UAAU,CAAC;CACnB;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,eAAe,GAAG,CAC5B,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,aAAa,CAAC,UAAU,CAAC,KAC5B,OAAO,CAAC,qBAAqB,CAAC,CAAC;AAgGpC;;;;;;;GAOG;AACH,wBAAsB,SAAS,CAC7B,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,eAAe,EACpB,IAAI,CAAC,EAAE,UAAU,EACjB,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,eAAe,CAAC,CAM1B;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,eAAe,EAAE,OAAO,GAAE,eAAoB,GAAG,MAAM,CAwCzF"}
@@ -1 +1 @@
1
- {"version":3,"file":"http-error.d.ts","sourceRoot":"","sources":["../src/http-error.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,qBAAa,SAAU,SAAQ,KAAK;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;gBAER,OAAO,GAAE,gBAAqB;IAM1C,kBAAkB,CAAC,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAIlF,MAAM,IAAI;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE;IAQnE,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,SAAS;IAM3C,MAAM,CAAC,qBAAqB,CAAC,OAAO,GAAE,gBAAqB,GAAG,SAAS;IAQvE,MAAM,CAAC,cAAc,CAAC,OAAO,GAAE,gBAAqB,GAAG,SAAS;IAQhE,MAAM,CAAC,iBAAiB,CAAC,OAAO,GAAE,gBAAqB,GAAG,SAAS;IAQnE,MAAM,CAAC,kBAAkB,CAAC,OAAO,GAAE,gBAAqB,GAAG,SAAS;CAOrE"}
1
+ {"version":3,"file":"http-error.d.ts","sourceRoot":"","sources":["../src/http-error.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,qBAAa,SAAU,SAAQ,KAAK;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,YAAY,OAAO,GAAE,gBAAqB,EAIzC;IAED,kBAAkB,CAAC,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEjF;IAED,MAAM,IAAI;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAMlE;IAED,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,SAAS,CAI1C;IAED,MAAM,CAAC,qBAAqB,CAAC,OAAO,GAAE,gBAAqB,GAAG,SAAS,CAMtE;IAED,MAAM,CAAC,cAAc,CAAC,OAAO,GAAE,gBAAqB,GAAG,SAAS,CAM/D;IAED,MAAM,CAAC,iBAAiB,CAAC,OAAO,GAAE,gBAAqB,GAAG,SAAS,CAMlE;IAED,MAAM,CAAC,kBAAkB,CAAC,OAAO,GAAE,gBAAqB,GAAG,SAAS,CAMnE;CACF"}
@@ -19,9 +19,21 @@ export interface SerializedHttpResponse {
19
19
  }
20
20
  export interface SerializedHttpEnvelope<Options> {
21
21
  options: Options;
22
+ /**
23
+ * The body bytes. Must be *productive*: it has to yield or finish on its own,
24
+ * because a body neither stub is allowed to read (a GET/HEAD/OPTIONS request,
25
+ * a null-body-status response) is released with `discard`, and `discard` must
26
+ * await one `.next()` before `.return()` — a bare `.return()` is a no-op on a
27
+ * generator in suspended start and would leak the producer. So a `content`
28
+ * that blocks forever without yielding makes the stub block with it, where an
29
+ * earlier version returned immediately and leaked instead. Every transport in
30
+ * this repo satisfies this; a `content` that waits on a peer that may never
31
+ * send should carry its own timeout.
32
+ */
22
33
  content: AsyncIterable<Uint8Array>;
23
34
  }
24
35
  export type HttpHandler = (request: Request) => Response | Promise<Response>;
36
+ export declare const NULL_BODY_STATUSES: Set<number>;
25
37
  /**
26
38
  * Returns an HTTP handler that serializes a Request, hands the envelope to
27
39
  * `send` for transport, and deserializes the reply into a Response. Used on
@@ -1 +1 @@
1
- {"version":3,"file":"http-stubs.d.ts","sourceRoot":"","sources":["../src/http-stubs.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,qBAAqB;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACjC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,sBAAsB,CAAC,OAAO;IAC7C,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;CACpC;AAED,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;AAmB7E;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,CACJ,QAAQ,EAAE,sBAAsB,CAAC,qBAAqB,CAAC,KACpD,OAAO,CAAC,sBAAsB,CAAC,sBAAsB,CAAC,GAAG,SAAS,CAAC,GACvE,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,OAAO,CAAC,QAAQ,CAAC,CAmC5D;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,WAAW,GACnB,CACD,QAAQ,EACJ,sBAAsB,CAAC,qBAAqB,CAAC,GAC7C,OAAO,CAAC,sBAAsB,CAAC,qBAAqB,CAAC,CAAC,KACvD,OAAO,CAAC,sBAAsB,CAAC,sBAAsB,CAAC,CAAC,CAyC3D"}
1
+ {"version":3,"file":"http-stubs.d.ts","sourceRoot":"","sources":["../src/http-stubs.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,qBAAqB;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACjC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,sBAAsB,CAAC,OAAO;IAC7C,OAAO,EAAE,OAAO,CAAC;IACjB;;;;;;;;;;OAUG;IACH,OAAO,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;CACpC;AAED,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;AAK7E,eAAO,MAAM,kBAAkB,aAAqC,CAAC;AAsBrE;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,CACJ,QAAQ,EAAE,sBAAsB,CAAC,qBAAqB,CAAC,KACpD,OAAO,CAAC,sBAAsB,CAAC,sBAAsB,CAAC,GAAG,SAAS,CAAC,GACvE,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,OAAO,CAAC,QAAQ,CAAC,CAmD5D;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,WAAW,GACnB,CACD,QAAQ,EACJ,sBAAsB,CAAC,qBAAqB,CAAC,GAC7C,OAAO,CAAC,sBAAsB,CAAC,qBAAqB,CAAC,CAAC,KACvD,OAAO,CAAC,sBAAsB,CAAC,sBAAsB,CAAC,CAAC,CA0D3D"}
@@ -0,0 +1,18 @@
1
+ import type { ByteReader } from "../bytes.js";
2
+ import type { ByteSource } from "../message.js";
3
+ /**
4
+ * Chunk sizes are written in HEXADECIMAL — a 21-byte chunk is `15`. Writing
5
+ * them in decimal is the defect note 15 found in @libp2p/http, and it is the
6
+ * one that cannot be caught downstream: decimal digits are also valid hex, so
7
+ * a conforming parser silently reads the wrong length.
8
+ *
9
+ * Zero-length source chunks are skipped; a zero-sized chunk on the wire is the
10
+ * body terminator.
11
+ */
12
+ export declare function encodeChunked(body: ByteSource): AsyncGenerator<Uint8Array>;
13
+ /**
14
+ * Decode a chunked body, yielding data chunks as they arrive. Nothing is
15
+ * accumulated: a chunk larger than the transport's frame is yielded in pieces.
16
+ */
17
+ export declare function decodeChunked(reader: ByteReader, maxLineBytes: number): AsyncGenerator<Uint8Array>;
18
+ //# sourceMappingURL=chunked.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunked.d.ts","sourceRoot":"","sources":["../../src/http1/chunked.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAQhD;;;;;;;;GAQG;AACH,wBAAuB,aAAa,CAAC,IAAI,EAAE,UAAU,GAAG,cAAc,CAAC,UAAU,CAAC,CAQjF;AAED;;;GAGG;AACH,wBAAuB,aAAa,CAClC,MAAM,EAAE,UAAU,EAClB,YAAY,EAAE,MAAM,GACnB,cAAc,CAAC,UAAU,CAAC,CAoD5B"}
@@ -0,0 +1,5 @@
1
+ import type { ByteSource, DecodedRequest, DecodedResponse } from "../message.js";
2
+ import type { ResolvedHttpCodecOptions } from "./encode.js";
3
+ export declare function decodeRequest(input: ByteSource, opts: ResolvedHttpCodecOptions): Promise<DecodedRequest>;
4
+ export declare function decodeResponse(input: ByteSource, opts: ResolvedHttpCodecOptions, method: string): Promise<DecodedResponse>;
5
+ //# sourceMappingURL=decode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decode.d.ts","sourceRoot":"","sources":["../../src/http1/decode.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAEjF,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAoG5D,wBAAsB,aAAa,CACjC,KAAK,EAAE,UAAU,EACjB,IAAI,EAAE,wBAAwB,GAC7B,OAAO,CAAC,cAAc,CAAC,CA4DzB;AAED,wBAAsB,cAAc,CAClC,KAAK,EAAE,UAAU,EACjB,IAAI,EAAE,wBAAwB,EAC9B,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,eAAe,CAAC,CAmC1B"}
@@ -0,0 +1,20 @@
1
+ import type { ByteSource, RequestEnvelope, ResponseEnvelope } from "../message.js";
2
+ export type ResolvedHttpCodecOptions = {
3
+ scheme: "http" | "https";
4
+ host: string;
5
+ maxHeaderBytes: number;
6
+ };
7
+ /**
8
+ * Split a URL into an origin-form request target and an authority *without*
9
+ * going through `new URL()`. `URL` normalises percent-encoding and would
10
+ * re-serialise the target — which is the exact class of defect note 15 found
11
+ * in @libp2p/http, where rebuilding the request line silently dropped
12
+ * `url.search`. Here the target is a verbatim slice of the caller's string.
13
+ */
14
+ export declare function splitTarget(url: string, opts: ResolvedHttpCodecOptions): {
15
+ target: string;
16
+ authority: string;
17
+ };
18
+ export declare function encodeRequest(env: RequestEnvelope, body: ByteSource | undefined, opts: ResolvedHttpCodecOptions): AsyncGenerator<Uint8Array>;
19
+ export declare function encodeResponse(env: ResponseEnvelope, body: ByteSource | undefined, _opts: ResolvedHttpCodecOptions, requestMethod?: string): AsyncGenerator<Uint8Array>;
20
+ //# sourceMappingURL=encode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encode.d.ts","sourceRoot":"","sources":["../../src/http1/encode.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAgBnF,MAAM,MAAM,wBAAwB,GAAG;IACrC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,MAAM,CAAC;CACxB,CAAC;AAMF;;;;;;GAMG;AACH,wBAAgB,WAAW,CACzB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,wBAAwB,GAC7B;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CA6BvC;AA0BD,wBAAuB,aAAa,CAClC,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,UAAU,GAAG,SAAS,EAC5B,IAAI,EAAE,wBAAwB,GAC7B,cAAc,CAAC,UAAU,CAAC,CA+B5B;AAED,wBAAuB,cAAc,CACnC,GAAG,EAAE,gBAAgB,EACrB,IAAI,EAAE,UAAU,GAAG,SAAS,EAK5B,KAAK,EAAE,wBAAwB,EAC/B,aAAa,CAAC,EAAE,MAAM,GACrB,cAAc,CAAC,UAAU,CAAC,CA0C5B"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Raised for any byte sequence this codec refuses to interpret. Every case is
3
+ * a refusal to guess: HTTP/1.1 parsers that guess are how request smuggling
4
+ * works.
5
+ */
6
+ export declare class HttpParseError extends Error {
7
+ readonly name = "HttpParseError";
8
+ }
9
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/http1/errors.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,qBAAa,cAAe,SAAQ,KAAK;IACvC,SAAkB,IAAI,oBAAoB;CAC3C"}