@cloudflare/sandbox 1.0.1 → 1.0.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/README.md CHANGED
@@ -99,7 +99,7 @@ npm run check
99
99
  npm test
100
100
  ```
101
101
 
102
- [Testing](docs/testing.md) explains what these commands check, how to build behind a TLS-inspecting proxy, and how to test in production. [Releasing](docs/releasing.md) explains how maintainers publish the package and its image.
102
+ [Testing](docs/testing.md) explains what these commands check, how to build behind a TLS-inspecting proxy, and how to test in production. [Releasing](docs/releasing.md) explains how to describe a change for the release notes, and how maintainers publish the package and its image.
103
103
 
104
104
  ## License
105
105
 
package/dist/index.mjs CHANGED
@@ -968,7 +968,10 @@ function readBytes(payload, offset, length) {
968
968
  }
969
969
  function decodeText(bytes) {
970
970
  try {
971
- return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
971
+ return new TextDecoder("utf-8", {
972
+ fatal: true,
973
+ ignoreBOM: true
974
+ }).decode(bytes);
972
975
  } catch (cause) {
973
976
  throw protocolError("sandbox-shim returned invalid UTF-8 in directory entry name", cause);
974
977
  }
@@ -985,11 +988,12 @@ async function readFile(container, path, options) {
985
988
  stderr: "pipe"
986
989
  });
987
990
  let control;
988
- let output;
991
+ let stdout;
989
992
  try {
990
993
  control = session.openStderrControl();
991
- output = session.openStdoutReader();
994
+ stdout = new StdoutReader(session.openStdoutReader());
992
995
  const opening = await control.readFrame();
996
+ stdout.stopReadingAhead();
993
997
  if (opening.kind === "fileError") {
994
998
  await control.expectEnd();
995
999
  await session.settle();
@@ -1001,17 +1005,69 @@ async function readFile(container, path, options) {
1001
1005
  if (opening.kind !== "success") throw protocolError("sandbox-shim returned data before file bytes");
1002
1006
  const terminal = readTerminalControl(control);
1003
1007
  terminal.catch(() => void 0);
1004
- return new Response(responseBody(session, control, output, terminal, path));
1008
+ return new Response(responseBody(session, control, stdout, terminal, path));
1005
1009
  } catch (error) {
1006
- terminateRead(session, control, output, error);
1010
+ terminateRead(session, control, stdout, error);
1007
1011
  throw error;
1008
1012
  }
1009
1013
  }
1010
- function responseBody(session, control, output, terminalFrame, path) {
1014
+ /**
1015
+ * Reads the shim's stdout, reading ahead until `readFile` has the opening frame from stderr.
1016
+ *
1017
+ * A container transport can carry stdout and stderr over one connection with shared backpressure,
1018
+ * and can deliver file bytes before the opening frame, or between its fragments. The rest of the
1019
+ * frame then arrives only after something reads stdout. The shim writes the whole opening frame
1020
+ * before any file byte, so the read-ahead stops once that frame is complete. After that, the
1021
+ * response body reads stdout on demand, so the caller's pace applies backpressure again.
1022
+ *
1023
+ * The read-ahead has no size limit, because stopping before the frame is complete would bring the
1024
+ * hang back. How much it holds depends on how far the transport lets stdout run ahead of stderr.
1025
+ */
1026
+ var StdoutReader = class {
1027
+ #reader;
1028
+ #chunks = [];
1029
+ #pending;
1030
+ #readingAhead = true;
1031
+ constructor(reader) {
1032
+ this.#reader = reader;
1033
+ this.#readNext();
1034
+ }
1035
+ /** Stops reading ahead. Chunks already read stay queued for `read()`. */
1036
+ stopReadingAhead() {
1037
+ this.#readingAhead = false;
1038
+ }
1039
+ read() {
1040
+ const chunk = this.#chunks.shift();
1041
+ if (chunk !== void 0) return Promise.resolve({
1042
+ done: false,
1043
+ value: chunk
1044
+ });
1045
+ const pending = this.#pending ?? this.#reader.read();
1046
+ this.#pending = void 0;
1047
+ return pending;
1048
+ }
1049
+ discard(reason) {
1050
+ this.#reader.cancel(reason).then(() => this.#reader.releaseLock(), () => this.#reader.releaseLock());
1051
+ }
1052
+ releaseLock() {
1053
+ this.#reader.releaseLock();
1054
+ }
1055
+ #readNext() {
1056
+ const read = this.#reader.read();
1057
+ this.#pending = read;
1058
+ read.then((result) => {
1059
+ if (this.#pending !== read || result.done) return;
1060
+ this.#pending = void 0;
1061
+ this.#chunks.push(result.value);
1062
+ if (this.#readingAhead) this.#readNext();
1063
+ }, () => void 0);
1064
+ }
1065
+ };
1066
+ function responseBody(session, control, stdout, terminalFrame, path) {
1011
1067
  return new ReadableStream({
1012
1068
  pull: async (controller) => {
1013
1069
  try {
1014
- const next = await session.waitFor(output.read());
1070
+ const next = await session.waitFor(stdout.read());
1015
1071
  if (!next.done) {
1016
1072
  controller.enqueue(next.value);
1017
1073
  return;
@@ -1025,16 +1081,16 @@ function responseBody(session, control, output, terminalFrame, path) {
1025
1081
  }, terminal.errno, terminal.detail);
1026
1082
  }
1027
1083
  if (terminal.kind !== "success") throw protocolError("sandbox-shim returned data after file bytes");
1028
- output.releaseLock();
1084
+ stdout.releaseLock();
1029
1085
  control.releaseLock();
1030
1086
  session.finish();
1031
1087
  controller.close();
1032
1088
  } catch (error) {
1033
- terminateRead(session, control, output, error);
1089
+ terminateRead(session, control, stdout, error);
1034
1090
  controller.error(error);
1035
1091
  }
1036
1092
  },
1037
- cancel: (reason) => terminateRead(session, control, output, reason)
1093
+ cancel: (reason) => terminateRead(session, control, stdout, reason)
1038
1094
  });
1039
1095
  }
1040
1096
  async function readTerminalControl(control) {
@@ -1042,10 +1098,10 @@ async function readTerminalControl(control) {
1042
1098
  await control.expectEnd();
1043
1099
  return frame;
1044
1100
  }
1045
- function terminateRead(session, control, output, reason) {
1101
+ function terminateRead(session, control, stdout, reason) {
1046
1102
  session.terminate();
1047
1103
  control?.discard(reason);
1048
- if (output !== void 0) output.cancel(reason).then(() => output.releaseLock(), () => output.releaseLock());
1104
+ stdout?.discard(reason);
1049
1105
  }
1050
1106
  //#endregion
1051
1107
  //#region src/files/stat-file.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudflare/sandbox",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "Structured workspace file operations for Cloudflare sandboxes",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -23,6 +23,6 @@
23
23
  },
24
24
  "dependencies": {
25
25
  "aws4fetch": "1.0.20",
26
- "zod": "4.6.5"
26
+ "zod": "4.6.7"
27
27
  }
28
28
  }