@sproutboat/toolchain 0.9.0 → 0.10.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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @sproutboat/toolchain
2
2
 
3
+ ## 0.10.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 6676ead: `env.BUCKET.put(key, request.body)`, `env.KV.put(key, request.body)` and `fetch(url, { body: request.body })` store and send the request body again. Since 0.19.0, `body` is a `ReadableStream`, and these took its string form. Incoming request bodies are also read as the bytes they are, so `arrayBuffer()`, `blob()`, `body` and KV `put` no longer UTF-8 encode a non-ASCII upload twice. R2 `put` accepts bytes as well.
8
+
9
+ ## 0.10.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 4274bef: `Response.body` and `Request.body` are `ReadableStream`s for every body type, as in the Fetch spec and workerd. Before, they held the value given (a string, bytes or a `Blob`). `res.body.getReader()`, `res.body.pipeThrough(...)` and `new Response(other.body)` now work on any response, including the incoming request. The value as given stays internal, so responses are still written with a fixed length unless the body really is a stream. A body whose stream has been locked can no longer be read with `text()` and the other readers, which throw a `TypeError`.
14
+
3
15
  ## 0.9.0
4
16
 
5
17
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sproutboat/toolchain",
3
- "version": "0.9.0",
3
+ "version": "0.10.1",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "repository": {
@@ -65,7 +65,25 @@ export const REQUEST_CLASS_SOURCE = String.raw`class Request {
65
65
  static _consume(message) {
66
66
  if (message._bodyUsed === true) throw new TypeError('Body has already been used');
67
67
  message._bodyUsed = true;
68
- return message.body;
68
+ // A stream the body getter handed out is what the reader reads: a locked one throws.
69
+ return message._bodyStream != null ? message._bodyStream : message._bodyInit;
70
+ }
71
+
72
+ // body is a ReadableStream, as in Fetch: a stream body itself, else a stream made once over
73
+ // the bytes. The value as given stays in _bodyInit, which the native writer, streaming and
74
+ // the prelude read; assigning body (the native ingress does) sets it.
75
+ static _bodyAsStream(message) {
76
+ const init = message._bodyInit;
77
+ if (init == null) return null;
78
+ if (Request._isStream(init)) return init;
79
+ if (message._bodyStream == null) {
80
+ const bytes = Request._messageBytes(message, init);
81
+ message._bodyStream = new globalThis.ReadableStream({ start(c) { if (bytes.length) c.enqueue(bytes); c.close(); } });
82
+ // The value the stream was made from, for the bindings that take a body at once
83
+ // (R2 and KV put, outbound fetch): put(key, request.body) is the Workers idiom.
84
+ message._bodyStream._sbBody = message._bodyLatin1 === true ? bytes : init;
85
+ }
86
+ return message._bodyStream;
69
87
  }
70
88
 
71
89
  static _bodyText(body) {
@@ -105,7 +123,17 @@ export const REQUEST_CLASS_SOURCE = String.raw`class Request {
105
123
  static _read(message, finish) {
106
124
  const body = Request._consume(message);
107
125
  if (Request._isStream(body)) return Request._streamBytes(body).then(finish);
108
- return finish(Request._bodyBytes(body));
126
+ return finish(Request._messageBytes(message, body));
127
+ }
128
+ // A body the native ingress delivered is its raw bytes, one character each
129
+ // (_bodyLatin1): those bytes, not the string UTF-8 encoded again.
130
+ static _messageBytes(message, body) {
131
+ if (message._bodyLatin1 === true && typeof body == 'string') {
132
+ const out = new Uint8Array(body.length);
133
+ for (let i = 0; i < body.length; i++) out[i] = body.charCodeAt(i) & 255;
134
+ return out;
135
+ }
136
+ return Request._bodyBytes(body);
109
137
  }
110
138
 
111
139
  constructor(input, init = undefined) {
@@ -119,7 +147,7 @@ export const REQUEST_CLASS_SOURCE = String.raw`class Request {
119
147
  url = input.url;
120
148
  method = input.method;
121
149
  headers = input.headers;
122
- body = input.body;
150
+ body = input._bodyStream != null ? input._bodyStream : input._bodyInit;
123
151
  redirect = input.redirect;
124
152
  } else if (input != null && Porffor.type(input) == Porffor.TYPES.object && input.href != null) url = input.href;
125
153
 
@@ -142,11 +170,13 @@ export const REQUEST_CLASS_SOURCE = String.raw`class Request {
142
170
  if (init != null && init.body != null && init.body !== '' && (this.method == 'GET' || this.method == 'HEAD'))
143
171
  throw new TypeError('Request with GET/HEAD method cannot have body.');
144
172
  this.headers = new Headers(headers);
145
- this.body = Request._extractBody(body, this.headers);
173
+ this._bodyInit = Request._extractBody(body, this.headers);
146
174
  if (redirect != null) this._redirect = String(redirect);
147
175
  }
148
176
 
149
177
  get redirect() { return this._redirect ?? 'follow'; }
178
+ get body() { return Request._bodyAsStream(this); }
179
+ set body(value) { this._bodyInit = value; this._bodyStream = null; }
150
180
  get bodyUsed() { return this._bodyUsed === true; }
151
181
 
152
182
  arrayBuffer() { return Request._read(this, (bytes) => bytes.buffer); }
@@ -179,7 +209,7 @@ export const RESPONSE_CLASS_SOURCE = String.raw`class Response {
179
209
  throw new TypeError('Response with null body status cannot have body');
180
210
  if (init?.statusText != null) this._statusText = String(init.statusText);
181
211
  this.headers = new Headers(init?.headers);
182
- this.body = Request._extractBody(body, this.headers);
212
+ this._bodyInit = Request._extractBody(body, this.headers);
183
213
  }
184
214
 
185
215
  static error() {
@@ -192,6 +222,8 @@ export const RESPONSE_CLASS_SOURCE = String.raw`class Response {
192
222
  get statusText() { return this._statusText ?? ''; }
193
223
  get type() { return this._type ?? 'default'; }
194
224
  get ok() { return this.status >= 200 && this.status < 300; }
225
+ get body() { return Request._bodyAsStream(this); }
226
+ set body(value) { this._bodyInit = value; this._bodyStream = null; }
195
227
  get bodyUsed() { return this._bodyUsed === true; }
196
228
 
197
229
  arrayBuffer() { return Request._read(this, (bytes) => bytes.buffer); }
@@ -199,7 +231,7 @@ export const RESPONSE_CLASS_SOURCE = String.raw`class Response {
199
231
  clone() {
200
232
  if (this._bodyUsed === true) throw new TypeError('Body has already been used');
201
233
  const copy = new Response(null, { status: this.status == 0 ? 200 : this.status, statusText: this._statusText, headers: this.headers });
202
- copy.body = this.body;
234
+ copy._bodyInit = this._bodyInit;
203
235
  copy.status = this.status;
204
236
  if (this._type != null) copy._type = this._type;
205
237
  return copy;
@@ -223,7 +255,7 @@ export const RESPONSE_BYTES_ANCHOR =
223
255
  export const RESPONSE_BYTES_INJECT =
224
256
  " // sproutboat: byte bodies go out as their bytes. A Latin-1 string, one character per byte,\n" +
225
257
  " // built in windows (concatenating one long string is quadratic), sent raw by the writer.\n" +
226
- " let body = response.body;\n" +
258
+ " let body = response._bodyInit !== undefined ? response._bodyInit : response.body;\n" +
227
259
  " let headersEntries = response.headers._entries;\n" +
228
260
  " if (body instanceof ArrayBuffer || ArrayBuffer.isView(body)) {\n" +
229
261
  " const view = body instanceof ArrayBuffer ? new Uint8Array(body) : new Uint8Array(body.buffer, body.byteOffset, body.byteLength);\n" +
@@ -21,7 +21,7 @@ export const PATCH_SOURCES = [
21
21
  * patch.ts as the module patch.ts is everywhere else. Kept current by fingerprint.test.ts
22
22
  * (UPDATE_FINGERPRINT=1 bun test packages/toolchain/src/fingerprint.test.ts).
23
23
  */
24
- export const RELEASED_FINGERPRINT = "5a243546eb20";
24
+ export const RELEASED_FINGERPRINT = "9a717433bb3b";
25
25
 
26
26
  /** The fingerprint of the sources in dir, or null where they are not on disk. */
27
27
  export function sourceFingerprint(dir: string): string | null {