@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.
- package/README.md +288 -11
- package/dist/bytes.d.ts +43 -0
- package/dist/bytes.d.ts.map +1 -0
- package/dist/codec-default.d.ts +7 -0
- package/dist/codec-default.d.ts.map +1 -0
- package/dist/duplex-site-builder.d.ts +3 -0
- package/dist/duplex-site-builder.d.ts.map +1 -1
- package/dist/envelope.d.ts +10 -10
- package/dist/envelope.d.ts.map +1 -1
- package/dist/fetch.d.ts +3 -2
- package/dist/fetch.d.ts.map +1 -1
- package/dist/http-data.d.ts +11 -7
- package/dist/http-data.d.ts.map +1 -1
- package/dist/http-error.d.ts.map +1 -1
- package/dist/http-stubs.d.ts +12 -0
- package/dist/http-stubs.d.ts.map +1 -1
- package/dist/http1/chunked.d.ts +18 -0
- package/dist/http1/chunked.d.ts.map +1 -0
- package/dist/http1/decode.d.ts +5 -0
- package/dist/http1/decode.d.ts.map +1 -0
- package/dist/http1/encode.d.ts +20 -0
- package/dist/http1/encode.d.ts.map +1 -0
- package/dist/http1/errors.d.ts +9 -0
- package/dist/http1/errors.d.ts.map +1 -0
- package/dist/http1/headers.d.ts +78 -0
- package/dist/http1/headers.d.ts.map +1 -0
- package/dist/http1/index.d.ts +18 -0
- package/dist/http1/index.d.ts.map +1 -0
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1039 -137
- package/dist/message.d.ts +50 -0
- package/dist/message.d.ts.map +1 -0
- package/dist/request-streams.d.ts +52 -0
- package/dist/request-streams.d.ts.map +1 -0
- package/dist/sniff.d.ts +18 -0
- package/dist/sniff.d.ts.map +1 -0
- package/package.json +8 -6
- package/src/bytes.ts +157 -0
- package/src/codec-default.ts +13 -0
- package/src/duplex-site-builder.ts +9 -1
- package/src/envelope.ts +43 -34
- package/src/fetch.ts +130 -12
- package/src/http-data.ts +160 -17
- package/src/http-stubs.ts +72 -18
- package/src/http1/chunked.ts +89 -0
- package/src/http1/decode.ts +208 -0
- package/src/http1/encode.ts +181 -0
- package/src/http1/errors.ts +8 -0
- package/src/http1/headers.ts +263 -0
- package/src/http1/index.ts +40 -0
- package/src/index.ts +18 -0
- package/src/message.ts +62 -0
- package/src/request-streams.ts +75 -0
- 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.
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
`
|
|
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
|
-
|
|
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
|
|
package/dist/bytes.d.ts
ADDED
|
@@ -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;
|
|
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"}
|
package/dist/envelope.d.ts
CHANGED
|
@@ -1,13 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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
|
package/dist/envelope.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"envelope.d.ts","sourceRoot":"","sources":["../src/envelope.ts"],"names":[],"mappings":"
|
|
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
|
|
13
|
+
export declare function serveFetchOverDuplex(handler: (request: Request) => Promise<Response>, options?: HttpDataOptions): Duplex;
|
|
13
14
|
//# sourceMappingURL=fetch.d.ts.map
|
package/dist/fetch.d.ts.map
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/http-data.d.ts
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import type { Duplex } from "@statewalker/webrun-streams";
|
|
2
|
-
import {
|
|
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?:
|
|
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?:
|
|
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`.
|
|
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
|
package/dist/http-data.d.ts.map
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/http-error.d.ts.map
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/http-stubs.d.ts
CHANGED
|
@@ -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
|
package/dist/http-stubs.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http-stubs.d.ts","sourceRoot":"","sources":["../src/http-stubs.ts"],"names":[],"mappings":"
|
|
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"}
|