@lunora/container 1.0.0-alpha.63 → 1.0.0-alpha.65

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.
@@ -1,3 +1,92 @@
1
+ /**
2
+ * The `handle.spawn()` contract: start a process in a named container
3
+ * instance and stream it, rather than buffer it the way `exec` does.
4
+ *
5
+ * Node-safe (no runtime imports): the Durable Object half lives in
6
+ * `do/spawn.ts`. This module owns the request shape both halves agree on and
7
+ * turns the RPC result into a {@link ContainerProcess}.
8
+ */
9
+ /** Per-call options for `handle.spawn()`. */
10
+ interface ContainerSpawnOptions {
11
+ /** Arguments passed to `command`, unshelled. */
12
+ args?: ReadonlyArray<string>;
13
+ /** Working directory for the process. */
14
+ cwd?: string;
15
+ /** Extra environment for this process, merged over the container's start env. */
16
+ env?: Readonly<Record<string, string>>;
17
+ /**
18
+ * Run the process on a pseudo-terminal of this size (defaults `80×24`).
19
+ * Under a PTY, stderr is merged into `stdout` and `resize()` works — what an
20
+ * interactive shell or a browser terminal needs.
21
+ */
22
+ pty?: {
23
+ cols?: number;
24
+ rows?: number;
25
+ };
26
+ /** Kill the process when this signal aborts. */
27
+ signal?: AbortSignal;
28
+ /**
29
+ * Give the process a stdin: `true` returns a writable `stdin` stream, a
30
+ * `ReadableStream` is piped in as the process's whole input. Omitted or
31
+ * `false` leaves stdin closed.
32
+ */
33
+ stdin?: ReadableStream<Uint8Array> | boolean;
34
+ /**
35
+ * Kill the process after this many ms. Unlike `exec`, a spawned process has
36
+ * no default deadline: it runs until it exits, is killed, or the container
37
+ * stops.
38
+ */
39
+ timeoutMs?: number;
40
+ }
41
+ /** The request `lunoraSpawn` takes over RPC. An `AbortSignal` cannot cross RPC, so it stays on the caller's side. */
42
+ interface ContainerSpawnRequest extends Omit<ContainerSpawnOptions, "args" | "env" | "signal"> {
43
+ args?: string[];
44
+ command: string;
45
+ env?: Record<string, string>;
46
+ }
47
+ /** The control stub `lunoraSpawn` returns, as the caller sees it over RPC. */
48
+ interface ContainerProcessControlStub {
49
+ exitCode: () => Promise<number>;
50
+ kill: (signal?: number) => Promise<void> | void;
51
+ resize: (cols: number, rows: number) => Promise<void> | void;
52
+ }
53
+ /** `lunoraSpawn`'s RPC result. */
54
+ interface SpawnResult {
55
+ control: ContainerProcessControlStub;
56
+ isPty: boolean;
57
+ pid: number;
58
+ stderr: ReadableStream<Uint8Array> | null;
59
+ stdin: WritableStream<Uint8Array> | null;
60
+ stdout: ReadableStream<Uint8Array> | null;
61
+ }
62
+ /**
63
+ * A running process in a container instance, returned by `handle.spawn()`.
64
+ *
65
+ * The container finishes a process's output, and settles `exitCode`, only
66
+ * while its output is being drained. Lunora drains `stdout` and `stderr` into
67
+ * a 1 MiB buffer each, so they can be read in either order, or after awaiting
68
+ * `exitCode`, while the stream you are not reading stays under 1 MiB. Past
69
+ * that the process stalls until you read it: for a process that may write more
70
+ * to one stream, read both concurrently, or cancel the one you do not need.
71
+ */
72
+ interface ContainerProcess {
73
+ /** Resolves with the exit code once the process ends. */
74
+ exitCode: Promise<number>;
75
+ /** Whether the process runs on a PTY (`spawn({ pty })`). */
76
+ isPty: boolean;
77
+ /** Send `signal` (default `SIGTERM`). Resolves once the signal is sent, not when the process exits. */
78
+ kill: (signal?: number) => Promise<void>;
79
+ /** The process id inside the container, or `-1` when the runtime does not report one. */
80
+ pid: number;
81
+ /** Resize the PTY. Rejects for a process spawned without `pty`. */
82
+ resize: (cols: number, rows: number) => Promise<void>;
83
+ /** The process's stderr, or `null` under `pty` (merged into `stdout`). */
84
+ stderr: ReadableStream<Uint8Array> | null;
85
+ /** The process's stdin when spawned with `stdin: true`, else `null`. */
86
+ stdin: WritableStream<Uint8Array> | null;
87
+ /** The process's stdout (and, under `pty`, its stderr too). */
88
+ stdout: ReadableStream<Uint8Array> | null;
89
+ }
1
90
  /**
2
91
  * Public configuration types for `@lunora/container`.
3
92
  *
@@ -26,6 +115,22 @@ interface CustomContainerInstanceType {
26
115
  * `ContainerInstanceType` is part of the experimental `@lunora/container` API and may change without a major version bump.
27
116
  */
28
117
  type ContainerInstanceType = CustomContainerInstanceType | NamedContainerInstanceType;
118
+ /**
119
+ * An instance size a Durable Object can pick at start under the
120
+ * `durable_object` scheduling policy. The runtime does not accept `basic` (or
121
+ * the legacy `dev`/`standard` aliases), and a custom size must set all three
122
+ * fields — it is passed to `ctx.container.start({ instance })` as-is.
123
+ */
124
+ type ContainerRuntimeInstanceType = Exclude<NamedContainerInstanceType, "basic"> | Required<CustomContainerInstanceType>;
125
+ /**
126
+ * One named image under the `durable_object` scheduling policy. A `string` is
127
+ * a local Dockerfile path or directory, built and uploaded by `wrangler deploy`
128
+ * (wrangler `dockerfile` + `build_context`); `{ registry }` must be a
129
+ * digest-pinned (`@sha256:…`) reference under `registry.cloudflare.com/`,
130
+ * the Cloudflare managed registry — Cloudflare does not accept Docker Hub, ECR
131
+ * or Artifact Registry references here.
132
+ */
133
+ type ContainerNamedImageSource = RegistryImageSource | string;
29
134
  /**
30
135
  * Rolling-deploy tuning for a container.
31
136
  */
@@ -73,17 +178,27 @@ type ContainerImageSource = BuildImageSource | RegistryImageSource | string;
73
178
  interface ContainerReadinessCheck {
74
179
  /** HTTP path probed on the container, e.g. `"/ready"` (a leading slash is optional). */
75
180
  path: string;
76
- /** Port to probe. Defaults to {@link ContainerConfig.defaultPort}. */
181
+ /** Port to probe. Defaults to {@link ContainerConfigBase.defaultPort}. */
77
182
  port?: number;
78
183
  /** HTTP status that means "ready". Defaults to `200`. */
79
184
  status?: number;
80
185
  }
81
186
  /**
82
- * `ContainerConfig` is part of the experimental `@lunora/container` API and may change without a major version bump.
187
+ * The fields every container accepts, whichever scheduling policy it uses.
188
+ */
189
+ /**
190
+ * Where `handle.backup()` stores directory backups for a `sandbox: true`
191
+ * container: an R2 bucket binding and an optional key prefix.
83
192
  */
84
- interface ContainerConfig {
193
+ interface ContainerBackupStorage {
194
+ /** Name of an R2 bucket binding on the Worker env (`r2_buckets[].binding`). */
195
+ bucket: string;
196
+ /** Key prefix for the backup objects. A non-empty prefix ends in `/`. */
197
+ prefix?: string;
198
+ }
199
+ interface ContainerConfigBase {
85
200
  /**
86
- * Hostnames the container may reach **even when {@link ContainerConfig.enableInternet}
201
+ * Hostnames the container may reach **even when {@link ContainerConfigBase.enableInternet}
87
202
  * is `false`** — an egress allow-list (Cloudflare's `allowedHosts`). Glob
88
203
  * patterns like `*.stripe.com` are supported. Pair with `enableInternet:
89
204
  * false` to deny all egress except these hosts (the firewall pattern
@@ -93,32 +208,39 @@ interface ContainerConfig {
93
208
  * handle's `egress` controls adjust the lists at runtime.
94
209
  */
95
210
  allowedHosts?: ReadonlyArray<string>;
211
+ /**
212
+ * Where `handle.backup()` saves directory backups (requires `sandbox: true`).
213
+ * The backup and restore traffic goes through the `DirectoryBackupGateway`
214
+ * Worker entrypoint, which hands the container a grant for one object per
215
+ * operation, so the container never holds bucket credentials.
216
+ */
217
+ backups?: ContainerBackupStorage;
96
218
  /**
97
219
  * Build-time variables for a Dockerfile/Railpack image — wrangler's
98
220
  * `image_vars` (equivalent to `docker build --build-arg`). For *runtime*
99
- * values use {@link ContainerConfig.env} / {@link ContainerConfig.secrets}.
221
+ * values use {@link ContainerConfigBase.env} / {@link ContainerConfigBase.secrets}.
100
222
  * Ignored for a pre-built `{ registry }` image.
101
223
  */
102
224
  buildArgs?: Readonly<Record<string, string>>;
103
225
  /**
104
226
  * The port the container listens on. Worker → container requests target
105
227
  * this port. Locally the Dockerfile must also `EXPOSE` it. For a
106
- * multi-port container also declare {@link ContainerConfig.requiredPorts}
228
+ * multi-port container also declare {@link ContainerConfigBase.requiredPorts}
107
229
  * and route per request with the handle's `.port(n)`.
108
230
  */
109
231
  defaultPort?: number;
110
232
  /**
111
233
  * Hostnames the container may **never** reach — an egress deny-list
112
234
  * (Cloudflare's `deniedHosts`). Overrides everything else, including
113
- * `enableInternet: true` and {@link ContainerConfig.allowedHosts}. Glob
235
+ * `enableInternet: true` and {@link ContainerConfigBase.allowedHosts}. Glob
114
236
  * patterns like `*.evil.com` are supported.
115
237
  */
116
238
  deniedHosts?: ReadonlyArray<string>;
117
239
  /**
118
240
  * Whether the container may open outbound internet connections. Defaults
119
241
  * to `true` — the platform default. Note that container egress is billed
120
- * per GB by Cloudflare. Combine with {@link ContainerConfig.allowedHosts} /
121
- * {@link ContainerConfig.deniedHosts} for a precise egress firewall.
242
+ * per GB by Cloudflare. Combine with {@link ContainerConfigBase.allowedHosts} /
243
+ * {@link ContainerConfigBase.deniedHosts} for a precise egress firewall.
122
244
  */
123
245
  enableInternet?: boolean;
124
246
  /**
@@ -129,14 +251,14 @@ interface ContainerConfig {
129
251
  entrypoint?: ReadonlyArray<string>;
130
252
  /**
131
253
  * Static environment variables passed to the container on every start.
132
- * For secret values use {@link ContainerConfig.secrets} instead so they
254
+ * For secret values use {@link ContainerConfigBase.secrets} instead so they
133
255
  * flow through Worker Secrets rather than source code.
134
256
  */
135
257
  env?: Readonly<Record<string, string>>;
136
258
  /**
137
259
  * Hard cap on how long an instance may run, measured from start regardless
138
260
  * of activity — a runaway-cost backstop on top of the idle
139
- * {@link ContainerConfig.sleepAfter}. Same grammar as `sleepAfter`
261
+ * {@link ContainerConfigBase.sleepAfter}. Same grammar as `sleepAfter`
140
262
  * (`"30s"`, `"5m"`, `"1h"`, or a plain number of seconds). When it elapses,
141
263
  * the `LunoraContainer.onHardTimeoutExpired` hook runs (default: `stop()`,
142
264
  * which sends SIGTERM and does not escalate — a container that ignores the
@@ -144,13 +266,6 @@ interface ContainerConfig {
144
266
  * `destroy()`). (Upstream cloudflare/containers#85.)
145
267
  */
146
268
  hardTimeout?: number | string;
147
- /** Image source — a local Dockerfile path/directory or a registry reference. */
148
- image: ContainerImageSource;
149
- /**
150
- * Resource class for each instance: a named Cloudflare instance type or a
151
- * custom `{ vcpu, memoryMib, diskMb }` object.
152
- */
153
- instanceType?: ContainerInstanceType;
154
269
  /**
155
270
  * Intercept the container's outbound **HTTPS** traffic so the egress
156
271
  * allow/deny lists apply to TLS connections too (Cloudflare's
@@ -166,11 +281,6 @@ interface ContainerConfig {
166
281
  * `start({ labels })`.
167
282
  */
168
283
  labels?: Readonly<Record<string, string>>;
169
- /**
170
- * Maximum number of concurrently *running* instances. Stopped (slept)
171
- * containers don't count. Also the default pool size for `.any()`.
172
- */
173
- maxInstances?: number;
174
284
  /**
175
285
  * Override for the wrangler `containers[].name` identifier. Defaults to
176
286
  * wrangler's own default (worker name + class name + environment).
@@ -196,17 +306,21 @@ interface ContainerConfig {
196
306
  * Ports the container must be listening on before it's considered ready
197
307
  * (Cloudflare's `requiredPorts`) — for multi-port containers. Start-up
198
308
  * waits for every listed port, and the handle's `.port(n)` routes a request
199
- * to any of them; {@link ContainerConfig.defaultPort} is the target when a
309
+ * to any of them; {@link ContainerConfigBase.defaultPort} is the target when a
200
310
  * request doesn't pick one.
201
311
  */
202
312
  requiredPorts?: ReadonlyArray<number>;
203
313
  /**
204
- * Rolling-deploy tuning. `stepPercentage` is the share of instances updated
205
- * per rollout step (wrangler `rollout_step_percentage`); `gracePeriodSeconds`
206
- * is how long an active instance is left running before it's eligible for
207
- * update (wrangler `rollout_active_grace_period`).
314
+ * Opt in to the Sandbox SDK helpers (`@cloudflare/sandbox`): structured
315
+ * file operations, directory backups to R2 and S3-compatible bucket mounts
316
+ * on a named instance. The image must ship the `sandbox-shim` helper at
317
+ * `/usr/local/bin/sandbox-shim`, copied from Cloudflare's shim-only
318
+ * `cloudflare/sandbox` image (`COPY --from=…`). Turning it on also makes codegen export the `S3Gateway` and
319
+ * `DirectoryBackupGateway` entrypoints from the worker, which the helpers
320
+ * route the container's storage traffic through. Must be a `true`/`false`
321
+ * literal, because codegen reads it. Defaults to `false`.
208
322
  */
209
- rollout?: ContainerRollout;
323
+ sandbox?: boolean;
210
324
  /**
211
325
  * Names of Worker secrets (from `wrangler secret` / `.dev.vars`) forwarded
212
326
  * into the container's environment at instance start. Each declared name
@@ -220,7 +334,7 @@ interface ContainerConfig {
220
334
  * binding name*. Each binding is resolved with its async `.get()` the first
221
335
  * time the instance starts, then injected as that env var — e.g.
222
336
  * `{ STRIPE_KEY: "STRIPE_SECRET" }` runs `env.STRIPE_SECRET.get()` and sets
223
- * `STRIPE_KEY` inside the container. Unlike {@link ContainerConfig.secrets}
337
+ * `STRIPE_KEY` inside the container. Unlike {@link ContainerConfigBase.secrets}
224
338
  * (plain Worker text secrets), this pulls from a `secrets_store_secrets`
225
339
  * binding. A name already used by `env`/`secrets` is rejected at authoring
226
340
  * time; a missing binding or unreadable value fails the start. Applies
@@ -236,13 +350,95 @@ interface ContainerConfig {
236
350
  */
237
351
  sleepAfter?: number | string;
238
352
  }
353
+ /**
354
+ * A container under the `default` scheduling policy: one application-wide
355
+ * image and instance size, set in wrangler and rolled out by Cloudflare on
356
+ * deploy.
357
+ */
358
+ interface DefaultScheduledContainerConfig extends ContainerConfigBase {
359
+ /** Image source — a local Dockerfile path/directory or a registry reference. */
360
+ image: ContainerImageSource;
361
+ /**
362
+ * Resource class for each instance: a named Cloudflare instance type or a
363
+ * custom `{ vcpu, memoryMib, diskMb }` object.
364
+ */
365
+ instanceType?: ContainerInstanceType;
366
+ /**
367
+ * Maximum number of concurrently *running* instances. Stopped (slept)
368
+ * containers don't count. Also the default pool size for `.any()`.
369
+ */
370
+ maxInstances?: number;
371
+ /**
372
+ * Rolling-deploy tuning. `stepPercentage` is the share of instances updated
373
+ * per rollout step (wrangler `rollout_step_percentage`); `gracePeriodSeconds`
374
+ * is how long an active instance is left running before it's eligible for
375
+ * update (wrangler `rollout_active_grace_period`).
376
+ */
377
+ rollout?: ContainerRollout;
378
+ /**
379
+ * Where the image and instance size are configured. `"default"` (the
380
+ * default) sets them once in wrangler; `"durable_object"` is the
381
+ * per-instance policy.
382
+ * Immutable once deployed: switching policies means a new container
383
+ * application, which replaces every running instance.
384
+ */
385
+ schedulingPolicy?: "default";
386
+ }
387
+ /**
388
+ * A container under the `durable_object` scheduling policy (Cloudflare public
389
+ * beta): each instance picks its image and size when it starts, via
390
+ * `ctx.containers.<name>.get(id).start({ image, instanceType })`, and can save
391
+ * and restore its filesystem with `snapshot()` / `start({ snapshot })`.
392
+ * Instances keep the image they started with across deploys — there is no
393
+ * application-wide rollout, and no `maxInstances` cap (running instances count
394
+ * against the account limit).
395
+ */
396
+ interface DurableObjectScheduledContainerConfig extends ContainerConfigBase {
397
+ /**
398
+ * The image a start uses when it names none — including the implicit start a
399
+ * `fetch`/`exec` triggers. Either a key of `images`
400
+ * or a Cloudflare-managed image such as `"cloudflare/debian-trixie"`.
401
+ * Without it, an instance must be started explicitly with an `image` (or a
402
+ * `snapshot`) before it can serve requests.
403
+ */
404
+ image?: string;
405
+ /**
406
+ * Named images an instance can start from, keyed by the name `start({ image })`
407
+ * takes (wrangler `containers[].images`). Up to 100, each name 1–128
408
+ * characters. Updating the map does not restart running instances.
409
+ */
410
+ images?: Readonly<Record<string, ContainerNamedImageSource>>;
411
+ /**
412
+ * The instance size a start uses when it names none. Cloudflare's runtime
413
+ * default is `"lite"`.
414
+ */
415
+ instanceType?: ContainerRuntimeInstanceType;
416
+ /** Selects the per-instance policy. Immutable once deployed. */
417
+ schedulingPolicy: "durable_object";
418
+ }
419
+ /**
420
+ * `ContainerConfig` is part of the experimental `@lunora/container` API and may change without a major version bump.
421
+ */
422
+ type ContainerConfig = DefaultScheduledContainerConfig | DurableObjectScheduledContainerConfig;
239
423
  /**
240
424
  * The value `defineContainer` returns: the validated config plus a brand the
241
425
  * codegen discovery and the generated Container DO class key on.
242
426
  */
243
- interface ContainerDefinition extends ContainerConfig {
427
+ type ContainerDefinition = ContainerConfig & {
244
428
  /** Brand marking a value as a Lunora container definition. */
245
429
  readonly isLunoraContainer: true;
430
+ };
431
+ /**
432
+ * A saved container filesystem, as `snapshot()` returns it. Plain data: store
433
+ * it anywhere and pass it back to `start({ snapshot })`, from the same or
434
+ * another instance of the same container. Tied to the image it was taken
435
+ * from; Cloudflare keeps it 30 days, refreshed on each restore.
436
+ */
437
+ interface ContainerSnapshot {
438
+ id: string;
439
+ name?: string;
440
+ /** Size in bytes. */
441
+ size: number;
246
442
  }
247
443
  /**
248
444
  * A normalized image source, as written into `wrangler.jsonc`.
@@ -268,4 +464,4 @@ type NormalizedContainerImage = {
268
464
  * @see https://developers.cloudflare.com/durable-objects/reference/data-location/
269
465
  */
270
466
  type DurableObjectJurisdiction = "eu" | "fedramp" | "us";
271
- export { BuildImageSource as B, ContainerDefinition as C, DurableObjectJurisdiction as D, NormalizedContainerImage as N, RegistryImageSource as R, ContainerConfig as a, ContainerImageSource as b, ContainerInstanceType as c, ContainerReadinessCheck as d, ContainerRollout as e, CustomContainerInstanceType as f, NamedContainerInstanceType as g };
467
+ export { BuildImageSource as B, ContainerDefinition as C, DurableObjectJurisdiction as D, NormalizedContainerImage as N, RegistryImageSource as R, SpawnResult as S, ContainerSpawnOptions as a, ContainerSnapshot as b, ContainerProcess as c, ContainerRuntimeInstanceType as d, ContainerSpawnRequest as e, DefaultScheduledContainerConfig as f, DurableObjectScheduledContainerConfig as g, ContainerImageSource as h, ContainerBackupStorage as i, ContainerConfig as j, ContainerConfigBase as k, ContainerInstanceType as l, ContainerNamedImageSource as m, ContainerReadinessCheck as n, ContainerRollout as o, CustomContainerInstanceType as p, NamedContainerInstanceType as q };
@@ -0,0 +1,110 @@
1
+ import { SandboxDirectoryEntry, SandboxFileStat, DirectoryBackupRecord, S3MountInspection } from '@cloudflare/sandbox';
2
+ /** Options every file operation accepts. */
3
+ interface ContainerFileOptions {
4
+ /** Absolute directory a relative path is joined onto. */
5
+ cwd?: string;
6
+ /** Numeric `uid:gid` that opens the file. */
7
+ user?: string;
8
+ }
9
+ /** What `handle.files.writeFile` accepts. A stream is written as it arrives. */
10
+ type ContainerFileContent = ArrayBuffer | ArrayBufferView | ReadableStream<Uint8Array> | string;
11
+ /**
12
+ * Structured file operations on the instance's own disk (`handle.files`),
13
+ * through `@cloudflare/sandbox`'s `Files`. A filesystem failure rejects with a
14
+ * `LunoraError` whose `data.errno` is the Linux code (`ENOENT`, `EACCES`, …).
15
+ */
16
+ interface ContainerFiles {
17
+ /** Create a directory; `recursive` creates parents and accepts an existing one. */
18
+ mkdir: (path: string, options?: ContainerFileOptions & {
19
+ recursive?: boolean;
20
+ }) => Promise<void>;
21
+ /** List a directory's immediate entries, without following symlinked entries. */
22
+ readDirectory: (path: string, options?: ContainerFileOptions) => Promise<SandboxDirectoryEntry[]>;
23
+ /**
24
+ * Stream a file out of the container. The body applies backpressure to the
25
+ * read. Read the body to the end or cancel it: the container is counted in
26
+ * flight until then, so an abandoned body keeps it from sleeping.
27
+ */
28
+ readFile: (path: string, options?: ContainerFileOptions) => Promise<Response>;
29
+ /** Remove a file or symlink; `recursive` removes a directory tree, `force` ignores a missing target. */
30
+ remove: (path: string, options?: ContainerFileOptions & {
31
+ force?: boolean;
32
+ recursive?: boolean;
33
+ }) => Promise<void>;
34
+ /** Rename within one filesystem (`EXDEV` across filesystems; there is no copy fallback). */
35
+ rename: (source: string, destination: string, options?: ContainerFileOptions) => Promise<void>;
36
+ /** Metadata for a path, following a final symlink. */
37
+ stat: (path: string, options?: ContainerFileOptions) => Promise<SandboxFileStat>;
38
+ /** Create or truncate a file and write `content` into it. */
39
+ writeFile: (path: string, content: ContainerFileContent, options?: ContainerFileOptions) => Promise<void>;
40
+ }
41
+ /** Options for `handle.backup()`. */
42
+ interface ContainerBackupOptions {
43
+ /** gitignore-syntax patterns, relative to the directory, to leave out. */
44
+ exclude?: ReadonlyArray<string>;
45
+ /** Also apply `.gitignore` files inside the directory and `.git/info/exclude`. */
46
+ gitignore?: boolean;
47
+ /** A label stored with the backup. */
48
+ name?: string;
49
+ }
50
+ /**
51
+ * Credentials for an S3-compatible bucket mount, by **name**: each value is the
52
+ * name of a Worker secret (`wrangler secret put`) the container Durable Object
53
+ * reads at mount time. The keys themselves never appear in source, and the
54
+ * Worker signs every storage request, so they never enter the container either.
55
+ */
56
+ interface ContainerMountCredentials {
57
+ /** Worker secret holding the access key id. */
58
+ accessKeyIdSecret: string;
59
+ /** Worker secret holding the secret access key. */
60
+ secretAccessKeySecret: string;
61
+ /** Worker secret holding a session token, for temporary credentials. */
62
+ sessionTokenSecret?: string;
63
+ }
64
+ /**
65
+ * An S3-compatible bucket (R2, S3, GCS) to mount at `path` with
66
+ * `handle.mount()`. For R2 the endpoint is
67
+ * `https://<account-id>.r2.cloudflarestorage.com` with region `"auto"`, and the
68
+ * credentials are an R2 API token's S3 keys — an R2 bucket binding is not used.
69
+ */
70
+ interface ContainerMountRequest {
71
+ access: "read-only" | "read-write";
72
+ bucket: string;
73
+ credentials: ContainerMountCredentials;
74
+ endpoint: string;
75
+ /** Mount only this key prefix. A non-empty prefix ends in `/`. */
76
+ keyPrefix?: string;
77
+ /** Absolute path inside the container to mount at. */
78
+ path: string;
79
+ region: string;
80
+ /** Extra s3fs options, passed through as-is. */
81
+ s3fsOptions?: Readonly<Record<string, boolean | number | string>>;
82
+ }
83
+ /** The sandbox operations on a named instance of a `defineContainer({ sandbox: true })` container. */
84
+ interface ContainerSandboxControls {
85
+ /**
86
+ * Save `directory` to the R2 bucket in the definition's `backups` and return its
87
+ * record — plain data to store and pass to `restore` later, here or on
88
+ * another instance, even one on a newer image. Pause writers in it first.
89
+ */
90
+ backup: (directory: string, options?: ContainerBackupOptions) => Promise<DirectoryBackupRecord>;
91
+ /** Delete a backup's object. Deleting one that is already gone succeeds. */
92
+ deleteBackup: (backup: DirectoryBackupRecord) => Promise<void>;
93
+ /** Structured file operations on the instance's disk. */
94
+ files: ContainerFiles;
95
+ /** Report a mount path's state without changing it. */
96
+ inspectMount: (path: string) => Promise<S3MountInspection>;
97
+ /** Mount an S3-compatible bucket at `request.path`. Mounting the same settings again reuses the mount. */
98
+ mount: (request: ContainerMountRequest) => Promise<void>;
99
+ /**
100
+ * Replace `options.directory` (default: the directory it was taken from) with the
101
+ * backup's contents. The target is swapped in only after the download is
102
+ * verified; a rejection means it was not replaced.
103
+ */
104
+ restore: (backup: DirectoryBackupRecord, options?: {
105
+ directory?: string;
106
+ }) => Promise<void>;
107
+ /** Stop access to a mount and unmount it. Never force-unmounts a busy path. */
108
+ unmount: (path: string) => Promise<void>;
109
+ }
110
+ export { ContainerFileOptions as C, ContainerFileContent as a, ContainerBackupOptions as b, ContainerMountRequest as c, ContainerSandboxControls as d, ContainerFiles as e, ContainerMountCredentials as f };
@@ -0,0 +1,110 @@
1
+ import { SandboxDirectoryEntry, SandboxFileStat, DirectoryBackupRecord, S3MountInspection } from '@cloudflare/sandbox';
2
+ /** Options every file operation accepts. */
3
+ interface ContainerFileOptions {
4
+ /** Absolute directory a relative path is joined onto. */
5
+ cwd?: string;
6
+ /** Numeric `uid:gid` that opens the file. */
7
+ user?: string;
8
+ }
9
+ /** What `handle.files.writeFile` accepts. A stream is written as it arrives. */
10
+ type ContainerFileContent = ArrayBuffer | ArrayBufferView | ReadableStream<Uint8Array> | string;
11
+ /**
12
+ * Structured file operations on the instance's own disk (`handle.files`),
13
+ * through `@cloudflare/sandbox`'s `Files`. A filesystem failure rejects with a
14
+ * `LunoraError` whose `data.errno` is the Linux code (`ENOENT`, `EACCES`, …).
15
+ */
16
+ interface ContainerFiles {
17
+ /** Create a directory; `recursive` creates parents and accepts an existing one. */
18
+ mkdir: (path: string, options?: ContainerFileOptions & {
19
+ recursive?: boolean;
20
+ }) => Promise<void>;
21
+ /** List a directory's immediate entries, without following symlinked entries. */
22
+ readDirectory: (path: string, options?: ContainerFileOptions) => Promise<SandboxDirectoryEntry[]>;
23
+ /**
24
+ * Stream a file out of the container. The body applies backpressure to the
25
+ * read. Read the body to the end or cancel it: the container is counted in
26
+ * flight until then, so an abandoned body keeps it from sleeping.
27
+ */
28
+ readFile: (path: string, options?: ContainerFileOptions) => Promise<Response>;
29
+ /** Remove a file or symlink; `recursive` removes a directory tree, `force` ignores a missing target. */
30
+ remove: (path: string, options?: ContainerFileOptions & {
31
+ force?: boolean;
32
+ recursive?: boolean;
33
+ }) => Promise<void>;
34
+ /** Rename within one filesystem (`EXDEV` across filesystems; there is no copy fallback). */
35
+ rename: (source: string, destination: string, options?: ContainerFileOptions) => Promise<void>;
36
+ /** Metadata for a path, following a final symlink. */
37
+ stat: (path: string, options?: ContainerFileOptions) => Promise<SandboxFileStat>;
38
+ /** Create or truncate a file and write `content` into it. */
39
+ writeFile: (path: string, content: ContainerFileContent, options?: ContainerFileOptions) => Promise<void>;
40
+ }
41
+ /** Options for `handle.backup()`. */
42
+ interface ContainerBackupOptions {
43
+ /** gitignore-syntax patterns, relative to the directory, to leave out. */
44
+ exclude?: ReadonlyArray<string>;
45
+ /** Also apply `.gitignore` files inside the directory and `.git/info/exclude`. */
46
+ gitignore?: boolean;
47
+ /** A label stored with the backup. */
48
+ name?: string;
49
+ }
50
+ /**
51
+ * Credentials for an S3-compatible bucket mount, by **name**: each value is the
52
+ * name of a Worker secret (`wrangler secret put`) the container Durable Object
53
+ * reads at mount time. The keys themselves never appear in source, and the
54
+ * Worker signs every storage request, so they never enter the container either.
55
+ */
56
+ interface ContainerMountCredentials {
57
+ /** Worker secret holding the access key id. */
58
+ accessKeyIdSecret: string;
59
+ /** Worker secret holding the secret access key. */
60
+ secretAccessKeySecret: string;
61
+ /** Worker secret holding a session token, for temporary credentials. */
62
+ sessionTokenSecret?: string;
63
+ }
64
+ /**
65
+ * An S3-compatible bucket (R2, S3, GCS) to mount at `path` with
66
+ * `handle.mount()`. For R2 the endpoint is
67
+ * `https://<account-id>.r2.cloudflarestorage.com` with region `"auto"`, and the
68
+ * credentials are an R2 API token's S3 keys — an R2 bucket binding is not used.
69
+ */
70
+ interface ContainerMountRequest {
71
+ access: "read-only" | "read-write";
72
+ bucket: string;
73
+ credentials: ContainerMountCredentials;
74
+ endpoint: string;
75
+ /** Mount only this key prefix. A non-empty prefix ends in `/`. */
76
+ keyPrefix?: string;
77
+ /** Absolute path inside the container to mount at. */
78
+ path: string;
79
+ region: string;
80
+ /** Extra s3fs options, passed through as-is. */
81
+ s3fsOptions?: Readonly<Record<string, boolean | number | string>>;
82
+ }
83
+ /** The sandbox operations on a named instance of a `defineContainer({ sandbox: true })` container. */
84
+ interface ContainerSandboxControls {
85
+ /**
86
+ * Save `directory` to the R2 bucket in the definition's `backups` and return its
87
+ * record — plain data to store and pass to `restore` later, here or on
88
+ * another instance, even one on a newer image. Pause writers in it first.
89
+ */
90
+ backup: (directory: string, options?: ContainerBackupOptions) => Promise<DirectoryBackupRecord>;
91
+ /** Delete a backup's object. Deleting one that is already gone succeeds. */
92
+ deleteBackup: (backup: DirectoryBackupRecord) => Promise<void>;
93
+ /** Structured file operations on the instance's disk. */
94
+ files: ContainerFiles;
95
+ /** Report a mount path's state without changing it. */
96
+ inspectMount: (path: string) => Promise<S3MountInspection>;
97
+ /** Mount an S3-compatible bucket at `request.path`. Mounting the same settings again reuses the mount. */
98
+ mount: (request: ContainerMountRequest) => Promise<void>;
99
+ /**
100
+ * Replace `options.directory` (default: the directory it was taken from) with the
101
+ * backup's contents. The target is swapped in only after the download is
102
+ * verified; a rejection means it was not replaced.
103
+ */
104
+ restore: (backup: DirectoryBackupRecord, options?: {
105
+ directory?: string;
106
+ }) => Promise<void>;
107
+ /** Stop access to a mount and unmount it. Never force-unmounts a busy path. */
108
+ unmount: (path: string) => Promise<void>;
109
+ }
110
+ export { ContainerFileOptions as C, ContainerFileContent as a, ContainerBackupOptions as b, ContainerMountRequest as c, ContainerSandboxControls as d, ContainerFiles as e, ContainerMountCredentials as f };
@@ -0,0 +1,66 @@
1
+ import { SandboxFileStat, SandboxDirectoryEntry, DirectoryBackupRecord, S3MountInspection } from '@cloudflare/sandbox';
2
+ export { DirectoryBackupGateway, S3Gateway } from '@cloudflare/sandbox';
3
+ import { LunoraContainer } from "../do/index.mjs";
4
+ import { C as ContainerFileOptions, a as ContainerFileContent, b as ContainerBackupOptions, c as ContainerMountRequest } from "../packem_shared/sandbox-types.d-DXlHnsEg.mjs";
5
+ import { C as ContainerDefinition } from "../packem_shared/jurisdiction.d-t4mtuqYW.mjs";
6
+ import 'cloudflare:workers';
7
+ type DurableObjectContext = ConstructorParameters<typeof LunoraContainer>[0];
8
+ declare class LunoraSandboxContainer<Env = unknown> extends LunoraContainer<Env> {
9
+ #private;
10
+ constructor(context: DurableObjectContext, env: Env, definition: ContainerDefinition, exportName?: string, jurisdiction?: ConstructorParameters<typeof LunoraContainer>[4]);
11
+ lunoraReadFile(path: string, options?: ContainerFileOptions): Promise<Response>;
12
+ lunoraWriteFile(path: string, content: ContainerFileContent, options?: ContainerFileOptions): Promise<void>;
13
+ lunoraStat(path: string, options?: ContainerFileOptions): Promise<SandboxFileStat>;
14
+ lunoraReadDirectory(path: string, options?: ContainerFileOptions): Promise<SandboxDirectoryEntry[]>;
15
+ lunoraMkdir(path: string, options?: ContainerFileOptions & {
16
+ recursive?: boolean;
17
+ }): Promise<void>;
18
+ lunoraRename(source: string, destination: string, options?: ContainerFileOptions): Promise<void>;
19
+ lunoraRemove(path: string, options?: ContainerFileOptions & {
20
+ force?: boolean;
21
+ recursive?: boolean;
22
+ }): Promise<void>;
23
+ lunoraBackup(directory: string, options?: ContainerBackupOptions): Promise<DirectoryBackupRecord>;
24
+ lunoraRestore(backup: DirectoryBackupRecord, options?: {
25
+ directory?: string;
26
+ }): Promise<void>;
27
+ /** Needs no running container — it only deletes the object. */
28
+ lunoraDeleteBackup(backup: DirectoryBackupRecord): Promise<void>;
29
+ lunoraMount(request: ContainerMountRequest): Promise<void>;
30
+ lunoraInspectMount(path: string): Promise<S3MountInspection>;
31
+ lunoraUnmount(path: string): Promise<void>;
32
+ /**
33
+ * Route backup traffic to its gateway before the base installs the egress
34
+ * policy's catch-all, which takes every hostname registered after it.
35
+ * Without an egress policy there is no catch-all, and each backup or
36
+ * restore registers its own route when it runs.
37
+ */
38
+ protected override beforeContainerStart(): Promise<void>;
39
+ /** Run one helper operation on the running container, translating sandbox errors. */
40
+ private withContainer;
41
+ private files;
42
+ /** One `DirectoryBackup` per instance: it runs one operation at a time per container, which only holds if every call shares it. */
43
+ private backups;
44
+ private mounts;
45
+ /** A Worker entrypoint from `ctx.exports`, with the fix when the worker does not export it. */
46
+ private workerExport;
47
+ /** Resolve a mount's credentials from the Worker secrets they name. */
48
+ private mountCredentials;
49
+ private label;
50
+ }
51
+ export {
52
+ /**
53
+ * `@lunora/container/sandbox` — the workerd-only half of the Sandbox SDK
54
+ * helpers (`@cloudflare/sandbox`) a `defineContainer({ sandbox: true })`
55
+ * container uses. A separate subpath from `/do`, so an app that never opts in
56
+ * never loads `@cloudflare/sandbox` (or the `zod` it depends on).
57
+ *
58
+ * `LunoraSandboxContainer` is the base class codegen extends for a `sandbox:
59
+ * true` container: `LunoraContainer` plus the file, backup and bucket-mount
60
+ * RPCs the named-instance handle calls. `DirectoryBackupGateway` and
61
+ * `S3Gateway` are the `WorkerEntrypoint`s the backup and mount helpers route
62
+ * the container's storage traffic through (reached as `ctx.exports.<Name>`),
63
+ * so the deployed worker must export both; the generated
64
+ * `_generated/containers.ts` re-exports them from here.
65
+ */
66
+ LunoraSandboxContainer };