@cloudflare/sandbox 0.13.0-next.633.1 → 0.13.0-next.651.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.
Files changed (35) hide show
  1. package/dist/bridge/index.d.ts.map +1 -1
  2. package/dist/bridge/index.js +3 -3
  3. package/dist/{contexts-DPFhc2nR.d.ts → contexts-BS0Bs6IU.d.ts} +1 -1
  4. package/dist/{contexts-DPFhc2nR.d.ts.map → contexts-BS0Bs6IU.d.ts.map} +1 -1
  5. package/dist/{dist-mAH_7Ui7.js → dist-DF8sudAg.js} +19 -2
  6. package/dist/dist-DF8sudAg.js.map +1 -0
  7. package/dist/{errors-CpoDEfUZ.js → errors-BG6NZiPD.js} +1 -1
  8. package/dist/{errors-CpoDEfUZ.js.map → errors-BG6NZiPD.js.map} +1 -1
  9. package/dist/extensions/index.d.ts +74 -0
  10. package/dist/extensions/index.d.ts.map +1 -0
  11. package/dist/extensions/index.js +153 -0
  12. package/dist/extensions/index.js.map +1 -0
  13. package/dist/index.d.ts +3 -2
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +3 -3
  16. package/dist/openai/index.d.ts +2 -2
  17. package/dist/openai/index.d.ts.map +1 -1
  18. package/dist/openai/index.js +1 -1
  19. package/dist/opencode/index.d.ts +3 -2
  20. package/dist/opencode/index.d.ts.map +1 -1
  21. package/dist/opencode/index.js +2 -2
  22. package/dist/{sandbox-44kEJjAc.d.ts → rpc-types-PBUY-xXM.d.ts} +97 -1070
  23. package/dist/rpc-types-PBUY-xXM.d.ts.map +1 -0
  24. package/dist/{sandbox-C9CHzLDx.js → sandbox-BgwMBQ7S.js} +7 -4
  25. package/dist/sandbox-BgwMBQ7S.js.map +1 -0
  26. package/dist/sandbox-D_MMqExx.d.ts +1077 -0
  27. package/dist/sandbox-D_MMqExx.d.ts.map +1 -0
  28. package/dist/sidecar/index.d.ts +77 -0
  29. package/dist/sidecar/index.d.ts.map +1 -0
  30. package/dist/sidecar/index.js +201 -0
  31. package/dist/sidecar/index.js.map +1 -0
  32. package/package.json +13 -3
  33. package/dist/dist-mAH_7Ui7.js.map +0 -1
  34. package/dist/sandbox-44kEJjAc.d.ts.map +0 -1
  35. package/dist/sandbox-C9CHzLDx.js.map +0 -1
@@ -0,0 +1,1077 @@
1
+ import { $ as RenameFileResult, A as ExecResult, B as ListFilesOptions, D as DirectoryBackup, E as DeleteFileResult, G as MountBucketOptions, J as ProcessOptions, K as MoveFileResult, N as FileEncoding, P as FileExistsResult, R as GitCheckoutResult, T as CheckChangesResult, V as ListFilesResult, W as MkdirResult, X as ReadFileResult, Z as ReadFileStreamResult, _ as SandboxWatchAPI, _t as RunCodeOptions, b as BackupOptions, c as SandboxControlCallback, ct as WriteFileResult, d as SandboxGitAPI, et as RestoreBackupResult, f as SandboxInterpreterAPI, g as SandboxUtilsAPI, gt as ExecutionResult, h as SandboxTunnelsAPI, it as StreamOptions, j as ExecutionSession, k as ExecOptions, l as SandboxExtensionsAPI, m as SandboxProcessesAPI, mt as CreateContextOptions, nt as SessionDeleteResult, o as SandboxBackupAPI, p as SandboxPortsAPI, pt as CodeContext, q as Process, rt as SessionOptions, s as SandboxCommandsAPI, st as WatchOptions, tt as SandboxOptions, u as SandboxFilesAPI, v as TunnelInfo, w as CheckChangesOptions, y as TunnelOptions, z as ISandbox } from "./rpc-types-PBUY-xXM.js";
2
+ import { Container, ContainerProxy } from "@cloudflare/containers";
3
+ import { RpcTarget, RpcTransport } from "capnweb";
4
+
5
+ //#region ../shared/dist/logger/types.d.ts
6
+
7
+ type LogComponent = 'container' | 'sandbox-do' | 'executor';
8
+ /**
9
+ * Context metadata included in every log entry
10
+ */
11
+ interface LogContext {
12
+ /**
13
+ * Unique trace ID for request correlation across distributed components
14
+ * Format: "tr_" + 16 hex chars (e.g., "tr_7f3a9b2c4e5d6f1a")
15
+ */
16
+ traceId: string;
17
+ /**
18
+ * Component that generated the log
19
+ */
20
+ component: LogComponent;
21
+ /**
22
+ * Sandbox identifier (which sandbox instance)
23
+ */
24
+ sandboxId?: string;
25
+ /**
26
+ * Session identifier (which session within sandbox)
27
+ */
28
+ sessionId?: string;
29
+ /**
30
+ * Process identifier (which background process)
31
+ */
32
+ processId?: string;
33
+ /**
34
+ * Command identifier (which command execution)
35
+ */
36
+ commandId?: string;
37
+ /**
38
+ * Duration in milliseconds
39
+ */
40
+ durationMs?: number;
41
+ /**
42
+ * Service version (from SANDBOX_VERSION env var)
43
+ */
44
+ serviceVersion?: string;
45
+ /**
46
+ * Instance identifier (hostname or container ID)
47
+ */
48
+ instanceId?: string;
49
+ /**
50
+ * Extensible for additional metadata
51
+ */
52
+ [key: string]: unknown;
53
+ }
54
+ /**
55
+ * Logger interface for structured logging
56
+ *
57
+ * All methods accept optional context that gets merged with the logger's base context.
58
+ */
59
+ interface Logger {
60
+ /**
61
+ * Log debug-level message (most verbose, typically disabled in production)
62
+ *
63
+ * @param message Human-readable message
64
+ * @param context Optional additional context
65
+ */
66
+ debug(message: string, context?: Partial<LogContext>): void;
67
+ /**
68
+ * Log info-level message (normal operational events)
69
+ *
70
+ * @param message Human-readable message
71
+ * @param context Optional additional context
72
+ */
73
+ info(message: string, context?: Partial<LogContext>): void;
74
+ /**
75
+ * Log warning-level message (recoverable issues, degraded state)
76
+ *
77
+ * @param message Human-readable message
78
+ * @param context Optional additional context
79
+ */
80
+ warn(message: string, context?: Partial<LogContext>): void;
81
+ /**
82
+ * Log error-level message (failures, exceptions)
83
+ *
84
+ * @param message Human-readable message
85
+ * @param error Optional Error object to include
86
+ * @param context Optional additional context
87
+ */
88
+ error(message: string, error?: Error, context?: Partial<LogContext>): void;
89
+ /**
90
+ * Create a child logger with additional context
91
+ *
92
+ * The child logger inherits all context from the parent and adds new context.
93
+ * This is useful for adding request-specific context without passing through parameters.
94
+ *
95
+ * @param context Additional context to merge
96
+ * @returns New logger instance with merged context
97
+ *
98
+ * @example
99
+ * const logger = createLogger({ component: 'sandbox-do', traceId: 'tr_abc123' });
100
+ * const execLogger = logger.child({ commandId: 'cmd-456' });
101
+ * execLogger.info('Command started'); // Includes all context: component, traceId, commandId
102
+ */
103
+ child(context: Partial<LogContext>): Logger;
104
+ }
105
+ //#endregion
106
+ //#region src/container-control/connection.d.ts
107
+ /** Stub that can issue a WebSocket-upgrade fetch through the DO's Container base class. */
108
+ interface ContainerFetchStub {
109
+ fetch(request: Request): Promise<Response>;
110
+ }
111
+ interface ContainerControlConnectionOptions {
112
+ stub: ContainerFetchStub;
113
+ port?: number;
114
+ logger?: Logger;
115
+ /**
116
+ * Total retry budget (ms) for retryable upgrade responses while the
117
+ * container is unavailable. Defaults to 120 000 (2 minutes). Set to 0 to
118
+ * disable retries.
119
+ */
120
+ retryTimeoutMs?: number;
121
+ /**
122
+ * Optional `localMain` exposed to the container side of the capnweb
123
+ * session. The container reaches it via
124
+ * `session.getRemoteMain()` and uses it for control-plane callbacks
125
+ * (e.g. notifying the DO when a tunnel's cloudflared process has
126
+ * exited). When omitted, the container sees an empty remote main.
127
+ */
128
+ localMain?: SandboxControlCallback & RpcTarget;
129
+ /**
130
+ * Invoked when an active WebSocket transitions to closed/errored.
131
+ * Fired at most once per successful connection from the WS event
132
+ * handlers in `doConnect`. Gives owners a synchronous teardown
133
+ * signal so recovery doesn't depend on a periodic poller running
134
+ * inside what may be an idle isolate.
135
+ *
136
+ * Also fired for `doConnect` failures after the deferred transport is
137
+ * aborted. A failed upgrade poisons the transport, so owners must discard
138
+ * the connection and create a fresh one for subsequent calls. Not fired for
139
+ * `disconnect()`.
140
+ */
141
+ onClose?: () => void;
142
+ }
143
+ //#endregion
144
+ //#region src/container-control/client.d.ts
145
+
146
+ interface ContainerControlClientOptions extends ContainerControlConnectionOptions {
147
+ /** Idle timeout before disconnecting the WebSocket (ms). Defaults to 1 000. */
148
+ idleDisconnectMs?: number;
149
+ /** Busy/idle poll interval (ms). Defaults to 1 000. */
150
+ busyPollIntervalMs?: number;
151
+ /**
152
+ * Renew the DO's activity timeout. Fires at the start of every RPC call
153
+ * and on every busy-poll tick while the session has work in flight.
154
+ * Mirrors what `containerFetch()` does at the top of each HTTP request.
155
+ */
156
+ onActivity?: () => void;
157
+ /**
158
+ * Fires once when the capnweb session transitions from idle to busy
159
+ * (an RPC call was started or a stream return is now in flight). The
160
+ * Sandbox DO wires this to increment the Container base class's
161
+ * in-flight request counter, which makes `isActivityExpired()` skip the
162
+ * sleepAfter comparison.
163
+ */
164
+ onSessionBusy?: () => void;
165
+ /**
166
+ * Fires once when the session transitions from busy back to idle
167
+ * (all RPC promises settled and all stream exports released). The
168
+ * Sandbox DO wires this to `inflightRequests = max(0, n-1)` and a
169
+ * final `renewActivityTimeout()`, matching containerFetch's finally
170
+ * block.
171
+ */
172
+ onSessionIdle?: () => void;
173
+ }
174
+ /**
175
+ * Sandbox control facade backed by direct capnweb RPC.
176
+ *
177
+ * All operations call the container's SandboxAPI control interface directly
178
+ * over capnweb, bypassing the HTTP handler/router layer entirely.
179
+ *
180
+ * Manages its own WebSocket lifecycle: a fresh `ContainerControlConnection` is
181
+ * created on demand and torn down after `idleDisconnectMs` of inactivity.
182
+ * Busy/idle detection relies on `RpcSession.getStats()` which tracks all
183
+ * in-flight RPC calls and stream exports — including long-lived streaming
184
+ * RPCs that would be invisible to a simple per-call request counter (see
185
+ * the file-level comment for the full rationale).
186
+ */
187
+ declare class ContainerControlClient {
188
+ private readonly connOptions;
189
+ private readonly idleDisconnectMs;
190
+ private readonly busyPollIntervalMs;
191
+ private readonly logger;
192
+ private readonly onActivity;
193
+ private readonly onSessionBusy;
194
+ private readonly onSessionIdle;
195
+ private conn;
196
+ private idleTimer;
197
+ private busyPollTimer;
198
+ /** Tracks whether we currently believe the session is busy. */
199
+ private busy;
200
+ constructor(options: ContainerControlClientOptions);
201
+ /**
202
+ * Return the current connection, creating one when the client is disconnected.
203
+ * Starts the busy-poll timer the first time a connection is materialized.
204
+ */
205
+ private getConnection;
206
+ /**
207
+ * Called synchronously at the start of each RPC method invocation.
208
+ * Renews the DO activity timeout so the sleepAfter alarm is pushed
209
+ * forward before the container processes the call.
210
+ */
211
+ private renewActivity;
212
+ /**
213
+ * Sample `getStats()` and update busy/idle state. While busy, renews the
214
+ * activity timeout each tick so an in-flight stream keeps pushing the
215
+ * sleepAfter deadline forward. On the busy → idle edge, fires
216
+ * `onSessionIdle` and schedules the WebSocket disconnect.
217
+ *
218
+ * If the WebSocket has dropped underneath us (container crash, network
219
+ * blip) we tear the connection down here. `destroyConnection()` fires
220
+ * `onSessionIdle` if we were busy, so the DO's inflight counter doesn't
221
+ * stay pinned forever waiting for a peer that's never going to reply.
222
+ */
223
+ private pollBusyState;
224
+ private startBusyPoll;
225
+ private stopBusyPoll;
226
+ private scheduleIdleDisconnect;
227
+ private clearIdleTimer;
228
+ private destroyConnection;
229
+ get commands(): SandboxCommandsAPI;
230
+ get files(): SandboxFilesAPI;
231
+ get processes(): SandboxProcessesAPI;
232
+ get ports(): SandboxPortsAPI;
233
+ get git(): SandboxGitAPI;
234
+ get utils(): SandboxUtilsAPI;
235
+ get backup(): SandboxBackupAPI;
236
+ get watch(): SandboxWatchAPI;
237
+ get tunnels(): SandboxTunnelsAPI;
238
+ get interpreter(): SandboxInterpreterAPI;
239
+ get extensions(): SandboxExtensionsAPI;
240
+ /**
241
+ * Update the upgrade retry budget. Applies to the current connection
242
+ * (if any) and is remembered for any future connections created after the
243
+ * client is torn down and reconnected.
244
+ */
245
+ setRetryTimeoutMs(ms: number): void;
246
+ isWebSocketConnected(): boolean;
247
+ connect(): Promise<void>;
248
+ disconnect(): void;
249
+ }
250
+ //#endregion
251
+ //#region src/tunnels/tunnels-handler.d.ts
252
+
253
+ interface TunnelsHandler {
254
+ get(port: number, options?: TunnelOptions): Promise<TunnelInfo>;
255
+ list(): Promise<TunnelInfo[]>;
256
+ destroy(portOrInfo: number | TunnelInfo): Promise<void>;
257
+ }
258
+ //#endregion
259
+ //#region src/sandbox.d.ts
260
+ type SandboxConfiguration = {
261
+ sandboxName?: {
262
+ name: string;
263
+ normalizeId?: boolean;
264
+ };
265
+ sleepAfter?: string | number;
266
+ keepAlive?: boolean;
267
+ containerTimeouts?: NonNullable<SandboxOptions['containerTimeouts']>;
268
+ };
269
+ /**
270
+ * SDK-level ContainerProxy that directly dispatches SDK-internal mount hosts
271
+ * (r2.internal, s3-credential-proxy.internal) without relying on
272
+ * outboundHandlersRegistry lookups, which are NOT shared between the Durable
273
+ * Object's execution context and the ContainerProxy WorkerEntrypoint context.
274
+ *
275
+ * Users must export this class from their Worker entrypoint so the Sandbox DO
276
+ * can create outbound-interception fetchers that reference it.
277
+ */
278
+ declare class ContainerProxy$1 extends ContainerProxy {
279
+ fetch(request: Request): Promise<Response>;
280
+ }
281
+ declare function getSandbox<T extends Sandbox<any>>(ns: DurableObjectNamespace<T>, id: string, options?: SandboxOptions): T;
282
+ declare class Sandbox<Env = unknown> extends Container<Env> implements ISandbox {
283
+ defaultPort: number;
284
+ sleepAfter: string | number;
285
+ client: ContainerControlClient;
286
+ private codeInterpreter;
287
+ private sandboxName;
288
+ private tunnelsHandler;
289
+ private tunnelExitHandler;
290
+ private destroyAllTunnels;
291
+ private readonly controlCallback;
292
+ private normalizeId;
293
+ private defaultSession;
294
+ private containerGeneration;
295
+ private defaultSessionInit;
296
+ envVars: Record<string, string>;
297
+ private logger;
298
+ private keepAliveEnabled;
299
+ private activeMounts;
300
+ private mountOperationQueue;
301
+ private currentRuntime;
302
+ private backupBucket;
303
+ /**
304
+ * Serializes backup operations to prevent concurrent create/restore on the same sandbox.
305
+ *
306
+ * This is in-memory state — it resets if the Durable Object is evicted and
307
+ * re-instantiated (e.g. after sleep). This is acceptable because the container
308
+ * filesystem is also lost on eviction, so there is no archive to race on.
309
+ */
310
+ private backupInProgress;
311
+ /**
312
+ * R2 presigned URL credentials for direct container-to-R2 transfers.
313
+ * All four fields plus the R2 binding must be configured for backup to work.
314
+ */
315
+ private r2AccessKeyId;
316
+ private r2SecretAccessKey;
317
+ private r2AccountId;
318
+ private backupBucketName;
319
+ private backupBucketEndpoint;
320
+ private r2Client;
321
+ /**
322
+ * Lazily-resolved Cloudflare account id for named-tunnel provisioning.
323
+ * Resolved on first access via `tunnels/credentials.ts` and cached for
324
+ * the lifetime of this DO instance. See the credentials helper for
325
+ * the precedence chain.
326
+ */
327
+ private tunnelAccountIdPromise;
328
+ /**
329
+ * Lazily-resolved Cloudflare zone id for named-tunnel provisioning.
330
+ * Falls back to the single zone the token can see under the resolved
331
+ * account id when `CLOUDFLARE_ZONE_ID` is not set. Cached for the
332
+ * lifetime of this DO instance.
333
+ */
334
+ private tunnelZoneIdPromise;
335
+ /**
336
+ * Default container startup timeouts (conservative for production)
337
+ * Based on Cloudflare docs: "Containers take several minutes to provision"
338
+ */
339
+ private readonly DEFAULT_CONTAINER_TIMEOUTS;
340
+ /**
341
+ * Active container timeout configuration
342
+ * Can be set via options, env vars, or defaults
343
+ */
344
+ private containerTimeouts;
345
+ /**
346
+ * True once containerTimeouts has been written to storage at least once
347
+ * (either via setContainerTimeouts or restored on cold start). Gates the
348
+ * idempotency check in setContainerTimeouts so a first explicit call
349
+ * persists even when the requested values already equal the in-memory
350
+ * defaults, distinguishing "user intent recorded" from "running on
351
+ * env/SDK defaults".
352
+ */
353
+ private hasStoredContainerTimeouts;
354
+ /**
355
+ * Dispatch method for tunnel operations.
356
+ * Called by the client-side proxy created in getSandbox() to provide
357
+ * the `sandbox.tunnels` API without relying on RPC pipelining
358
+ * through property getters which is broken when using vite-plugin.
359
+ */
360
+ callTunnels(method: string, args: unknown[]): Promise<unknown>;
361
+ /**
362
+ * Compute the control-channel upgrade retry budget from current container
363
+ * timeouts.
364
+ *
365
+ * The budget covers the full container startup window (instance provisioning
366
+ * + port readiness) plus a 30s margin for the maximum single backoff delay.
367
+ * The 120s floor preserves the default for short timeout configurations.
368
+ */
369
+ private computeRetryTimeoutMs;
370
+ /**
371
+ * Create the single control-plane client used for all SDK operations.
372
+ */
373
+ private createClient;
374
+ constructor(ctx: DurableObjectState<{}>, env: Env);
375
+ setSandboxName(name: string, normalizeId?: boolean): Promise<void>;
376
+ configure(configuration: SandboxConfiguration): Promise<void>;
377
+ setSleepAfter(sleepAfter: string | number): Promise<void>;
378
+ setKeepAlive(keepAlive: boolean): Promise<void>;
379
+ setEnvVars(envVars: Record<string, string | undefined>): Promise<void>;
380
+ setContainerTimeouts(timeouts: NonNullable<SandboxOptions['containerTimeouts']>): Promise<void>;
381
+ private validateTimeout;
382
+ private getDefaultTimeouts;
383
+ /**
384
+ * Mount an S3-compatible bucket as a local directory.
385
+ *
386
+ * Requires explicit endpoint URL for production. Credentials are auto-detected from environment
387
+ * variables or can be provided explicitly.
388
+ *
389
+ * @param bucket - Bucket name (or R2 binding name when localBucket is true)
390
+ * @param mountPath - Absolute path in container to mount at
391
+ * @param options - Mount configuration
392
+ * @throws MissingCredentialsError if no credentials found in environment
393
+ * @throws S3FSMountError if S3FS mount command fails
394
+ * @throws InvalidMountConfigError if bucket name, mount path, or endpoint is invalid
395
+ */
396
+ mountBucket(bucket: string, mountPath: string, options: MountBucketOptions): Promise<void>;
397
+ private runMountOperation;
398
+ private mountBucketUnlocked;
399
+ /**
400
+ * Local dev mount: bidirectional sync via R2 binding + file/watch APIs
401
+ */
402
+ private mountBucketLocal;
403
+ private getR2EgressParams;
404
+ private validateProtectedS3fsOptions;
405
+ private getS3CredentialProxyParams;
406
+ private resolveCredentialProxyAuthStrategy;
407
+ /**
408
+ * Credential-less R2 mount: egress interception routes s3fs requests to the
409
+ * R2 binding. No S3 credentials are needed in the container or Worker env.
410
+ */
411
+ private mountBucketR2Egress;
412
+ /**
413
+ * Production mount: S3FS-FUSE inside the container
414
+ */
415
+ private mountBucketFuse;
416
+ /**
417
+ * Manually unmount a bucket filesystem
418
+ *
419
+ * @param mountPath - Absolute path where the bucket is mounted
420
+ * @throws InvalidMountConfigError if mount path doesn't exist or isn't mounted
421
+ */
422
+ unmountBucket(mountPath: string): Promise<void>;
423
+ private unmountBucketUnlocked;
424
+ /**
425
+ * Shared validation for mount path (absolute, not already in use).
426
+ */
427
+ private validateMountPath;
428
+ /**
429
+ * Validate mount options for remote (FUSE) mounts
430
+ */
431
+ private validateMountOptions;
432
+ /**
433
+ * Generate unique password file path for s3fs credentials
434
+ */
435
+ private generatePasswordFilePath;
436
+ /**
437
+ * Generate unique ahbe_conf file path for s3fs additional header config
438
+ */
439
+ private generateS3FSAdditionalHeaderFilePath;
440
+ /**
441
+ * Create s3fs ahbe_conf file that suppresses the Expect: 100-continue header.
442
+ * Restricted to 0600 so s3fs will accept it (same requirement as passwd files).
443
+ */
444
+ private createDisableExpectHeaderFile;
445
+ /**
446
+ * Create password file with s3fs credentials
447
+ * Format: bucket:accessKeyId:secretAccessKey
448
+ */
449
+ private createPasswordFile;
450
+ /**
451
+ * Delete password file
452
+ */
453
+ private deletePasswordFile;
454
+ private deleteAdditionalHeaderFile;
455
+ /**
456
+ * Execute S3FS mount command
457
+ */
458
+ private executeS3FSMount;
459
+ private unmountTrackedFuseMount;
460
+ /**
461
+ * In-flight `destroy()` promise. While set, concurrent callers coalesce
462
+ * onto the same teardown instead of triggering a second one. Cleared when
463
+ * the underlying work settles, so a later call that genuinely needs to
464
+ * recreate a destroyed sandbox still runs.
465
+ *
466
+ * If the underlying teardown hangs (e.g. `super.destroy()` never resolves
467
+ * because the Containers control plane is unresponsive), every coalesced
468
+ * caller hangs on the same promise until the Durable Object is evicted.
469
+ * This is deliberate: a second concurrent teardown would not make a stuck
470
+ * control plane unstuck, and spawning one would defeat the point of
471
+ * coalescing. Callers that need bounded waits must apply their own
472
+ * timeout around `destroy()`.
473
+ */
474
+ private inflightDestroy;
475
+ /**
476
+ * Cleanup and destroy the sandbox container.
477
+ *
478
+ * Concurrent calls coalesce: if a previous `destroy()` is still in flight,
479
+ * subsequent calls await the same underlying work instead of starting a
480
+ * second teardown. A canonical `sandbox.destroy.coalesced` event is logged
481
+ * per coalesced call so repeated destroy traffic is observable.
482
+ */
483
+ destroy(): Promise<void>;
484
+ private doDestroy;
485
+ onStart(): Promise<void>;
486
+ stop(signal?: Parameters<Container<Env>['stop']>[0]): Promise<void>;
487
+ /**
488
+ * Read the `portTokens` map from DO storage, normalizing the legacy
489
+ * string-valued format (just a token) to the current object format
490
+ * ({ token, name? }). The legacy format predates port-name persistence and
491
+ * can appear on any DO whose storage was written before that change.
492
+ */
493
+ private readPortTokens;
494
+ private readActivePreviewPorts;
495
+ private writeActivePreviewPorts;
496
+ private readPreviewState;
497
+ private clearActivePreviewPorts;
498
+ /**
499
+ * Check if the container version matches the SDK version
500
+ * Logs a warning if there's a mismatch
501
+ */
502
+ private checkVersionCompatibility;
503
+ onStop(): Promise<void>;
504
+ onError(error: unknown): void;
505
+ /**
506
+ * Override Container.containerFetch to use production-friendly timeouts
507
+ * Automatically starts container with longer timeouts if not running
508
+ */
509
+ containerFetch(requestOrUrl: Request | string | URL, portOrInit?: number | RequestInit, portParam?: number): Promise<Response>;
510
+ /**
511
+ * Helper: Check if error is "no container instance available"
512
+ * This indicates the container VM is still being provisioned.
513
+ */
514
+ private isNoInstanceError;
515
+ /**
516
+ * Helper: Check if error is a transient startup error that should trigger retry
517
+ *
518
+ * These errors occur during normal container startup and are recoverable:
519
+ * - Port not yet mapped (container starting, app not listening yet)
520
+ * - Connection refused (port mapped but app not ready)
521
+ * - Timeouts during startup (recoverable with retry)
522
+ * - Network transients (temporary connectivity issues)
523
+ *
524
+ * Errors NOT included (permanent failures):
525
+ * - "no such image" - missing Docker image
526
+ * - "container already exists" - name collision
527
+ * - Configuration errors
528
+ */
529
+ private isTransientStartupError;
530
+ /**
531
+ * Helper: Check if error is a permanent startup failure that will never recover
532
+ *
533
+ * These errors indicate resource exhaustion, misconfiguration, or missing images.
534
+ * Retrying will never succeed, so the SDK should fail fast with HTTP 500.
535
+ *
536
+ * Error sources (traced from platform internals):
537
+ * - Container runtime: OOM, PID limit
538
+ * - Scheduling/provisioning: no matching app, no namespace configured
539
+ * - workerd container-client.c++: no such image
540
+ * - @cloudflare/containers: did not call start
541
+ */
542
+ private isPermanentStartupError;
543
+ /**
544
+ * Helper: Parse containerFetch arguments (supports multiple signatures)
545
+ */
546
+ private parseContainerFetchArgs;
547
+ /**
548
+ * Override onActivityExpired to prevent automatic shutdown when keepAlive is enabled
549
+ * When keepAlive is disabled, calls parent implementation which stops the container
550
+ */
551
+ onActivityExpired(): Promise<void>;
552
+ private isPreviewProxyRequest;
553
+ private invalidPreviewTokenResponse;
554
+ private stalePreviewURLResponse;
555
+ private getPreviewForwardingContainer;
556
+ private beginPreviewForward;
557
+ private fetchPreviewIfRunning;
558
+ private buildPreviewProxyRequest;
559
+ private proxyPreviewRequest;
560
+ fetch(request: Request): Promise<Response>;
561
+ wsConnect(request: Request, port: number): Promise<Response>;
562
+ private determinePort;
563
+ /**
564
+ * Return the default session id, lazily creating the container session
565
+ * on first use. Called by every public method that needs a session.
566
+ * Concurrent callers that target the same sessionId share one
567
+ * in-flight initialization promise.
568
+ */
569
+ private ensureDefaultSession;
570
+ private getOrInitializeDefaultSession;
571
+ private isDefaultSessionInitContainerRestart;
572
+ private initializeDefaultSession;
573
+ /**
574
+ * Persist the container's placement ID in DO storage.
575
+ *
576
+ * Called from the session-create handshake so subsequent reads via
577
+ * `getContainerPlacementId()` do not require a round-trip to the container. The value
578
+ * is overwritten on every handshake so that container replacements (which
579
+ * assign a new placement ID) are reflected on the next session-create.
580
+ *
581
+ * A value of `undefined` means the handshake response omitted the field
582
+ * (older container, unexpected error shape) and the stored value is left
583
+ * untouched. `null` means the env var is not set in the container and is
584
+ * stored as-is so callers can distinguish "observed and absent" from "not
585
+ * yet observed."
586
+ */
587
+ private capturePlacementId;
588
+ private resolveExecution;
589
+ private validateExplicitSessionId;
590
+ private serializeExecutionContext;
591
+ private getPublicExecutionSessionId;
592
+ /**
593
+ * Resolves the session ID to annotate returned Process objects.
594
+ *
595
+ * Unlike `resolveExecution`, this is synchronous and never creates a
596
+ * session. When the default session hasn't been established yet, it returns
597
+ * `undefined` rather than triggering session creation. The resolved value is
598
+ * only used to populate `Process.sessionId` on the returned object — it is
599
+ * never sent to the container API.
600
+ */
601
+ private getProcessSessionBinding;
602
+ private resolveExecutionEnv;
603
+ private buildExecutionRequestOptions;
604
+ exec(command: string, options?: ExecOptions): Promise<ExecResult>;
605
+ execWithSessionToken(command: string, sessionId: string, options?: ExecOptions): Promise<ExecResult>;
606
+ /**
607
+ * Execute an infrastructure command (backup, mount, env setup, etc.)
608
+ * tagged with origin: 'internal' so logging demotes it to debug level.
609
+ */
610
+ private execInternal;
611
+ /**
612
+ * Internal session-aware exec implementation
613
+ * Used by both public exec() and session wrappers
614
+ */
615
+ private execWithSession;
616
+ private executeWithStreaming;
617
+ private mapExecuteResponseToExecResult;
618
+ /**
619
+ * Create a Process domain object from HTTP client DTO
620
+ * Centralizes process object creation with bound methods
621
+ * This eliminates duplication across startProcess, listProcesses, getProcess, and session wrappers
622
+ */
623
+ private createProcessFromDTO;
624
+ /**
625
+ * Wait for a log pattern to appear in process output
626
+ */
627
+ private waitForLogPattern;
628
+ /**
629
+ * Wait for a port to become available (for process readiness checking)
630
+ */
631
+ private waitForPortReady;
632
+ /**
633
+ * Wait for a process to exit
634
+ * Returns the exit code
635
+ */
636
+ private waitForProcessExit;
637
+ /**
638
+ * Match a pattern against text
639
+ */
640
+ private matchPattern;
641
+ /**
642
+ * Convert a log pattern to a human-readable string
643
+ */
644
+ private conditionToString;
645
+ /**
646
+ * Create a ProcessReadyTimeoutError
647
+ */
648
+ private createReadyTimeoutError;
649
+ /**
650
+ * Create a ProcessExitedBeforeReadyError
651
+ */
652
+ private createExitedBeforeReadyError;
653
+ startProcess(command: string, options?: ProcessOptions, sessionId?: string): Promise<Process>;
654
+ /**
655
+ * Start background streaming for process callbacks
656
+ * Opens SSE stream to container and routes events to callbacks
657
+ */
658
+ private startProcessCallbackStream;
659
+ listProcesses(sessionId?: string): Promise<Process[]>;
660
+ getProcess(id: string, sessionId?: string): Promise<Process | null>;
661
+ killProcess(id: string, signal?: string, sessionId?: string): Promise<void>;
662
+ killAllProcesses(sessionId?: string): Promise<number>;
663
+ cleanupCompletedProcesses(sessionId?: string): Promise<number>;
664
+ getProcessLogs(id: string, sessionId?: string): Promise<{
665
+ stdout: string;
666
+ stderr: string;
667
+ processId: string;
668
+ }>;
669
+ execStream(command: string, options?: StreamOptions): Promise<ReadableStream<Uint8Array>>;
670
+ execStreamWithSessionToken(command: string, sessionId: string, options?: StreamOptions): Promise<ReadableStream<Uint8Array>>;
671
+ /**
672
+ * Internal session-aware execStream implementation
673
+ */
674
+ private execStreamWithSession;
675
+ /**
676
+ * Stream logs from a background process as a ReadableStream.
677
+ */
678
+ streamProcessLogs(processId: string, options?: {
679
+ signal?: AbortSignal;
680
+ }): Promise<ReadableStream<Uint8Array>>;
681
+ gitCheckout(repoUrl: string, options?: {
682
+ branch?: string;
683
+ targetDir?: string;
684
+ sessionId?: string;
685
+ /** Clone depth for shallow clones (e.g., 1 for latest commit only) */
686
+ depth?: number;
687
+ /** Maximum wall-clock time for the git clone subprocess in milliseconds */
688
+ cloneTimeoutMs?: number;
689
+ }): Promise<GitCheckoutResult>;
690
+ mkdir(path: string, options?: {
691
+ recursive?: boolean;
692
+ sessionId?: string;
693
+ }): Promise<MkdirResult>;
694
+ writeFile(path: string, content: string | ReadableStream<Uint8Array>, options?: {
695
+ encoding?: string;
696
+ sessionId?: string;
697
+ }): Promise<{
698
+ success: boolean;
699
+ path: string;
700
+ bytesWritten: number;
701
+ timestamp: string;
702
+ } | WriteFileResult>;
703
+ deleteFile(path: string, sessionId?: string): Promise<DeleteFileResult>;
704
+ renameFile(oldPath: string, newPath: string, sessionId?: string): Promise<RenameFileResult>;
705
+ moveFile(sourcePath: string, destinationPath: string, sessionId?: string): Promise<MoveFileResult>;
706
+ /**
707
+ * Read a file from the sandbox.
708
+ *
709
+ * @param encoding - How to encode the returned content:
710
+ * - `undefined` (default): auto-detect from MIME type (text → UTF-8 string, binary → base64 string)
711
+ * - `'utf-8'` / `'utf8'`: always return as UTF-8 string
712
+ * - `'base64'`: always return as base64-encoded string
713
+ * - `'none'`: return a result whose `content` is a raw binary `ReadableStream<Uint8Array>`
714
+ * with no encoding overhead.
715
+ */
716
+ readFile(path: string, options: {
717
+ encoding: 'none';
718
+ sessionId?: string;
719
+ }): Promise<ReadFileStreamResult>;
720
+ readFile(path: string, options?: {
721
+ encoding?: Exclude<FileEncoding, 'none'>;
722
+ sessionId?: string;
723
+ }): Promise<ReadFileResult>;
724
+ /**
725
+ * Stream a file from the sandbox using Server-Sent Events
726
+ * Returns a ReadableStream that can be consumed with streamFile() or collectFile() utilities
727
+ * @param path - Path to the file to stream
728
+ * @param options - Optional session ID
729
+ */
730
+ readFileStream(path: string, options?: {
731
+ sessionId?: string;
732
+ }): Promise<ReadableStream<Uint8Array>>;
733
+ listFiles(path: string, options?: ListFilesOptions): Promise<ListFilesResult>;
734
+ exists(path: string, sessionId?: string): Promise<FileExistsResult>;
735
+ /**
736
+ * Watch a directory for file system changes using native inotify.
737
+ *
738
+ * The returned promise resolves only after the watcher is established on the
739
+ * filesystem, so callers can immediately perform actions that depend on the
740
+ * watch being active. The returned stream contains the full event sequence
741
+ * starting with the `watching` event.
742
+ *
743
+ * Consume the stream with `parseSSEStream<FileWatchSSEEvent>(stream)`.
744
+ *
745
+ * @param path - Path to watch (absolute or relative to /workspace)
746
+ * @param options - Watch options
747
+ */
748
+ watch(path: string, options?: WatchOptions): Promise<ReadableStream<Uint8Array>>;
749
+ /**
750
+ * Check whether a path changed while this caller was disconnected.
751
+ *
752
+ * Pass the `version` returned from a prior call in `options.since` to learn
753
+ * whether the path is unchanged, changed, or needs a full resync because the
754
+ * retained change state was reset.
755
+ *
756
+ * @param path - Path to check (absolute or relative to /workspace)
757
+ * @param options - Change-check options
758
+ */
759
+ checkChanges(path: string, options?: CheckChangesOptions): Promise<CheckChangesResult>;
760
+ /**
761
+ * Expose a port and get a preview URL for accessing services running in the sandbox
762
+ *
763
+ * Preview URL authorization survives transient container restarts, but
764
+ * forwarding is active only for the runtime where `exposePort()` was last
765
+ * called. Call `exposePort()` again after a restart to reactivate an
766
+ * existing URL for the current runtime.
767
+ *
768
+ * @param port - Port number to expose (1024-65535)
769
+ * @param options - Configuration options
770
+ * @param options.hostname - Your Worker's domain name (required for preview URL construction)
771
+ * @param options.name - Optional friendly name for the port
772
+ * @param options.token - Optional custom token for the preview URL (1-16 characters: lowercase letters, numbers, underscores)
773
+ * If not provided, a random 16-character token will be generated automatically
774
+ * @returns Preview URL information including the full URL, port number, and optional name
775
+ *
776
+ * @example
777
+ * // With auto-generated token
778
+ * const { url } = await sandbox.exposePort(8080, { hostname: 'example.com' });
779
+ * // url: https://8080-sandbox-id-abc123random4567.example.com
780
+ *
781
+ * @example
782
+ * // With custom token for stable URLs across deployments
783
+ * const { url } = await sandbox.exposePort(8080, {
784
+ * hostname: 'example.com',
785
+ * token: 'my_token_v1'
786
+ * });
787
+ * // url: https://8080-sandbox-id-my_token_v1.example.com
788
+ */
789
+ exposePort(port: number, options: {
790
+ name?: string;
791
+ hostname: string;
792
+ token?: string;
793
+ }): Promise<{
794
+ url: string;
795
+ port: number;
796
+ name: string | undefined;
797
+ }>;
798
+ /**
799
+ * Revoke preview URL authorization and current-runtime activation for a port.
800
+ *
801
+ * Revocation is idempotent: calling this for a port with no preview state is
802
+ * still successful. The operation clears Durable Object-owned preview state
803
+ * only and does not contact, probe, wake, or clean up the container runtime.
804
+ */
805
+ unexposePort(port: number): Promise<void>;
806
+ /**
807
+ * Returns preview URLs that are currently forwardable in the active runtime.
808
+ * Durable authorization without current-runtime activation is omitted.
809
+ */
810
+ getExposedPorts(hostname: string): Promise<{
811
+ url: string;
812
+ port: number;
813
+ status: "active";
814
+ }[]>;
815
+ /**
816
+ * Namespaced tunnel API. Quick tunnels are zero-config preview URLs
817
+ * backed by Cloudflare's trycloudflare service. Named tunnels bind a
818
+ * stable hostname under the configured Cloudflare zone.
819
+ *
820
+ * - `tunnels.get(port)` — idempotent. Returns the cached tunnel for
821
+ * `port` if one exists in DO storage, otherwise spawns a fresh
822
+ * cloudflared process and persists the record.
823
+ * - `tunnels.list()` — records currently known to this sandbox, from
824
+ * DO storage.
825
+ * - `tunnels.destroy(portOrInfo)` — tear down by port number or by
826
+ * the record returned from `get()`.
827
+ *
828
+ * Container restarts drop quick-tunnel records because their
829
+ * `*.trycloudflare.com` URLs are tied to the dead cloudflared process.
830
+ * Named-tunnel records stay in storage and are marked for respawn so the
831
+ * next `get(port, { name })` call reuses the Cloudflare tunnel and DNS
832
+ * record while starting a fresh cloudflared process.
833
+ */
834
+ get tunnels(): TunnelsHandler;
835
+ /**
836
+ * Lazily construct both the public tunnels handler and its sibling
837
+ * exit-handler callback. Called from the `tunnels` getter on first
838
+ * access.
839
+ */
840
+ private ensureTunnelsBuilt;
841
+ /**
842
+ * Resolve the Cloudflare account id used for named-tunnel provisioning.
843
+ *
844
+ * Memoised for the lifetime of this DO instance. The first call may hit
845
+ * `GET /user/tokens/verify` to derive the account id from the configured
846
+ * `CLOUDFLARE_API_TOKEN`; subsequent calls return the cached promise.
847
+ *
848
+ * Only successful resolutions are cached: a rejected lookup clears the
849
+ * slot so the next caller retries. Otherwise a transient failure on
850
+ * first use would permanently poison every later named-tunnel `get()`
851
+ * on this DO instance.
852
+ */
853
+ private getTunnelAccountId;
854
+ /**
855
+ * Resolve the Cloudflare zone id used for named-tunnel provisioning.
856
+ *
857
+ * Memoised for the lifetime of this DO instance. Falls back to the
858
+ * single zone the token can see under `accountId` via `GET /zones`
859
+ * when `CLOUDFLARE_ZONE_ID` is not set. Failed lookups clear the cache
860
+ * so the next caller retries — see `getTunnelAccountId` for the
861
+ * rationale.
862
+ */
863
+ private getTunnelZoneId;
864
+ /**
865
+ * Returns whether a port is currently preview-forwardable.
866
+ * This checks Durable Object-owned auth and runtime activation without
867
+ * contacting or waking the container.
868
+ */
869
+ isPortExposed(port: number): Promise<boolean>;
870
+ /**
871
+ * Checks durable preview URL authorization for a port/token pair.
872
+ *
873
+ * This does not check whether the port is activated for the current runtime
874
+ * and is not sufficient to decide whether preview traffic may forward.
875
+ */
876
+ validatePortToken(port: number, token: string): Promise<boolean>;
877
+ private validatePreviewURLForRuntime;
878
+ private getCurrentPreviewPorts;
879
+ private previewTokensMatch;
880
+ private validateCustomToken;
881
+ private generatePortToken;
882
+ private constructPreviewUrl;
883
+ /**
884
+ * Create isolated execution session for advanced use cases
885
+ * Returns ExecutionSession with full sandbox API bound to specific session
886
+ */
887
+ createSession(options?: SessionOptions): Promise<ExecutionSession>;
888
+ /**
889
+ * Get an existing session by ID
890
+ * Returns ExecutionSession wrapper bound to the specified session
891
+ *
892
+ * This is useful for retrieving sessions across different requests/contexts
893
+ * without storing the ExecutionSession object (which has RPC lifecycle limitations)
894
+ *
895
+ * @param sessionId - The ID of an existing session
896
+ * @returns ExecutionSession wrapper bound to the session
897
+ */
898
+ getSession(sessionId: string): Promise<ExecutionSession>;
899
+ /**
900
+ * Delete an execution session
901
+ * Cleans up session resources and removes it from the container
902
+ * Note: Cannot delete the default session. To reset the default session,
903
+ * use sandbox.destroy() to terminate the entire sandbox.
904
+ *
905
+ * @param sessionId - The ID of the session to delete
906
+ * @returns Result with success status, sessionId, and timestamp
907
+ * @throws Error if attempting to delete the default session
908
+ */
909
+ deleteSession(sessionId: string): Promise<SessionDeleteResult>;
910
+ /**
911
+ * Get the Cloudflare placement ID observed for the underlying container.
912
+ *
913
+ * The placement ID is captured during the first session-create handshake
914
+ * after a container start and stored in Durable Object storage, so this
915
+ * method returns the cached value without contacting the container. A new
916
+ * placement ID is captured on each subsequent session-create handshake,
917
+ * which occurs whenever the container has been replaced.
918
+ *
919
+ * Returns `null` when a handshake has completed but the container's
920
+ * `CLOUDFLARE_PLACEMENT_ID` environment variable is not set (for example,
921
+ * in local development).
922
+ *
923
+ * Returns `undefined` when no handshake has been observed yet on this
924
+ * sandbox. Call any method that triggers session creation (such as
925
+ * `exec()`) to populate the value.
926
+ */
927
+ getContainerPlacementId(): Promise<string | null | undefined>;
928
+ private getSessionWrapper;
929
+ createCodeContext(options?: CreateContextOptions): Promise<CodeContext>;
930
+ runCode(code: string, options?: RunCodeOptions): Promise<ExecutionResult>;
931
+ runCodeStream(code: string, options?: RunCodeOptions): Promise<ReadableStream>;
932
+ listCodeContexts(): Promise<CodeContext[]>;
933
+ deleteCodeContext(contextId: string): Promise<void>;
934
+ /** UUID v4 format validator for backup IDs */
935
+ private static readonly UUID_REGEX;
936
+ /**
937
+ * Validate that a directory path is safe for backup operations.
938
+ * Rejects empty, relative, traversal, null-byte, and unsupported-root paths.
939
+ */
940
+ private static validateBackupDir;
941
+ /**
942
+ * Returns the R2 bucket or throws if backup is not configured.
943
+ */
944
+ private requireBackupBucket;
945
+ private normalizeBackupExcludes;
946
+ private resolveBackupCompression;
947
+ private static readonly PRESIGNED_URL_EXPIRY_SECONDS;
948
+ /**
949
+ * Create a unique, dedicated session for a single backup operation.
950
+ * Each call produces a fresh session ID so concurrent or sequential
951
+ * operations never share shell state. Callers must destroy the session
952
+ * in a finally block via `client.utils.deleteSession()`.
953
+ */
954
+ private ensureBackupSession;
955
+ /**
956
+ * Returns validated presigned URL configuration or throws if not configured.
957
+ * All credential fields plus the R2 binding are required for backup to work.
958
+ */
959
+ private requirePresignedURLSupport;
960
+ private getBackupBucketEndpoint;
961
+ private getBackupObjectURL;
962
+ /**
963
+ * Generate a presigned GET URL for downloading an object from R2.
964
+ * The container can curl this URL directly without credentials.
965
+ */
966
+ private generatePresignedGetURL;
967
+ /**
968
+ * Generate a presigned PUT URL for uploading an object to R2.
969
+ * The container can curl PUT to this URL directly without credentials.
970
+ */
971
+ private generatePresignedPutURL;
972
+ /**
973
+ * Upload a backup archive via presigned PUT URL.
974
+ * The container curls the archive directly to R2, bypassing the DO.
975
+ * ~24 MB/s throughput vs ~0.6 MB/s for base64 readFile.
976
+ */
977
+ private uploadBackupPresigned;
978
+ /**
979
+ * Generate a presigned PUT URL for a single part in a multipart upload.
980
+ */
981
+ private generatePresignedPartURL;
982
+ /**
983
+ * Upload a backup archive to R2 using parallel multipart upload.
984
+ * Uses the S3-compatible API exclusively for create/complete/abort so that
985
+ * the uploadId is in the same namespace as the presigned part PUT URLs.
986
+ */
987
+ private uploadBackupMultipart;
988
+ /**
989
+ * Download a backup archive from R2 via presigned GET URL.
990
+ * For archives >= BACKUP_DOWNLOAD_PARALLEL_MIN_SIZE, uses BACKUP_DOWNLOAD_PARALLEL_PARTS
991
+ * concurrent curl processes (each downloading a byte-range) to maximise both
992
+ * network and disk-write throughput. Parts are written into a pre-sized file
993
+ * with dd using byte offsets, then atomically moved to the final path.
994
+ */
995
+ private downloadBackupParallel;
996
+ /**
997
+ * Serialize backup operations on this sandbox instance.
998
+ * Concurrent backup/restore calls are queued so the multi-step
999
+ * create-archive → read → upload (or mount → extract) flow
1000
+ * is not interleaved with another backup operation on the same directory.
1001
+ */
1002
+ private enqueueBackupOp;
1003
+ /**
1004
+ * Create a backup of a directory and upload it to R2.
1005
+ *
1006
+ * Flow:
1007
+ * 1. Container creates squashfs archive from the directory
1008
+ * 2. Container uploads the archive directly to R2 via presigned URL
1009
+ * 3. DO writes metadata to R2
1010
+ * 4. Container cleans up the local archive
1011
+ *
1012
+ * The returned DirectoryBackup handle is serializable. Store it anywhere
1013
+ * (KV, D1, DO storage) and pass it to restoreBackup() later.
1014
+ *
1015
+ * Concurrent backup/restore calls on the same sandbox are serialized.
1016
+ *
1017
+ * Partially-written files in the target directory may not be captured
1018
+ * consistently. Completed writes are captured.
1019
+ *
1020
+ * NOTE: Expired backups are not automatically deleted from R2. Configure
1021
+ * R2 lifecycle rules on the BACKUP_BUCKET to garbage-collect objects
1022
+ * under the `backups/` prefix after the desired retention period.
1023
+ */
1024
+ createBackup(options: BackupOptions): Promise<DirectoryBackup>;
1025
+ private doCreateBackup;
1026
+ /**
1027
+ * Local-dev implementation of createBackup.
1028
+ * Uses the R2 binding directly instead of presigned URLs.
1029
+ * Archive format is identical to production (squashfs + meta.json).
1030
+ */
1031
+ private doCreateBackupLocal;
1032
+ /**
1033
+ * Restore a backup from R2 into a directory.
1034
+ *
1035
+ * **Production flow** (`localBucket` not set):
1036
+ * 1. DO reads metadata from R2 and checks TTL
1037
+ * 2. Container mounts the backup archive from R2 via s3fs
1038
+ * 3. Container mounts the squashfs archive with FUSE overlayfs
1039
+ *
1040
+ * The target directory becomes an overlay mount with the backup as a
1041
+ * read-only lower layer and a writable upper layer for copy-on-write.
1042
+ * Any processes writing to the directory should be stopped first.
1043
+ *
1044
+ * **Mount Lifecycle**: The FUSE overlay mount persists only while the
1045
+ * container is running. When the sandbox sleeps or the container restarts,
1046
+ * the mount is lost and the directory becomes empty. Re-restore from the
1047
+ * backup handle to recover. This is an ephemeral restore, not a persistent
1048
+ * extraction.
1049
+ *
1050
+ * **Local-dev flow** (`localBucket: true` on the originating `createBackup` call):
1051
+ * 1. DO reads metadata and checks TTL via R2 binding
1052
+ * 2. DO downloads the archive from R2 and writes it to the container
1053
+ * 3. Container extracts the archive with `unsquashfs` (no FUSE needed)
1054
+ *
1055
+ * The backup is restored into `backup.dir`. This may differ from the
1056
+ * directory that was originally backed up, allowing cross-directory restore.
1057
+ *
1058
+ * Overlapping backups are independent: restoring a parent directory
1059
+ * overwrites everything inside it, including subdirectories that were
1060
+ * backed up separately. When restoring both, restore the parent first.
1061
+ *
1062
+ * Concurrent backup/restore calls on the same sandbox are serialized.
1063
+ */
1064
+ restoreBackup(backup: DirectoryBackup): Promise<RestoreBackupResult>;
1065
+ private doRestoreBackup;
1066
+ /**
1067
+ * Local-dev implementation of restoreBackup.
1068
+ * Uses the R2 binding directly instead of presigned URLs, and
1069
+ * unsquashfs for extraction instead of squashfuse + fuse-overlayfs.
1070
+ */
1071
+ private doRestoreBackupLocal;
1072
+ private configureR2EgressOutbound;
1073
+ private configureS3CredentialProxyOutbound;
1074
+ }
1075
+ //#endregion
1076
+ export { Sandbox as n, getSandbox as r, ContainerProxy$1 as t };
1077
+ //# sourceMappingURL=sandbox-D_MMqExx.d.ts.map