@sproutboat/runtime 0.13.2 → 0.13.3

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,11 @@
1
1
  # @sproutboat/runtime
2
2
 
3
+ ## 0.13.3
4
+
5
+ ### Patch Changes
6
+
7
+ - 733bd9e: Outbound `fetch()` and service-binding responses keep binary bodies intact (baronunread/sproutboat#232). `arrayBuffer()` and `bytes()` on a fetched response used to go through Porffor's `text()`, which the runtime overrides to decode UTF-8, so binary bodies collapsed; they now read the raw bytes. Through the broker, `fetch` and `service.fetch` sent over a v1 frame now get the upstream body as raw bytes in the frame's binary section instead of `TextDecoder` output. v0 frames, which already-deployed sprouts use, are unchanged, and a new sprout talking to an older broker falls back to the previous text behaviour.
8
+
3
9
  ## 0.13.2
4
10
 
5
11
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sproutboat/runtime",
3
- "version": "0.13.2",
3
+ "version": "0.13.3",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "repository": {
@@ -655,6 +655,12 @@ function __sbRawBodyResponse(body, init) {
655
655
  return decoded;
656
656
  };
657
657
  resp.json = () => JSON.parse(resp.text());
658
+ // #232 — Porffor's arrayBuffer() goes through text(), which decodes UTF-8
659
+ // above and so collapsed a binary body's multi-byte runs. The body already
660
+ // is the bytes, one char per byte. blob() stays text-based: Porffor's Blob
661
+ // only holds text and has no arrayBuffer() to read bytes back out of.
662
+ resp.arrayBuffer = () => __sbBufFrom(body);
663
+ resp.bytes = () => new Uint8Array(__sbBufFrom(body));
658
664
  }
659
665
  return resp;
660
666
  }
@@ -1584,7 +1590,7 @@ globalThis.__sbInstallBindings = function (target, bindings) {
1584
1590
  if (__sbIsFn(opts.headers.forEach)) opts.headers.forEach((v, k) => headers.push([k, v]));
1585
1591
  else for (const k in opts.headers) headers.push([k, opts.headers[k]]);
1586
1592
  }
1587
- const r = __sbRpc("service.fetch", {
1593
+ const r = globalThis.__sbFetchUpstream("service.fetch", {
1588
1594
  binding,
1589
1595
  url,
1590
1596
  method: opts.method || "GET",
@@ -1611,7 +1617,7 @@ globalThis.__sbInstallBindings = function (target, bindings) {
1611
1617
  if (__sbIsFn(opts.headers.forEach)) opts.headers.forEach((v, k) => headers.push([k, v]));
1612
1618
  else for (const k in opts.headers) headers.push([k, opts.headers[k]]);
1613
1619
  }
1614
- const r = __sbRpc("fetch", {
1620
+ const r = globalThis.__sbFetchUpstream("fetch", {
1615
1621
  url,
1616
1622
  method: opts.method || "GET",
1617
1623
  headers,
@@ -12,7 +12,8 @@ import { readFileSync } from "node:fs";
12
12
  import { fileURLToPath } from "node:url";
13
13
 
14
14
  const src = readFileSync(fileURLToPath(new URL("./native-fetch-prelude.js", import.meta.url)), "utf8");
15
- const utf8Start = src.indexOf("function __sbFromUtf8(");
15
+ // From __sbBufFrom (raw-body arrayBuffer, #232) through __sbFromUtf8.
16
+ const utf8Start = src.indexOf("function __sbBufFrom(");
16
17
  const rawStart = src.indexOf("function __sbRawBodyResponse(");
17
18
  const utf8End = rawStart;
18
19
  const rawEnd = src.indexOf("function __sbEqCt(");
@@ -391,6 +391,22 @@ globalThis.__sbR2MultipartPut = function (bucket, key, uploadId, partNumber, bod
391
391
  return reply;
392
392
  };
393
393
 
394
+ /**
395
+ * #232 — outbound / service fetch over a v1 frame, so the body arrives as the
396
+ * upstream's raw bytes (one char per byte) instead of UTF-8-decoded text. A
397
+ * broker that predates this answers v1 fetch with the body still in its JSON:
398
+ * re-encode that to bytes so it matches today's behaviour rather than breaking.
399
+ */
400
+ globalThis.__sbFetchUpstream = function (op, msg) {
401
+ const req = { v: 1, id: ++__sbReqId, token: __sbEnv("SB_BROKER_TOKEN"), op };
402
+ for (const k in msg) req[k] = msg[k];
403
+ const reply = JSON.parse(__sbCallBin(JSON.stringify(req), ""));
404
+ if (reply.ok === false) throw new Error("sproutboat " + op + ": " + (reply.error || "failed"));
405
+ const bytes = __sbTakeBin();
406
+ reply.body = reply.body != null ? __sbToBytes(reply.body) : bytes;
407
+ return reply;
408
+ };
409
+
394
410
  /** Static asset metadata stays JSON; the response body uses the v1 byte tail. */
395
411
  globalThis.__sbAssetsGet = function (path) {
396
412
  const token = __sbEnv("SB_BROKER_TOKEN");
@@ -2603,6 +2603,11 @@ globalThis.__sbR2MultipartPut = function (bucket, key, uploadId, partNumber, bod
2603
2603
  return { ok: true, part: { partNumber, etag: reply.etag } };
2604
2604
  };
2605
2605
 
2606
+ // #232 — the C HTTP client already hands back raw bytes, one char per byte.
2607
+ globalThis.__sbFetchUpstream = function (op, msg) {
2608
+ return __sbRpc(op, msg);
2609
+ };
2610
+
2606
2611
  globalThis.__sbAssetsGet = function (path) {
2607
2612
  return __sbEmbeddedDispatch({ op: "assets.get", path });
2608
2613
  };