@cloudflare/sandbox 1.0.0 → 1.0.2

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
@@ -62,17 +62,17 @@ The image needs the helper that `Files` runs, and the Worker needs `nodejs_compa
62
62
 
63
63
  ## What you can build
64
64
 
65
- | Goal | Guide | Example |
66
- | -------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
67
- | Run a script and read its output | [Execute commands](https://developers.cloudflare.com/containers/guides/execute-commands/) | [`workspace`](examples/workspace) |
68
- | Keep a server or build running | [Run background processes](https://developers.cloudflare.com/sandbox/commands/run-background-processes/) | [`process-workspace`](examples/process-workspace) |
69
- | Open a shell in the browser | [Open a terminal in the browser](https://developers.cloudflare.com/sandbox/commands/open-a-terminal-in-the-browser/) | [`terminal-workspace`](examples/terminal-workspace) |
70
- | Process files from a bucket | [Mount an R2 bucket](https://developers.cloudflare.com/sandbox/files/mount-an-r2-bucket/) | [`artifact-workspace`](examples/artifact-workspace) |
71
- | Save a workspace and resume it later | [Save and restore a sandbox](https://developers.cloudflare.com/sandbox/files/save-and-restore-a-workspace/) | [`checkpoint-workspace`](examples/checkpoint-workspace), [`backup-workspace`](examples/backup-workspace) |
72
- | Preview a web app while you edit it | [Preview a web application](https://developers.cloudflare.com/sandbox/previews/) | [`preview-workspace`](examples/preview-workspace) |
73
- | Share a port on its own URL | [Serve previews on their own hostnames](https://developers.cloudflare.com/sandbox/previews/serve-previews-on-their-own-hostnames/) | [`share-workspace`](examples/share-workspace) |
74
- | Choose which hosts a sandbox can reach | [Control network access](https://developers.cloudflare.com/sandbox/network/) | [`outbound-workspace`](examples/outbound-workspace) |
75
- | Run a coding agent on a repository | [Coding agents](https://developers.cloudflare.com/sandbox/coding-agents/) | [`coding-agents`](examples/coding-agents), [`devin`](devin), [`openai/agents-api`](openai/agents-api) |
65
+ | Goal | Guide | Example |
66
+ | -------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
67
+ | Run a script and read its output | [Execute commands](https://developers.cloudflare.com/containers/guides/execute-commands/) | [`workspace`](examples/workspace) |
68
+ | Keep a server or build running | [Run background processes](https://developers.cloudflare.com/sandbox/commands/run-background-processes/) | [`process-workspace`](examples/process-workspace) |
69
+ | Open a shell in the browser | [Open a terminal in the browser](https://developers.cloudflare.com/sandbox/commands/open-a-terminal-in-the-browser/) | [`terminal-workspace`](examples/terminal-workspace) |
70
+ | Process files from a bucket | [Mount an R2 bucket](https://developers.cloudflare.com/sandbox/files/mount-an-r2-bucket/) | [`artifact-workspace`](examples/artifact-workspace) |
71
+ | Save a workspace and resume it later | [Save and restore a sandbox](https://developers.cloudflare.com/sandbox/files/save-and-restore-a-workspace/) | [`checkpoint-workspace`](examples/checkpoint-workspace), [`backup-workspace`](examples/backup-workspace) |
72
+ | Preview a web app while you edit it | [Preview a web application](https://developers.cloudflare.com/sandbox/previews/) | [`preview-workspace`](examples/preview-workspace) |
73
+ | Share a port on its own URL | [Serve previews on their own hostnames](https://developers.cloudflare.com/sandbox/previews/serve-previews-on-their-own-hostnames/) | [`share-workspace`](examples/share-workspace) |
74
+ | Choose which hosts a sandbox can reach | [Control network access](https://developers.cloudflare.com/sandbox/network/) | [`outbound-workspace`](examples/outbound-workspace) |
75
+ | Run a coding agent on a repository | [Coding agents](https://developers.cloudflare.com/sandbox/coding-agents/) | [`coding-agents`](examples/coding-agents), [`templates/devin`](templates/devin), [`templates/openai-agents-api`](templates/openai-agents-api) |
76
76
 
77
77
  To run JavaScript or Python without a Linux environment, use [Dynamic Workers](https://developers.cloudflare.com/sandbox/concepts/) instead.
78
78
 
@@ -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.d.mts CHANGED
@@ -101,7 +101,7 @@ interface DirectoryBackupGatewayBinding {
101
101
  * The container reaches `fetch()` through the outbound intercept and can only use the grant
102
102
  * its current operation holds. The other methods are for the Durable Object alone.
103
103
  */
104
- declare class DirectoryBackupGateway extends WorkerEntrypoint<object, DirectoryBackupGatewayProps> {
104
+ export declare class DirectoryBackupGateway extends WorkerEntrypoint<object, DirectoryBackupGatewayProps> {
105
105
  #private;
106
106
  fetch(request: Request): Promise<Response>;
107
107
  createUpload(name?: string): Promise<string>;
@@ -121,7 +121,7 @@ type DirectoryBackupContainer = Pick<Container, "exec" | "interceptOutboundHttp"
121
121
  * starts, retries, or times out anything. The application stores the returned records and
122
122
  * decides when to delete them.
123
123
  */
124
- declare class DirectoryBackup {
124
+ export declare class DirectoryBackup {
125
125
  #private;
126
126
  constructor(container: DirectoryBackupContainer, gateway: DirectoryBackupGatewayBinding, storage: DirectoryBackupStorage);
127
127
  /**
@@ -214,7 +214,7 @@ type MkdirOptions = FileOperationOptions & {
214
214
  * Operations run against the current native container execution. Its image must provide the matching shim at
215
215
  * `/usr/local/bin/sandbox-shim`.
216
216
  */
217
- declare class Files {
217
+ export declare class Files {
218
218
  #private;
219
219
  constructor(container: Pick<Container, "exec">);
220
220
  /**
@@ -452,7 +452,7 @@ interface S3GatewayBinding {
452
452
  }
453
453
  //#endregion
454
454
  //#region src/s3-mount/s3-gateway.d.ts
455
- declare class S3Gateway extends WorkerEntrypoint<object, S3GatewayProps> {
455
+ export declare class S3Gateway extends WorkerEntrypoint<object, S3GatewayProps> {
456
456
  fetch(request: Request): Promise<Response>;
457
457
  }
458
458
  //#endregion
@@ -470,7 +470,7 @@ type S3MountContainer = Pick<Container, "exec" | "interceptOutboundHttp">;
470
470
  * monitors, or replaces it. The mounted path is not a POSIX filesystem. Do not
471
471
  * use it for locking or atomic rename.
472
472
  */
473
- declare class S3Mount {
473
+ export declare class S3Mount {
474
474
  #private;
475
475
  constructor(container: S3MountContainer, gateway: S3GatewayBinding);
476
476
  /**
@@ -505,7 +505,7 @@ declare const FILE_OPERATIONS: readonly ["readFile", "writeFile", "stat", "lstat
505
505
  /** Filesystem operation that failed. */
506
506
  type SandboxFileOperation = (typeof FILE_OPERATIONS)[number];
507
507
  /** A native Linux filesystem failure reported by the sandbox container. */
508
- interface SandboxFileError extends Error {
508
+ export interface SandboxFileError extends Error {
509
509
  readonly name: "SandboxFileError";
510
510
  readonly code: SandboxFileErrorCode;
511
511
  readonly operation: SandboxFileOperation;
@@ -513,38 +513,38 @@ interface SandboxFileError extends Error {
513
513
  readonly destination?: string;
514
514
  readonly detail: string;
515
515
  }
516
- declare const SandboxFileError: {
516
+ export declare const SandboxFileError: {
517
517
  /** Recognizes local and JSRPC-crossed SandboxFileError values. */
518
518
  is(cause: unknown): cause is SandboxFileError;
519
519
  };
520
520
  /** An incompatible or malformed exchange with `sandbox-shim`. */
521
- interface SandboxProtocolError extends Error {
521
+ export interface SandboxProtocolError extends Error {
522
522
  readonly name: "SandboxProtocolError";
523
523
  readonly code: "SANDBOX_PROTOCOL_ERROR";
524
524
  readonly detail: string;
525
525
  }
526
- declare const SandboxProtocolError: {
526
+ export declare const SandboxProtocolError: {
527
527
  /** Recognizes local and JSRPC-crossed SandboxProtocolError values. */
528
528
  is(cause: unknown): cause is SandboxProtocolError;
529
529
  };
530
530
  type SandboxS3MountErrorCode = "S3_MOUNT_CONFLICT" | "S3_MOUNT_BUSY" | "S3_MOUNT_FAILED" | "S3_MOUNT_INCOMPATIBLE";
531
531
  type S3MountOperation = "mount" | "inspect" | "unmount";
532
532
  /** A classifiable failure while reconciling an S3-compatible filesystem mount. */
533
- interface SandboxS3MountError extends Error {
533
+ export interface SandboxS3MountError extends Error {
534
534
  readonly name: "SandboxS3MountError";
535
535
  readonly code: SandboxS3MountErrorCode;
536
536
  readonly operation: S3MountOperation;
537
537
  readonly path: string;
538
538
  readonly detail: string;
539
539
  }
540
- declare const SandboxS3MountError: {
540
+ export declare const SandboxS3MountError: {
541
541
  /** Recognizes local and JSRPC-crossed SandboxS3MountError values. */
542
542
  is(cause: unknown): cause is SandboxS3MountError;
543
543
  };
544
544
  type SandboxBackupErrorCode = "BACKUP_NOT_FOUND" | "BACKUP_INTEGRITY" | "BACKUP_TRANSFER";
545
545
  type DirectoryBackupOperation = "backup" | "restore" | "delete";
546
546
  /** A failure specific to a directory backup: a missing, altered, or unreachable object. */
547
- interface SandboxBackupError extends Error {
547
+ export interface SandboxBackupError extends Error {
548
548
  readonly name: "SandboxBackupError";
549
549
  readonly code: SandboxBackupErrorCode;
550
550
  readonly operation: DirectoryBackupOperation;
@@ -552,9 +552,9 @@ interface SandboxBackupError extends Error {
552
552
  readonly path: string;
553
553
  readonly detail: string;
554
554
  }
555
- declare const SandboxBackupError: {
555
+ export declare const SandboxBackupError: {
556
556
  /** Recognizes local and JSRPC-crossed SandboxBackupError values. */
557
557
  is(cause: unknown): cause is SandboxBackupError;
558
558
  };
559
559
  //#endregion
560
- export { DirectoryBackup, type DirectoryBackupDeleteOptions, DirectoryBackupGateway, type DirectoryBackupGatewayBinding, type DirectoryBackupOperation, type DirectoryBackupOptions, type DirectoryBackupRecord, type DirectoryBackupStorage, type DirectoryRestoreOptions, type FileContent, type FileOperationOptions, Files, type MkdirOptions, type RemoveOptions, S3Gateway, type S3GatewayBinding, S3Mount, type S3MountInspection, type S3MountOperation, type S3MountOperationOptions, type S3MountRequest, SandboxBackupError, type SandboxBackupErrorCode, type SandboxDirectoryEntry, SandboxFileError, type SandboxFileStat, type SandboxFileType, SandboxProtocolError, SandboxS3MountError, type SandboxS3MountErrorCode };
560
+ export type { DirectoryBackupDeleteOptions, DirectoryBackupGatewayBinding, DirectoryBackupOperation, DirectoryBackupOptions, DirectoryBackupRecord, DirectoryBackupStorage, DirectoryRestoreOptions, FileContent, FileOperationOptions, MkdirOptions, RemoveOptions, S3GatewayBinding, S3MountInspection, S3MountOperation, S3MountOperationOptions, S3MountRequest, SandboxBackupErrorCode, SandboxDirectoryEntry, SandboxFileStat, SandboxFileType, SandboxS3MountErrorCode };
package/dist/index.mjs CHANGED
@@ -985,11 +985,12 @@ async function readFile(container, path, options) {
985
985
  stderr: "pipe"
986
986
  });
987
987
  let control;
988
- let output;
988
+ let stdout;
989
989
  try {
990
990
  control = session.openStderrControl();
991
- output = session.openStdoutReader();
991
+ stdout = new StdoutReader(session.openStdoutReader());
992
992
  const opening = await control.readFrame();
993
+ stdout.stopReadingAhead();
993
994
  if (opening.kind === "fileError") {
994
995
  await control.expectEnd();
995
996
  await session.settle();
@@ -1001,17 +1002,69 @@ async function readFile(container, path, options) {
1001
1002
  if (opening.kind !== "success") throw protocolError("sandbox-shim returned data before file bytes");
1002
1003
  const terminal = readTerminalControl(control);
1003
1004
  terminal.catch(() => void 0);
1004
- return new Response(responseBody(session, control, output, terminal, path));
1005
+ return new Response(responseBody(session, control, stdout, terminal, path));
1005
1006
  } catch (error) {
1006
- terminateRead(session, control, output, error);
1007
+ terminateRead(session, control, stdout, error);
1007
1008
  throw error;
1008
1009
  }
1009
1010
  }
1010
- function responseBody(session, control, output, terminalFrame, path) {
1011
+ /**
1012
+ * Reads the shim's stdout, reading ahead until `readFile` has the opening frame from stderr.
1013
+ *
1014
+ * A container transport can carry stdout and stderr over one connection with shared backpressure,
1015
+ * and can deliver file bytes before the opening frame, or between its fragments. The rest of the
1016
+ * frame then arrives only after something reads stdout. The shim writes the whole opening frame
1017
+ * before any file byte, so the read-ahead stops once that frame is complete. After that, the
1018
+ * response body reads stdout on demand, so the caller's pace applies backpressure again.
1019
+ *
1020
+ * The read-ahead has no size limit, because stopping before the frame is complete would bring the
1021
+ * hang back. How much it holds depends on how far the transport lets stdout run ahead of stderr.
1022
+ */
1023
+ var StdoutReader = class {
1024
+ #reader;
1025
+ #chunks = [];
1026
+ #pending;
1027
+ #readingAhead = true;
1028
+ constructor(reader) {
1029
+ this.#reader = reader;
1030
+ this.#readNext();
1031
+ }
1032
+ /** Stops reading ahead. Chunks already read stay queued for `read()`. */
1033
+ stopReadingAhead() {
1034
+ this.#readingAhead = false;
1035
+ }
1036
+ read() {
1037
+ const chunk = this.#chunks.shift();
1038
+ if (chunk !== void 0) return Promise.resolve({
1039
+ done: false,
1040
+ value: chunk
1041
+ });
1042
+ const pending = this.#pending ?? this.#reader.read();
1043
+ this.#pending = void 0;
1044
+ return pending;
1045
+ }
1046
+ discard(reason) {
1047
+ this.#reader.cancel(reason).then(() => this.#reader.releaseLock(), () => this.#reader.releaseLock());
1048
+ }
1049
+ releaseLock() {
1050
+ this.#reader.releaseLock();
1051
+ }
1052
+ #readNext() {
1053
+ const read = this.#reader.read();
1054
+ this.#pending = read;
1055
+ read.then((result) => {
1056
+ if (this.#pending !== read || result.done) return;
1057
+ this.#pending = void 0;
1058
+ this.#chunks.push(result.value);
1059
+ if (this.#readingAhead) this.#readNext();
1060
+ }, () => void 0);
1061
+ }
1062
+ };
1063
+ function responseBody(session, control, stdout, terminalFrame, path) {
1011
1064
  return new ReadableStream({
1012
1065
  pull: async (controller) => {
1013
1066
  try {
1014
- const next = await session.waitFor(output.read());
1067
+ const next = await session.waitFor(stdout.read());
1015
1068
  if (!next.done) {
1016
1069
  controller.enqueue(next.value);
1017
1070
  return;
@@ -1025,16 +1078,16 @@ function responseBody(session, control, output, terminalFrame, path) {
1025
1078
  }, terminal.errno, terminal.detail);
1026
1079
  }
1027
1080
  if (terminal.kind !== "success") throw protocolError("sandbox-shim returned data after file bytes");
1028
- output.releaseLock();
1081
+ stdout.releaseLock();
1029
1082
  control.releaseLock();
1030
1083
  session.finish();
1031
1084
  controller.close();
1032
1085
  } catch (error) {
1033
- terminateRead(session, control, output, error);
1086
+ terminateRead(session, control, stdout, error);
1034
1087
  controller.error(error);
1035
1088
  }
1036
1089
  },
1037
- cancel: (reason) => terminateRead(session, control, output, reason)
1090
+ cancel: (reason) => terminateRead(session, control, stdout, reason)
1038
1091
  });
1039
1092
  }
1040
1093
  async function readTerminalControl(control) {
@@ -1042,10 +1095,10 @@ async function readTerminalControl(control) {
1042
1095
  await control.expectEnd();
1043
1096
  return frame;
1044
1097
  }
1045
- function terminateRead(session, control, output, reason) {
1098
+ function terminateRead(session, control, stdout, reason) {
1046
1099
  session.terminate();
1047
1100
  control?.discard(reason);
1048
- if (output !== void 0) output.cancel(reason).then(() => output.releaseLock(), () => output.releaseLock());
1101
+ stdout?.discard(reason);
1049
1102
  }
1050
1103
  //#endregion
1051
1104
  //#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.0",
3
+ "version": "1.0.2",
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.5.4"
26
+ "zod": "4.6.7"
27
27
  }
28
28
  }