@cloudflare/sandbox 1.0.0-rc.2 → 1.0.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/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
 
package/dist/index.d.mts CHANGED
@@ -41,7 +41,7 @@ interface DirectoryBackupOptions {
41
41
  interface DirectoryRestoreOptions {
42
42
  /** Absolute path to restore into instead of the record's `dir`. */
43
43
  dir?: string;
44
- /** Cancels the operation without imposing a timeout. */
44
+ /** Cancels the restore until the download is verified, without imposing a timeout. */
45
45
  signal?: AbortSignal;
46
46
  }
47
47
  interface DirectoryBackupDeleteOptions {
@@ -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
  /**
@@ -145,9 +145,12 @@ declare class DirectoryBackup {
145
145
  backup(options: DirectoryBackupOptions): Promise<DirectoryBackupRecord>;
146
146
  /**
147
147
  * Replaces `options.dir`, or the record's `dir`, with the backup's contents. The directory is
148
- * extracted beside the target and swapped in only after the download is verified, so a failed
149
- * or aborted restore leaves the target as it was. The target's parent must exist; the target
150
- * need not.
148
+ * extracted beside the target and swapped in only after the download is verified. A
149
+ * `SandboxFileError` or `SandboxBackupError` means the target was not replaced, and so does
150
+ * an abort before the download is verified; after that, aborting no longer ends the call. If the
151
+ * connection to the Container is lost, as when the Durable Object restarts, the target may or
152
+ * may not have been replaced, and restoring again settles it. The target's parent must exist;
153
+ * the target need not.
151
154
  *
152
155
  * @throws {SandboxFileError} The target's parent is missing (`ENOENT`), the target isn't a
153
156
  * directory (`ENOTDIR`), is a mount point or has one inside it (`EBUSY`), the swap fails (for
@@ -211,7 +214,7 @@ type MkdirOptions = FileOperationOptions & {
211
214
  * Operations run against the current native container execution. Its image must provide the matching shim at
212
215
  * `/usr/local/bin/sandbox-shim`.
213
216
  */
214
- declare class Files {
217
+ export declare class Files {
215
218
  #private;
216
219
  constructor(container: Pick<Container, "exec">);
217
220
  /**
@@ -449,7 +452,7 @@ interface S3GatewayBinding {
449
452
  }
450
453
  //#endregion
451
454
  //#region src/s3-mount/s3-gateway.d.ts
452
- declare class S3Gateway extends WorkerEntrypoint<object, S3GatewayProps> {
455
+ export declare class S3Gateway extends WorkerEntrypoint<object, S3GatewayProps> {
453
456
  fetch(request: Request): Promise<Response>;
454
457
  }
455
458
  //#endregion
@@ -467,7 +470,7 @@ type S3MountContainer = Pick<Container, "exec" | "interceptOutboundHttp">;
467
470
  * monitors, or replaces it. The mounted path is not a POSIX filesystem. Do not
468
471
  * use it for locking or atomic rename.
469
472
  */
470
- declare class S3Mount {
473
+ export declare class S3Mount {
471
474
  #private;
472
475
  constructor(container: S3MountContainer, gateway: S3GatewayBinding);
473
476
  /**
@@ -502,7 +505,7 @@ declare const FILE_OPERATIONS: readonly ["readFile", "writeFile", "stat", "lstat
502
505
  /** Filesystem operation that failed. */
503
506
  type SandboxFileOperation = (typeof FILE_OPERATIONS)[number];
504
507
  /** A native Linux filesystem failure reported by the sandbox container. */
505
- interface SandboxFileError extends Error {
508
+ export interface SandboxFileError extends Error {
506
509
  readonly name: "SandboxFileError";
507
510
  readonly code: SandboxFileErrorCode;
508
511
  readonly operation: SandboxFileOperation;
@@ -510,38 +513,38 @@ interface SandboxFileError extends Error {
510
513
  readonly destination?: string;
511
514
  readonly detail: string;
512
515
  }
513
- declare const SandboxFileError: {
516
+ export declare const SandboxFileError: {
514
517
  /** Recognizes local and JSRPC-crossed SandboxFileError values. */
515
518
  is(cause: unknown): cause is SandboxFileError;
516
519
  };
517
520
  /** An incompatible or malformed exchange with `sandbox-shim`. */
518
- interface SandboxProtocolError extends Error {
521
+ export interface SandboxProtocolError extends Error {
519
522
  readonly name: "SandboxProtocolError";
520
523
  readonly code: "SANDBOX_PROTOCOL_ERROR";
521
524
  readonly detail: string;
522
525
  }
523
- declare const SandboxProtocolError: {
526
+ export declare const SandboxProtocolError: {
524
527
  /** Recognizes local and JSRPC-crossed SandboxProtocolError values. */
525
528
  is(cause: unknown): cause is SandboxProtocolError;
526
529
  };
527
530
  type SandboxS3MountErrorCode = "S3_MOUNT_CONFLICT" | "S3_MOUNT_BUSY" | "S3_MOUNT_FAILED" | "S3_MOUNT_INCOMPATIBLE";
528
531
  type S3MountOperation = "mount" | "inspect" | "unmount";
529
532
  /** A classifiable failure while reconciling an S3-compatible filesystem mount. */
530
- interface SandboxS3MountError extends Error {
533
+ export interface SandboxS3MountError extends Error {
531
534
  readonly name: "SandboxS3MountError";
532
535
  readonly code: SandboxS3MountErrorCode;
533
536
  readonly operation: S3MountOperation;
534
537
  readonly path: string;
535
538
  readonly detail: string;
536
539
  }
537
- declare const SandboxS3MountError: {
540
+ export declare const SandboxS3MountError: {
538
541
  /** Recognizes local and JSRPC-crossed SandboxS3MountError values. */
539
542
  is(cause: unknown): cause is SandboxS3MountError;
540
543
  };
541
544
  type SandboxBackupErrorCode = "BACKUP_NOT_FOUND" | "BACKUP_INTEGRITY" | "BACKUP_TRANSFER";
542
545
  type DirectoryBackupOperation = "backup" | "restore" | "delete";
543
546
  /** A failure specific to a directory backup: a missing, altered, or unreachable object. */
544
- interface SandboxBackupError extends Error {
547
+ export interface SandboxBackupError extends Error {
545
548
  readonly name: "SandboxBackupError";
546
549
  readonly code: SandboxBackupErrorCode;
547
550
  readonly operation: DirectoryBackupOperation;
@@ -549,9 +552,9 @@ interface SandboxBackupError extends Error {
549
552
  readonly path: string;
550
553
  readonly detail: string;
551
554
  }
552
- declare const SandboxBackupError: {
555
+ export declare const SandboxBackupError: {
553
556
  /** Recognizes local and JSRPC-crossed SandboxBackupError values. */
554
557
  is(cause: unknown): cause is SandboxBackupError;
555
558
  };
556
559
  //#endregion
557
- 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
@@ -367,6 +367,10 @@ var ShimSession = class ShimSession {
367
367
  waitFor(operation) {
368
368
  return this.#abort.waitFor(operation);
369
369
  }
370
+ /** Stops following the caller's signal: later waits no longer reject when it aborts. */
371
+ stopFollowingSignal() {
372
+ this.#abort.dispose();
373
+ }
370
374
  finish() {
371
375
  if (this.#settled) return;
372
376
  this.#settled = true;
@@ -490,6 +494,7 @@ const errorSchema = z.object({
490
494
  detail: z.string()
491
495
  });
492
496
  const lockedSchema = z.strictObject({ kind: z.literal("locked") });
497
+ const verifiedSchema = z.strictObject({ kind: z.literal("verified") });
493
498
  /**
494
499
  * Runs one `directory-backup` shim exchange: wait for the lock, register the grant, acknowledge,
495
500
  * wait for the result, then deny and close stdin, in that order on every path. The shim holds
@@ -497,7 +502,10 @@ const lockedSchema = z.strictObject({ kind: z.literal("locked") });
497
502
  * operation's deny.
498
503
  *
499
504
  * An abort closes stdin rather than killing the shim, which then removes whatever it had
500
- * partly written, and rejects at once with the signal's reason.
505
+ * partly written, and rejects with the signal's reason without waiting for the shim. A restore
506
+ * reports `verified` before it swaps and swaps only once acknowledged, so the exchange
507
+ * acknowledges it only while the signal hasn't aborted, and from then on reports the swap's
508
+ * outcome instead of the abort.
501
509
  */
502
510
  async function runShimExchange(container, exchange) {
503
511
  const { signal } = exchange;
@@ -535,6 +543,13 @@ async function runShimExchange(container, exchange) {
535
543
  granting = exchange.grant();
536
544
  await session.waitFor(granting);
537
545
  await session.waitFor(input.write(ACKNOWLEDGEMENT));
546
+ if (exchange.command === "restore") {
547
+ const verified = await readMessage(control, operation, exchange.path);
548
+ if (!verifiedSchema.safeParse(verified).success) throw protocolError("sandbox-shim did not report a verified restore");
549
+ signal?.throwIfAborted();
550
+ session.stopFollowingSignal();
551
+ await input.write(ACKNOWLEDGEMENT);
552
+ }
538
553
  const result = await readMessage(control, operation, exchange.path);
539
554
  const done = exchange.done.safeParse(result);
540
555
  if (!done.success) throw protocolError("sandbox-shim returned an invalid backup result");
@@ -704,9 +719,12 @@ var DirectoryBackup = class {
704
719
  }
705
720
  /**
706
721
  * Replaces `options.dir`, or the record's `dir`, with the backup's contents. The directory is
707
- * extracted beside the target and swapped in only after the download is verified, so a failed
708
- * or aborted restore leaves the target as it was. The target's parent must exist; the target
709
- * need not.
722
+ * extracted beside the target and swapped in only after the download is verified. A
723
+ * `SandboxFileError` or `SandboxBackupError` means the target was not replaced, and so does
724
+ * an abort before the download is verified; after that, aborting no longer ends the call. If the
725
+ * connection to the Container is lost, as when the Durable Object restarts, the target may or
726
+ * may not have been replaced, and restoring again settles it. The target's parent must exist;
727
+ * the target need not.
710
728
  *
711
729
  * @throws {SandboxFileError} The target's parent is missing (`ENOENT`), the target isn't a
712
730
  * directory (`ENOTDIR`), is a mount point or has one inside it (`EBUSY`), the swap fails (for
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudflare/sandbox",
3
- "version": "1.0.0-rc.2",
3
+ "version": "1.0.1",
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.5"
27
27
  }
28
28
  }