@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/src/sniff.ts ADDED
@@ -0,0 +1,68 @@
1
+ import { ByteReader } from "./bytes.js";
2
+ import { HttpParseError } from "./http1/errors.js";
3
+ import type { ByteSource, MessageCodec } from "./message.js";
4
+
5
+ export type SniffingCodecOptions = {
6
+ /** Codec used for everything this peer writes. */
7
+ write: MessageCodec;
8
+ /** Codecs this peer accepts on read, tried in order. */
9
+ accept: MessageCodec[];
10
+ };
11
+
12
+ /**
13
+ * Dispatches on byte 0. The formats are self-identifying — a JSON envelope
14
+ * always begins `{`, which is not a token character and so can never begin an
15
+ * HTTP start-line — so no negotiation handshake, magic prefix, or version byte
16
+ * is needed.
17
+ *
18
+ * This is what makes a mixed-version peer pair safe: readers accept either
19
+ * format, so the two ends can be upgraded in any order.
20
+ */
21
+ export function newSniffingCodec(options: SniffingCodecOptions): MessageCodec {
22
+ const { write, accept } = options;
23
+
24
+ async function pick(input: ByteSource): Promise<{ codec: MessageCodec; input: ByteSource }> {
25
+ const reader = new ByteReader(input);
26
+ const first = await reader.peekByte();
27
+ if (first === undefined) {
28
+ throw new HttpParseError("sniff: stream ended before any bytes arrived");
29
+ }
30
+ const codec = accept.find((c) => c.sniff(first));
31
+ if (!codec) {
32
+ throw new HttpParseError(
33
+ `sniff: no accepted codec recognises a message starting with byte 0x${first
34
+ .toString(16)
35
+ .padStart(2, "0")}`,
36
+ );
37
+ }
38
+ return { codec, input: reader.rest() };
39
+ }
40
+
41
+ return {
42
+ name: `sniff(write=${write.name}; accept=${accept.map((c) => c.name).join(",")})`,
43
+ sniff: (byte) => accept.some((c) => c.sniff(byte)),
44
+ encodeRequest: (env, body) => write.encodeRequest(env, body),
45
+ encodeResponse: (env, body, o) => write.encodeResponse(env, body, o),
46
+ decodeRequest: async (input) => {
47
+ const picked = await pick(input);
48
+ try {
49
+ const decoded = await picked.codec.decodeRequest(picked.input);
50
+ return { ...decoded, codec: picked.codec };
51
+ } catch (error) {
52
+ // Sniffing already told us which format the peer is speaking. Carry
53
+ // that on the error so a refusal can be answered in the peer's own
54
+ // format — otherwise a peer pinned to the legacy envelope receives its
55
+ // error response as HTTP/1.1, the same mismatch reply-in-kind avoids
56
+ // on the success path.
57
+ if (error !== null && typeof error === "object") {
58
+ (error as { codec?: MessageCodec }).codec = picked.codec;
59
+ }
60
+ throw error;
61
+ }
62
+ },
63
+ decodeResponse: async (input, o) => {
64
+ const picked = await pick(input);
65
+ return picked.codec.decodeResponse(picked.input, o);
66
+ },
67
+ };
68
+ }