@sproutboat/runtime 0.19.0 → 0.19.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,11 @@
1
1
  # @sproutboat/runtime
2
2
 
3
+ ## 0.19.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
+
3
9
  ## 0.19.0
4
10
 
5
11
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sproutboat/runtime",
3
- "version": "0.19.0",
3
+ "version": "0.19.1",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "repository": {
@@ -4045,7 +4045,14 @@ function __sbFromUtf8(bytes) {
4045
4045
  // #232 — a fetch() request body for the wire: text as-is, and an ArrayBuffer or
4046
4046
  // any view as its bytes (one char per byte) flagged bodyBinary, instead of
4047
4047
  // String(view), which sent "0,1,2,...".
4048
+ // A body stream the runtime made over a value (Response/Request body): that value. Bindings
4049
+ // that take a body at once (R2 and KV put, outbound fetch) read it without draining the
4050
+ // stream, so put(key, request.body) works as in Workers. Other values pass through.
4051
+ function __sbUnwrapBody(value) {
4052
+ return value != null && value._sbBody !== undefined ? value._sbBody : value;
4053
+ }
4048
4054
  function __sbRequestBody(body) {
4055
+ body = __sbUnwrapBody(body);
4049
4056
  if (body == null) return { body: null };
4050
4057
  if (!__sbIsStr(body) && body.byteLength !== undefined) {
4051
4058
  const bytes = body.buffer !== undefined ? new Uint8Array(body.buffer, body.byteOffset, body.byteLength) : body;
@@ -4966,6 +4973,7 @@ globalThis.__sbInstallBindings = function (target, bindings) {
4966
4973
  return type === "json" ? JSON.parse(text) : text;
4967
4974
  },
4968
4975
  put(key, value, options) {
4976
+ value = __sbUnwrapBody(value);
4969
4977
  const msg = { ns, key: String(key), value: "" };
4970
4978
  if (value != null && !__sbIsStr(value) && value.byteLength !== undefined) {
4971
4979
  // ArrayBuffer or any ArrayBufferView: its bytes, not String(view) ("0,1,2,...").
@@ -5011,12 +5019,15 @@ globalThis.__sbInstallBindings = function (target, bindings) {
5011
5019
  const name = bindings.r2[i];
5012
5020
  target[name] = {
5013
5021
  put(key, value, options) {
5022
+ value = __sbUnwrapBody(value);
5014
5023
  const o = options || {};
5015
5024
  // #56 — the body goes out of band where the transport allows it.
5025
+ // Bytes go in as bytes: a byte view or the bytes of a body stream (request.body).
5026
+ const raw = value == null ? "" : !__sbIsStr(value) && value.byteLength !== undefined ? __sbToBytes(value.buffer !== undefined ? new Uint8Array(value.buffer, value.byteOffset, value.byteLength) : value) : String(value);
5016
5027
  return __sbR2Put(
5017
5028
  name,
5018
5029
  String(key),
5019
- value == null ? "" : String(value),
5030
+ raw,
5020
5031
  o.httpMetadata || {},
5021
5032
  o.customMetadata || {},
5022
5033
  ).object;
@@ -5802,6 +5813,8 @@ async function __sbQueueWithDrain(result, tasks) {
5802
5813
  function __sbDecodeIngress(request) {
5803
5814
  const raw = __sbRawBody(request);
5804
5815
  if (raw == null) return;
5816
+ // Raw bytes, one character each: arrayBuffer(), blob() and body read them as bytes.
5817
+ request._bodyLatin1 = true;
5805
5818
  let decoded = null;
5806
5819
  request.text = function () {
5807
5820
  if (decoded === null) decoded = __sbFromUtf8(raw);
@@ -20,7 +20,7 @@ const CASES = [
20
20
  ];
21
21
  const handler = `export default {async fetch(request) {
22
22
  // The incoming request: its body is a ReadableStream, and text() still reads it.
23
- if (request.method === "POST") return new Response((request.body instanceof ReadableStream) + ":" + (await request.text()));
23
+ if (request.method === "POST") { const viaStream = await new Response(request.body).text(); return new Response((request.body instanceof ReadableStream) + ":" + viaStream); }
24
24
  const cases = [
25
25
  ${CASES.map((c) => ` () => (${c}),`).join("\n")}
26
26
  ];
@@ -17,7 +17,14 @@ const text = "/café/日本/😀/x";
17
17
  const handler = `
18
18
  const URL = "shadowed";
19
19
  export default {
20
- async fetch() {
20
+ async fetch(request) {
21
+ // put(key, request.body) and fetch(url, { body: request.body }) are the Workers idiom:
22
+ // body is a ReadableStream, and the bindings take the value it was made from.
23
+ if (request.method === "POST") {
24
+ env.UPLOADS.put("upload", request.body);
25
+ await env.KV.put("upload", request.body);
26
+ return Response.json({ r2: env.UPLOADS.get("upload").text(), kv: await env.KV.get("upload") });
27
+ }
21
28
  await env.KV.put("k", ${JSON.stringify(text)});
22
29
  const kv = await env.KV.get("k");
23
30
  await env.DB.prepare("CREATE TABLE t (v TEXT)").run();
@@ -38,7 +45,7 @@ function freePort(): number {
38
45
  }
39
46
 
40
47
  const workdir = await mkdtemp(join(tmpdir(), "sb-native-utf8-"));
41
- const bindings = { ...EMPTY_BINDINGS, kv: ["KV"], d1: ["DB"] };
48
+ const bindings = { ...EMPTY_BINDINGS, kv: ["KV"], d1: ["DB"], r2: ["UPLOADS"] };
42
49
  const broker = createBroker({
43
50
  db: ":memory:",
44
51
  token: "native-utf8",
@@ -113,7 +120,9 @@ try {
113
120
  );
114
121
  assert.match(body.error, /tabellà/);
115
122
  assert.equal(body.url, "shadowed");
116
- console.log("Non-ASCII KV, D1 and error text round-trip through the broker; a handler-level URL stays scoped");
123
+ const stored = await (await fetch(`http://127.0.0.1:${port}/`, { method: "POST", body: "uploaded é" })).json();
124
+ assert.deepEqual(stored, { r2: "uploaded é", kv: "uploaded é" }, "put(key, request.body) stores the request body");
125
+ console.log("Non-ASCII KV, D1 and error text round-trip through the broker; a handler-level URL stays scoped; put(key, request.body) stores the body");
117
126
  } finally {
118
127
  if (server) {
119
128
  server.kill(9);