@lunora/container 1.0.0-alpha.63 → 1.0.0-alpha.64
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 +17 -0
- package/dist/do/index.d.mts +65 -15
- package/dist/do/index.d.ts +65 -15
- package/dist/do/index.mjs +1 -1
- package/dist/index.d.mts +56 -8
- package/dist/index.d.ts +56 -8
- package/dist/index.mjs +1 -1
- package/dist/packem_shared/CONTAINER_EXEC_PATH-7Hcd6-zs.mjs +1 -0
- package/dist/packem_shared/{ContainerProxy-fTW_pXWY.mjs → ContainerProxy-CfXjS6ZI.mjs} +7 -7
- package/dist/packem_shared/DURABLE_OBJECT_POLICY_FORBIDDEN_KEYS-Dk6cuK7G.mjs +1 -0
- package/dist/packem_shared/createContainerContext-DP-kZaQ1.mjs +1 -0
- package/dist/packem_shared/exec-BYLW70sR.mjs +1 -0
- package/dist/packem_shared/{jurisdiction.d-DPvNG2h5.d.mts → jurisdiction.d-CoLtDVr_.d.mts} +113 -34
- package/dist/packem_shared/{jurisdiction.d-DPvNG2h5.d.ts → jurisdiction.d-CoLtDVr_.d.ts} +113 -34
- package/package.json +2 -2
- package/dist/packem_shared/CONTAINER_EXEC_PATH-ZfG5wgai.mjs +0 -1
- package/dist/packem_shared/containerBindingName-C7SonAIK.mjs +0 -1
- package/dist/packem_shared/createContainerContext-CLvjjJLs.mjs +0 -1
- package/dist/packem_shared/exec-WbCOLswq.mjs +0 -1
package/README.md
CHANGED
|
@@ -181,6 +181,23 @@ export const job = defineContainer({
|
|
|
181
181
|
});
|
|
182
182
|
```
|
|
183
183
|
|
|
184
|
+
### Per-instance images and snapshots
|
|
185
|
+
|
|
186
|
+
`schedulingPolicy: "durable_object"` (Cloudflare public beta) lets each instance pick its image and size at start, and save and restore its filesystem:
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
export const agentComputer = defineContainer({
|
|
190
|
+
schedulingPolicy: "durable_object",
|
|
191
|
+
images: { base: "./containers/base" },
|
|
192
|
+
image: "base",
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
const computer = ctx.containers.agentComputer.get(taskId);
|
|
196
|
+
await computer.start({ image: "base", instanceType: "standard-2" });
|
|
197
|
+
const saved = await computer.snapshot();
|
|
198
|
+
await ctx.containers.agentComputer.get(otherId).start({ snapshot: saved });
|
|
199
|
+
```
|
|
200
|
+
|
|
184
201
|
### Calling Lunora from inside a container
|
|
185
202
|
|
|
186
203
|
Container code calls back into your app's functions with the bridge client (any JS runtime), over the Worker's HTTP RPC endpoint:
|
package/dist/do/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as ContainerRuntimeInstanceType, d as ContainerDefinition, D as DurableObjectJurisdiction, C as ContainerSnapshot } from "../packem_shared/jurisdiction.d-CoLtDVr_.mjs";
|
|
2
2
|
import { DurableObject, WorkerEntrypoint } from 'cloudflare:workers';
|
|
3
3
|
/**
|
|
4
4
|
* ContainerStartOptions as they come from worker types
|
|
@@ -345,7 +345,7 @@ declare class Container<Env = Cloudflare.Env> extends DurableObject<Env> {
|
|
|
345
345
|
* When the counter transitions to 0, renew the activity timeout so the
|
|
346
346
|
* inactivity window starts fresh from the moment the last request completes.
|
|
347
347
|
*/
|
|
348
|
-
|
|
348
|
+
protected decrementInflight(): void;
|
|
349
349
|
/**
|
|
350
350
|
* Schedule a task to be executed in the future.
|
|
351
351
|
*
|
|
@@ -387,10 +387,17 @@ declare class Container<Env = Cloudflare.Env> extends DurableObject<Env> {
|
|
|
387
387
|
private onStopCalled;
|
|
388
388
|
private state;
|
|
389
389
|
private monitor;
|
|
390
|
-
|
|
390
|
+
protected startInFlight?: Promise<unknown>;
|
|
391
391
|
private monitoredPromise;
|
|
392
392
|
private sleepAfterMs;
|
|
393
|
-
|
|
393
|
+
protected inflightRequests: number;
|
|
394
|
+
/** Patched (Lunora): the `durable_object`-policy image reference and size `start()` forwards. */
|
|
395
|
+
protected image?: string;
|
|
396
|
+
protected instance?: string | {
|
|
397
|
+
vcpu: number;
|
|
398
|
+
memoryMib: number;
|
|
399
|
+
diskMb: number;
|
|
400
|
+
};
|
|
394
401
|
private outboundByHostOverrides;
|
|
395
402
|
private outboundHandlerOverride?;
|
|
396
403
|
private allowedHostsOverride?;
|
|
@@ -483,7 +490,22 @@ declare class Container<Env = Cloudflare.Env> extends DurableObject<Env> {
|
|
|
483
490
|
getSchedule<T = string>(id: string): Promise<Schedule<T> | undefined>;
|
|
484
491
|
private isActivityExpired;
|
|
485
492
|
}
|
|
493
|
+
/** What an explicit `start()` chose that later restarts must reuse. */
|
|
494
|
+
interface StartOverride {
|
|
495
|
+
/** Replaces the declared env, `secrets` and `secretsStore` wholesale. */
|
|
496
|
+
envVars?: Record<string, string>;
|
|
497
|
+
/** A key of the definition's `images`, or a managed `cloudflare/…` image. */
|
|
498
|
+
image?: string;
|
|
499
|
+
instanceType?: ContainerRuntimeInstanceType;
|
|
500
|
+
}
|
|
486
501
|
type DurableObjectContext = ConstructorParameters<typeof Container>[0];
|
|
502
|
+
/**
|
|
503
|
+
* The `start()` options `LunoraContainer` accepts on top of the base's: the
|
|
504
|
+
* `durable_object`-policy image, size and snapshot to restore.
|
|
505
|
+
*/
|
|
506
|
+
type LunoraStartOptions = NonNullable<Parameters<Container["start"]>[0]> & Pick<StartOverride, "image" | "instanceType"> & {
|
|
507
|
+
snapshot?: ContainerSnapshot;
|
|
508
|
+
};
|
|
487
509
|
/**
|
|
488
510
|
* Base class for the generated Container DO classes. Applies a
|
|
489
511
|
* `defineContainer` definition onto `@cloudflare/containers`' `Container`:
|
|
@@ -522,12 +544,16 @@ declare class LunoraContainer<Env = unknown> extends Container<Env> {
|
|
|
522
544
|
private readonly lunoraSecretsStore?;
|
|
523
545
|
/** The definition's `env` + `secrets`, as resolved at construction — the env a start uses absent an override. */
|
|
524
546
|
private readonly lunoraDeclaredEnv;
|
|
525
|
-
/** The persisted
|
|
526
|
-
private
|
|
547
|
+
/** The persisted start override, once loaded. See {@link START_OVERRIDE_KEY}. */
|
|
548
|
+
private lunoraOverride?;
|
|
527
549
|
/** Whether the override field reflects storage (read once per DO instance, then kept in step). */
|
|
528
|
-
private
|
|
550
|
+
private lunoraOverrideLoaded;
|
|
529
551
|
/** Memoised Secrets Store resolution: the resolved `name → value` map, fetched once. */
|
|
530
552
|
private lunoraSecretsStoreResolved?;
|
|
553
|
+
/** Whether the definition uses the `durable_object` scheduling policy. */
|
|
554
|
+
private readonly lunoraDurableObjectScheduled;
|
|
555
|
+
/** The definition's default image name and instance size (`durable_object` policy only). */
|
|
556
|
+
private readonly lunoraDefaultSelection;
|
|
531
557
|
/**
|
|
532
558
|
* Count of runs observed to have ENDED, bumped by the `onStop` hook. Read
|
|
533
559
|
* across a start: a bump means the run that start snapshotted is over,
|
|
@@ -535,6 +561,15 @@ declare class LunoraContainer<Env = unknown> extends Container<Env> {
|
|
|
535
561
|
*/
|
|
536
562
|
private lunoraStops;
|
|
537
563
|
constructor(context: DurableObjectContext, env: Env, definition: ContainerDefinition, exportName?: string, jurisdiction?: DurableObjectJurisdiction);
|
|
564
|
+
/**
|
|
565
|
+
* Save the running container's filesystem (`durable_object` policy only).
|
|
566
|
+
* The handle is plain data: store it and pass it to `start({ snapshot })` to
|
|
567
|
+
* restore it here or in another instance. Memory and processes are not
|
|
568
|
+
* captured — a restored container runs its entrypoint again.
|
|
569
|
+
*/
|
|
570
|
+
lunoraSnapshot(options?: {
|
|
571
|
+
name?: string;
|
|
572
|
+
}): Promise<ContainerSnapshot>;
|
|
538
573
|
/**
|
|
539
574
|
* HTTP entry for every request to this Durable Object. Refuses the reserved
|
|
540
575
|
* `/__lunora/*` namespace unconditionally: `exec` reaches it only through
|
|
@@ -589,10 +624,10 @@ declare class LunoraContainer<Env = unknown> extends Container<Env> {
|
|
|
589
624
|
* return early on a live container, reporting a start whose env never took
|
|
590
625
|
* effect. `stop()` it first, or `destroy()` it to drop the override.
|
|
591
626
|
*/
|
|
592
|
-
override start(
|
|
627
|
+
override start(options?: LunoraStartOptions, waitOptions?: Parameters<Container<Env>["start"]>[1]): Promise<void>;
|
|
593
628
|
/**
|
|
594
|
-
* Stop the container and forget this instance's
|
|
595
|
-
*
|
|
629
|
+
* Stop the container and forget this instance's start override, so the next
|
|
630
|
+
* start uses the declared env, image and size again.
|
|
596
631
|
*/
|
|
597
632
|
override destroy(): Promise<void>;
|
|
598
633
|
override onActivityExpired(): Promise<void>;
|
|
@@ -711,19 +746,34 @@ declare class LunoraContainer<Env = unknown> extends Container<Env> {
|
|
|
711
746
|
* `secretsStore`.
|
|
712
747
|
*/
|
|
713
748
|
private resolveSecretsStoreEnv;
|
|
714
|
-
/** The persisted
|
|
715
|
-
private
|
|
749
|
+
/** The persisted start override, read from storage once per instance lifetime. */
|
|
750
|
+
private readOverride;
|
|
716
751
|
/**
|
|
717
|
-
*
|
|
718
|
-
*
|
|
752
|
+
* Validate an explicit start's choices and persist them — merged over the
|
|
753
|
+
* stored override, so `start({ instanceType })` alone keeps a chosen image.
|
|
754
|
+
* Everything is checked before the one write, so a rejected start stores
|
|
755
|
+
* nothing. A start whose choices differ from what a running (or starting)
|
|
756
|
+
* container has is REJECTED rather than silently joined: the base would
|
|
757
|
+
* return early on a live container, reporting a start that never took
|
|
758
|
+
* effect. A snapshot restore only applies to a fresh start, so it is refused
|
|
759
|
+
* on a busy one.
|
|
719
760
|
*/
|
|
720
|
-
private
|
|
761
|
+
private recordStartOverride;
|
|
721
762
|
/**
|
|
722
763
|
* Set `envVars` for the start about to happen: the persisted override when
|
|
723
764
|
* there is one (the Secrets Store is not read), else the declared env plus
|
|
724
765
|
* the resolved Secrets Store values.
|
|
725
766
|
*/
|
|
726
767
|
private applyStartEnv;
|
|
768
|
+
/**
|
|
769
|
+
* Resolve the image reference and instance size for the start about to
|
|
770
|
+
* happen (`durable_object` policy only): the persisted explicit choice, else
|
|
771
|
+
* the definition default. A fresh boot with neither an image nor a snapshot
|
|
772
|
+
* would be rejected by the runtime, so it fails here with the fix instead.
|
|
773
|
+
*/
|
|
774
|
+
private applyStartSelection;
|
|
775
|
+
/** Map an image name to the reference `ctx.container.start()` takes, via `ctx.container.images`. */
|
|
776
|
+
private resolveImage;
|
|
727
777
|
/**
|
|
728
778
|
* Block until every `readyOn` probe responds with its expected status, or
|
|
729
779
|
* throw once the readiness budget is spent. Probes run in parallel and hit
|
package/dist/do/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as ContainerRuntimeInstanceType, d as ContainerDefinition, D as DurableObjectJurisdiction, C as ContainerSnapshot } from "../packem_shared/jurisdiction.d-CoLtDVr_.js";
|
|
2
2
|
import { DurableObject, WorkerEntrypoint } from 'cloudflare:workers';
|
|
3
3
|
/**
|
|
4
4
|
* ContainerStartOptions as they come from worker types
|
|
@@ -345,7 +345,7 @@ declare class Container<Env = Cloudflare.Env> extends DurableObject<Env> {
|
|
|
345
345
|
* When the counter transitions to 0, renew the activity timeout so the
|
|
346
346
|
* inactivity window starts fresh from the moment the last request completes.
|
|
347
347
|
*/
|
|
348
|
-
|
|
348
|
+
protected decrementInflight(): void;
|
|
349
349
|
/**
|
|
350
350
|
* Schedule a task to be executed in the future.
|
|
351
351
|
*
|
|
@@ -387,10 +387,17 @@ declare class Container<Env = Cloudflare.Env> extends DurableObject<Env> {
|
|
|
387
387
|
private onStopCalled;
|
|
388
388
|
private state;
|
|
389
389
|
private monitor;
|
|
390
|
-
|
|
390
|
+
protected startInFlight?: Promise<unknown>;
|
|
391
391
|
private monitoredPromise;
|
|
392
392
|
private sleepAfterMs;
|
|
393
|
-
|
|
393
|
+
protected inflightRequests: number;
|
|
394
|
+
/** Patched (Lunora): the `durable_object`-policy image reference and size `start()` forwards. */
|
|
395
|
+
protected image?: string;
|
|
396
|
+
protected instance?: string | {
|
|
397
|
+
vcpu: number;
|
|
398
|
+
memoryMib: number;
|
|
399
|
+
diskMb: number;
|
|
400
|
+
};
|
|
394
401
|
private outboundByHostOverrides;
|
|
395
402
|
private outboundHandlerOverride?;
|
|
396
403
|
private allowedHostsOverride?;
|
|
@@ -483,7 +490,22 @@ declare class Container<Env = Cloudflare.Env> extends DurableObject<Env> {
|
|
|
483
490
|
getSchedule<T = string>(id: string): Promise<Schedule<T> | undefined>;
|
|
484
491
|
private isActivityExpired;
|
|
485
492
|
}
|
|
493
|
+
/** What an explicit `start()` chose that later restarts must reuse. */
|
|
494
|
+
interface StartOverride {
|
|
495
|
+
/** Replaces the declared env, `secrets` and `secretsStore` wholesale. */
|
|
496
|
+
envVars?: Record<string, string>;
|
|
497
|
+
/** A key of the definition's `images`, or a managed `cloudflare/…` image. */
|
|
498
|
+
image?: string;
|
|
499
|
+
instanceType?: ContainerRuntimeInstanceType;
|
|
500
|
+
}
|
|
486
501
|
type DurableObjectContext = ConstructorParameters<typeof Container>[0];
|
|
502
|
+
/**
|
|
503
|
+
* The `start()` options `LunoraContainer` accepts on top of the base's: the
|
|
504
|
+
* `durable_object`-policy image, size and snapshot to restore.
|
|
505
|
+
*/
|
|
506
|
+
type LunoraStartOptions = NonNullable<Parameters<Container["start"]>[0]> & Pick<StartOverride, "image" | "instanceType"> & {
|
|
507
|
+
snapshot?: ContainerSnapshot;
|
|
508
|
+
};
|
|
487
509
|
/**
|
|
488
510
|
* Base class for the generated Container DO classes. Applies a
|
|
489
511
|
* `defineContainer` definition onto `@cloudflare/containers`' `Container`:
|
|
@@ -522,12 +544,16 @@ declare class LunoraContainer<Env = unknown> extends Container<Env> {
|
|
|
522
544
|
private readonly lunoraSecretsStore?;
|
|
523
545
|
/** The definition's `env` + `secrets`, as resolved at construction — the env a start uses absent an override. */
|
|
524
546
|
private readonly lunoraDeclaredEnv;
|
|
525
|
-
/** The persisted
|
|
526
|
-
private
|
|
547
|
+
/** The persisted start override, once loaded. See {@link START_OVERRIDE_KEY}. */
|
|
548
|
+
private lunoraOverride?;
|
|
527
549
|
/** Whether the override field reflects storage (read once per DO instance, then kept in step). */
|
|
528
|
-
private
|
|
550
|
+
private lunoraOverrideLoaded;
|
|
529
551
|
/** Memoised Secrets Store resolution: the resolved `name → value` map, fetched once. */
|
|
530
552
|
private lunoraSecretsStoreResolved?;
|
|
553
|
+
/** Whether the definition uses the `durable_object` scheduling policy. */
|
|
554
|
+
private readonly lunoraDurableObjectScheduled;
|
|
555
|
+
/** The definition's default image name and instance size (`durable_object` policy only). */
|
|
556
|
+
private readonly lunoraDefaultSelection;
|
|
531
557
|
/**
|
|
532
558
|
* Count of runs observed to have ENDED, bumped by the `onStop` hook. Read
|
|
533
559
|
* across a start: a bump means the run that start snapshotted is over,
|
|
@@ -535,6 +561,15 @@ declare class LunoraContainer<Env = unknown> extends Container<Env> {
|
|
|
535
561
|
*/
|
|
536
562
|
private lunoraStops;
|
|
537
563
|
constructor(context: DurableObjectContext, env: Env, definition: ContainerDefinition, exportName?: string, jurisdiction?: DurableObjectJurisdiction);
|
|
564
|
+
/**
|
|
565
|
+
* Save the running container's filesystem (`durable_object` policy only).
|
|
566
|
+
* The handle is plain data: store it and pass it to `start({ snapshot })` to
|
|
567
|
+
* restore it here or in another instance. Memory and processes are not
|
|
568
|
+
* captured — a restored container runs its entrypoint again.
|
|
569
|
+
*/
|
|
570
|
+
lunoraSnapshot(options?: {
|
|
571
|
+
name?: string;
|
|
572
|
+
}): Promise<ContainerSnapshot>;
|
|
538
573
|
/**
|
|
539
574
|
* HTTP entry for every request to this Durable Object. Refuses the reserved
|
|
540
575
|
* `/__lunora/*` namespace unconditionally: `exec` reaches it only through
|
|
@@ -589,10 +624,10 @@ declare class LunoraContainer<Env = unknown> extends Container<Env> {
|
|
|
589
624
|
* return early on a live container, reporting a start whose env never took
|
|
590
625
|
* effect. `stop()` it first, or `destroy()` it to drop the override.
|
|
591
626
|
*/
|
|
592
|
-
override start(
|
|
627
|
+
override start(options?: LunoraStartOptions, waitOptions?: Parameters<Container<Env>["start"]>[1]): Promise<void>;
|
|
593
628
|
/**
|
|
594
|
-
* Stop the container and forget this instance's
|
|
595
|
-
*
|
|
629
|
+
* Stop the container and forget this instance's start override, so the next
|
|
630
|
+
* start uses the declared env, image and size again.
|
|
596
631
|
*/
|
|
597
632
|
override destroy(): Promise<void>;
|
|
598
633
|
override onActivityExpired(): Promise<void>;
|
|
@@ -711,19 +746,34 @@ declare class LunoraContainer<Env = unknown> extends Container<Env> {
|
|
|
711
746
|
* `secretsStore`.
|
|
712
747
|
*/
|
|
713
748
|
private resolveSecretsStoreEnv;
|
|
714
|
-
/** The persisted
|
|
715
|
-
private
|
|
749
|
+
/** The persisted start override, read from storage once per instance lifetime. */
|
|
750
|
+
private readOverride;
|
|
716
751
|
/**
|
|
717
|
-
*
|
|
718
|
-
*
|
|
752
|
+
* Validate an explicit start's choices and persist them — merged over the
|
|
753
|
+
* stored override, so `start({ instanceType })` alone keeps a chosen image.
|
|
754
|
+
* Everything is checked before the one write, so a rejected start stores
|
|
755
|
+
* nothing. A start whose choices differ from what a running (or starting)
|
|
756
|
+
* container has is REJECTED rather than silently joined: the base would
|
|
757
|
+
* return early on a live container, reporting a start that never took
|
|
758
|
+
* effect. A snapshot restore only applies to a fresh start, so it is refused
|
|
759
|
+
* on a busy one.
|
|
719
760
|
*/
|
|
720
|
-
private
|
|
761
|
+
private recordStartOverride;
|
|
721
762
|
/**
|
|
722
763
|
* Set `envVars` for the start about to happen: the persisted override when
|
|
723
764
|
* there is one (the Secrets Store is not read), else the declared env plus
|
|
724
765
|
* the resolved Secrets Store values.
|
|
725
766
|
*/
|
|
726
767
|
private applyStartEnv;
|
|
768
|
+
/**
|
|
769
|
+
* Resolve the image reference and instance size for the start about to
|
|
770
|
+
* happen (`durable_object` policy only): the persisted explicit choice, else
|
|
771
|
+
* the definition default. A fresh boot with neither an image nor a snapshot
|
|
772
|
+
* would be rejected by the runtime, so it fails here with the fix instead.
|
|
773
|
+
*/
|
|
774
|
+
private applyStartSelection;
|
|
775
|
+
/** Map an image name to the reference `ctx.container.start()` takes, via `ctx.container.images`. */
|
|
776
|
+
private resolveImage;
|
|
727
777
|
/**
|
|
728
778
|
* Block until every `readyOn` probe responds with its expected status, or
|
|
729
779
|
* throw once the readiness budget is spent. Probes run in parallel and hit
|
package/dist/do/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{LunoraError as u}from"@lunora/errors";import{a as R}from"../packem_shared/abort-deadline-jwbW9Ala.mjs";import{resolveContainerEnvVars as g,parseDurationSeconds as y}from"../packem_shared/containerBindingName-C7SonAIK.mjs";import{C as S,p as N}from"../packem_shared/exec-WbCOLswq.mjs";import{a as O}from"../packem_shared/jurisdiction-DtE9s70w.mjs";import{Container as _}from"../packem_shared/ContainerProxy-fTW_pXWY.mjs";import{ContainerProxy as W,outboundParams as Y}from"../packem_shared/ContainerProxy-fTW_pXWY.mjs";const T="lunora",A=(r,t,n,e)=>({container:r,event:n,instance:t,level:n==="error"?"error":"info",message:e,source:T,ts:Date.now(),type:"container"}),i=(r,t,n,e)=>{const s=A(r,t,n,e),a=JSON.stringify(s);return n==="error"?console.error(a):console.log(a),s},P="__lunora_admin__:recordContainerEvent",w="__root__",I=r=>{if(r===null||typeof r!="object")return!1;const t=r;return typeof t.get=="function"&&typeof t.idFromName=="function"},x=(r,t)=>{const n=O(r,t);return typeof n.getByName=="function"?n.getByName(w):n.get(n.idFromName(w))},L=async(r,t,n)=>{try{const e=r??{},s=e.SHARD;if(!I(s))return;const a=typeof e.LUNORA_ADMIN_TOKEN=="string"?e.LUNORA_ADMIN_TOKEN:void 0;if(!a||a.length===0)return;const o=new Request("https://shard.internal/rpc",{body:JSON.stringify({args:{event:t},functionPath:P}),headers:{authorization:`Bearer ${a}`,"content-type":"application/json"},method:"POST"});await x(s,n).fetch(o)}catch{}},f=500,E=3e4,d="__lunoraHardTimeoutGeneration",h="__lunoraEnvOverride",C=(r,t)=>{if(r===void 0||t===void 0)return r===t;const n=Object.keys(r);return n.length===Object.keys(t).length&&n.every(e=>Object.hasOwn(t,e)&&r[e]===t[e])},m="__lunora",D=/[/\\]/u,H=r=>{const t=r.indexOf(";");return t===-1?r:r.slice(0,t)},$=r=>N(r,n=>n.split(D).some(e=>H(e).toLowerCase()===m)),b=r=>{const t=r instanceof Request?r.url:String(r);return URL.parse(t,"https://container")?.pathname??t},V="cf-container-target-port";class k extends _{lunoraJurisdiction;lunoraName;lunoraDefaultPort;lunoraHardTimeoutSeconds;lunoraReadiness;lunoraReadyOn;lunoraSecretsStore;lunoraDeclaredEnv;lunoraEnvOverride;lunoraEnvOverrideLoaded=!1;lunoraSecretsStoreResolved;lunoraStops=0;constructor(t,n,e,s,a){const o=g(e,n,s);super(t,n,{defaultPort:e.defaultPort,entrypoint:e.entrypoint?[...e.entrypoint]:void 0,envVars:o,sleepAfter:e.sleepAfter}),this.lunoraDeclaredEnv={...o},e.enableInternet!==void 0&&(this.enableInternet=e.enableInternet),e.requiredPorts!==void 0&&(this.requiredPorts=[...e.requiredPorts]),e.interceptHttps!==void 0&&(this.interceptHttps=e.interceptHttps),e.allowedHosts!==void 0&&(this.allowedHosts=[...e.allowedHosts]),e.deniedHosts!==void 0&&(this.deniedHosts=[...e.deniedHosts]),e.pingEndpoint!==void 0&&(this.pingEndpoint=e.pingEndpoint),e.labels!==void 0&&(this.labels={...e.labels}),this.lunoraName=s??"container",this.lunoraJurisdiction=a,this.lunoraDefaultPort=e.defaultPort,this.lunoraReadyOn=e.readyOn?[...e.readyOn]:[],this.lunoraHardTimeoutSeconds=e.hardTimeout===void 0?void 0:y(e.hardTimeout),this.lunoraSecretsStore=e.secretsStore}async fetch(t){return this.refuseReserved(new URL(t.url).pathname)??super.fetch(t)}async containerFetch(...t){const n=this.refuseReserved(b(t[0]));return n!==void 0?n:(await this.awaitReadinessGate(),super.containerFetch(...t))}async lunoraExec(t){const{pathname:n}=new URL(t.url);if(t.method!=="POST"||n!==S)return new Response(`container "${this.lunoraName}": lunoraExec only serves POST ${S}.`,{status:400});const e=t.headers.get(V),s=e===null?this.defaultPort:Number.parseInt(e,10);return s===void 0||Number.isNaN(s)?new Response(`container "${this.lunoraName}": exec needs a port — set \`defaultPort\` or exec through \`.port(n)\`.`,{status:400}):(await this.awaitReadinessGate(),super.containerFetch(t,s))}async startAndWaitForPorts(...t){await this.applyStartEnv();const n=this.lunoraStops,e=this.beginStart();await super.startAndWaitForPorts(...t),await this.afterContainerStart(e&&this.lunoraStops===n)}async start(...t){const[n]=t;n?.envVars!==void 0&&await this.persistEnvOverride({...n.envVars}),await this.applyStartEnv();const e=this.lunoraStops,s=this.beginStart();await super.start(...t),await this.afterContainerStart(s&&this.lunoraStops===e)}async destroy(){await super.destroy(),await this.ctx.storage.delete(h),this.lunoraEnvOverride=void 0}async onActivityExpired(){const t=i(this.lunoraName,this.instanceId(),"sleep");this.surfaceInStudioLogs(t),await super.onActivityExpired()}onError(t){const n=i(this.lunoraName,this.instanceId(),"error",t instanceof Error?t.message:String(t));return this.surfaceInStudioLogs(n),super.onError(t)}async onStart(){const t=i(this.lunoraName,this.instanceId(),"start");this.surfaceInStudioLogs(t),await super.onStart()}async onHardTimeoutExpired(t){const n=await this.ctx.storage.get(d);if(t?.generation!==void 0&&t.generation!==n||this.ctx.container?.running!==!0)return;const e=i(this.lunoraName,this.instanceId(),"stop","hard timeout reached");this.surfaceInStudioLogs(e),await this.stop()}async onStop(t){const n=i(this.lunoraName,this.instanceId(),"stop",`${t.reason} (exit ${String(t.exitCode)})`);this.surfaceInStudioLogs(n),this.lunoraReadiness=void 0,this.lunoraStops+=1,await super.onStop(t)}refuseReserved(t){if($(t))return new Response(`container "${this.lunoraName}": /${m}/* is reserved for Lunora's own container routes; use exec.`,{status:403})}async afterContainerStart(t){const n=this.lunoraReadiness;if(n!==void 0){await n;return}const e=(async()=>{t||await this.armHardTimeout(),await this.awaitContainerReadiness()})();this.lunoraReadiness=e;try{await e}catch(s){throw this.lunoraReadiness===e&&(this.lunoraReadiness=void 0),s}}beginStart(){const t=this.ctx.container?.running===!0;return t||(this.lunoraReadiness=void 0),t}async awaitReadinessGate(){if(this.lunoraReadyOn.length===0)return;if(this.lunoraReadiness===void 0){const{status:n}=await this.getState();if(n!=="healthy")return;this.lunoraReadiness=this.awaitContainerReadiness()}const t=this.lunoraReadiness;try{await t}catch(n){throw this.lunoraReadiness===t&&(this.lunoraReadiness=void 0),n}}async armHardTimeout(){if(this.lunoraHardTimeoutSeconds===void 0)return;const t=(await this.ctx.storage.get(d)??0)+1;await this.ctx.storage.put(d,t),await this.schedule(this.lunoraHardTimeoutSeconds,"onHardTimeoutExpired",{generation:t})}async resolveSecretsStoreEnv(){const t=this.lunoraSecretsStore;return t===void 0?{}:(this.lunoraSecretsStoreResolved??=(async()=>{const n=this.env,e={};for(const[s,a]of Object.entries(t)){const o=n[a];if(o===void 0||typeof o.get!="function")throw new u("INTERNAL",`container "${this.lunoraName}": secretsStore env "${s}" points at binding "${a}", which is not a Secrets Store binding on the Worker env. Add a \`secrets_store_secrets\` entry binding "${a}".`);const c=await o.get();if(typeof c!="string")throw new TypeError(`container "${this.lunoraName}": Secrets Store binding "${a}" (env "${s}") did not resolve to a string value.`);e[s]=c}return e})().catch(n=>{throw this.lunoraSecretsStoreResolved=void 0,n}),this.lunoraSecretsStoreResolved)}async readEnvOverride(){return this.lunoraEnvOverrideLoaded||(this.lunoraEnvOverride=await this.ctx.storage.get(h),this.lunoraEnvOverrideLoaded=!0),this.lunoraEnvOverride}async persistEnvOverride(t){const n=await this.readEnvOverride();if((this.ctx.container?.running===!0||this.startInFlight!==void 0)&&!C(n,t))throw new u("CONFLICT",`container "${this.lunoraName}": start({ envVars }) on an instance that is already running with a different env — the running container keeps its own. stop() it first (or destroy() it to also drop the override), then start again.`);await this.ctx.storage.put(h,t),this.lunoraEnvOverride=t}async applyStartEnv(){const t=await this.readEnvOverride();this.envVars=t===void 0?{...this.lunoraDeclaredEnv,...await this.resolveSecretsStoreEnv()}:{...t}}async awaitContainerReadiness(){if(this.lunoraReadyOn.length===0)return;const{container:t}=this.ctx;if(t===void 0)return;const n=Date.now()+E;await Promise.all(this.lunoraReadyOn.map(async e=>this.awaitReadinessCheck(t,e,n)))}async awaitReadinessCheck(t,n,e){const s=n.port??this.lunoraDefaultPort;if(s===void 0)throw new u("INTERNAL",`container "${this.lunoraName}": readyOn check "${n.path}" has no port — set the check's \`port\` or the container \`defaultPort\`.`);const a=n.status??200,o=n.path.startsWith("/")?n.path:`/${n.path}`,c=t.getTcpPort(s);for(;;){const l=Math.max(f,e-Date.now()),p=R(void 0,l,()=>new DOMException(`readiness probe timed out after ${String(l)}ms`,"TimeoutError"));try{if((await c.fetch(`http://container${o}`,{signal:p.signal})).status===a)return}catch{}finally{p.dispose()}if(Date.now()>=e)throw new u("INTERNAL",`container "${this.lunoraName}": readiness check "${n.path}" (port ${String(s)}) did not return ${String(a)} within ${String(E)}ms`);await new Promise(v=>{setTimeout(v,f)})}}surfaceInStudioLogs(t){L(this.env,t,this.lunoraJurisdiction).catch(()=>{})}instanceId(){try{const{id:t}=this.ctx;return typeof t?.toString=="function"?t.toString():"unknown"}catch{return"unknown"}}}export{W as ContainerProxy,k as LunoraContainer,Y as outboundParams};
|
|
1
|
+
import{LunoraError as u}from"@lunora/errors";import{a as $}from"../packem_shared/abort-deadline-jwbW9Ala.mjs";import{isManagedImage as P,resolveContainerEnvVars as I,parseDurationSeconds as L}from"../packem_shared/DURABLE_OBJECT_POLICY_FORBIDDEN_KEYS-Dk6cuK7G.mjs";import{D as C,C as O,p as H}from"../packem_shared/exec-BYLW70sR.mjs";import{a as j}from"../packem_shared/jurisdiction-DtE9s70w.mjs";import{Container as V}from"../packem_shared/ContainerProxy-CfXjS6ZI.mjs";import{ContainerProxy as dt,outboundParams as lt}from"../packem_shared/ContainerProxy-CfXjS6ZI.mjs";const M="lunora",U=(a,t,n,e)=>({container:a,event:n,instance:t,level:n==="error"?"error":"info",message:e,source:M,ts:Date.now(),type:"container"}),p=(a,t,n,e)=>{const r=U(a,t,n,e),s=JSON.stringify(r);return n==="error"?console.error(s):console.log(s),r},F=async(a,t,n,e)=>{const r=t.maxOutputBytes??C,s=await a([t.command,...t.args??[]],{...t.cwd===void 0?{}:{cwd:t.cwd},env:{...n,...t.env},stderr:"pipe",stdout:"pipe"}),o=new AbortController;let i=!1;const c={overflowed:!1,timedOut:!1},m=()=>c.timedOut,v=s.exitCode.finally(()=>{i=!0}),S=()=>{o.abort(),i||(i=!0,s.kill())},A=t.timeoutMs===void 0?void 0:setTimeout(()=>{c.timedOut=!0,S()},t.timeoutMs);let y=0;const R=async l=>{if(l===null)return"";const d=l.getReader(),f=new TextDecoder,h=()=>{d.cancel().catch(()=>{})};let E="";o.signal.addEventListener("abort",h);try{for(;!o.signal.aborted;){const{done:D,value:T}=await d.read();if(D)break;if(y+=T.byteLength,y>r){c.overflowed=!0,S();break}E+=f.decode(T,{stream:!0})}return E+f.decode()}finally{o.signal.removeEventListener("abort",h),d.cancel().catch(()=>{})}};try{const[l,d]=await Promise.allSettled([R(s.stdout),R(s.stderr)]);if(c.overflowed)return new Response(`container "${e}": exec output exceeded ${String(r)} bytes; the process was killed`,{status:413});if(c.timedOut)return new Response(`container "${e}": exec timed out after ${String(t.timeoutMs)}ms`,{status:504});if(l.status==="rejected")throw l.reason;if(d.status==="rejected")throw d.reason;const f=await v;if(m())return new Response(`container "${e}": exec timed out after ${String(t.timeoutMs)}ms`,{status:504});const h=JSON.stringify({code:f,stderr:d.value,stdout:l.value});return new TextEncoder().encode(h).byteLength>r?new Response(`container "${e}": exec output exceeded ${String(r)} bytes once encoded`,{status:413}):new Response(h,{headers:{"content-type":"application/json"}})}finally{clearTimeout(A)}},k=async(a,t,n,e)=>{let r;try{r=await t.json()}catch{return new Response(`container "${e}": exec body must be JSON`,{status:400})}return typeof r.command!="string"||r.command.length===0?new Response(`container "${e}": exec requires a non-empty \`command\``,{status:400}):F(a,r,n,e)},B="__lunora_admin__:recordContainerEvent",N="__root__",J=a=>{if(a===null||typeof a!="object")return!1;const t=a;return typeof t.get=="function"&&typeof t.idFromName=="function"},G=(a,t)=>{const n=j(a,t);return typeof n.getByName=="function"?n.getByName(N):n.get(n.idFromName(N))},Q=async(a,t,n)=>{try{const e=a??{},r=e.SHARD;if(!J(r))return;const s=typeof e.LUNORA_ADMIN_TOKEN=="string"?e.LUNORA_ADMIN_TOKEN:void 0;if(!s||s.length===0)return;const o=new Request("https://shard.internal/rpc",{body:JSON.stringify({args:{event:t},functionPath:B}),headers:{authorization:`Bearer ${s}`,"content-type":"application/json"},method:"POST"});await G(r,n).fetch(o)}catch{}},g="__lunoraStartOverride",K=(a,t)=>{if(a===void 0||t===void 0)return a===t;const n=Object.keys(a);return n.length===Object.keys(t).length&&n.every(e=>Object.hasOwn(t,e)&&a[e]===t[e])},W=(a,t)=>K(a.envVars,t.envVars)&&a.image===t.image&&JSON.stringify(a.instanceType)===JSON.stringify(t.instanceType),z=(a,t,n)=>{if(P(t))return t;const e=a[t];if(e===void 0){const r=Object.keys(a);throw new u("BAD_REQUEST",`container "${n}": no image named "${t}" — ${r.length===0?"the container declares no images":`declared images: ${r.join(", ")}`}`)}return e},_=500,x=3e4,w="__lunoraHardTimeoutGeneration",b="__lunora",X=/[/\\]/u,Y=a=>{const t=a.indexOf(";");return t===-1?a:a.slice(0,t)},q=a=>H(a,n=>n.split(X).some(e=>Y(e).toLowerCase()===b)),Z=a=>{const t=a instanceof Request?a.url:String(a);return URL.parse(t,"https://container")?.pathname??t},tt="cf-container-target-port";class it extends V{lunoraJurisdiction;lunoraName;lunoraDefaultPort;lunoraHardTimeoutSeconds;lunoraReadiness;lunoraReadyOn;lunoraSecretsStore;lunoraDeclaredEnv;lunoraOverride;lunoraOverrideLoaded=!1;lunoraSecretsStoreResolved;lunoraDurableObjectScheduled;lunoraDefaultSelection;lunoraStops=0;constructor(t,n,e,r,s){const o=I(e,n,r);super(t,n,{defaultPort:e.defaultPort,entrypoint:e.entrypoint?[...e.entrypoint]:void 0,envVars:o,sleepAfter:e.sleepAfter}),this.lunoraDeclaredEnv={...o},e.enableInternet!==void 0&&(this.enableInternet=e.enableInternet),e.requiredPorts!==void 0&&(this.requiredPorts=[...e.requiredPorts]),e.interceptHttps!==void 0&&(this.interceptHttps=e.interceptHttps),e.allowedHosts!==void 0&&(this.allowedHosts=[...e.allowedHosts]),e.deniedHosts!==void 0&&(this.deniedHosts=[...e.deniedHosts]),e.pingEndpoint!==void 0&&(this.pingEndpoint=e.pingEndpoint),e.labels!==void 0&&(this.labels={...e.labels}),this.lunoraName=r??"container",this.lunoraJurisdiction=s,this.lunoraDefaultPort=e.defaultPort,this.lunoraReadyOn=e.readyOn?[...e.readyOn]:[],this.lunoraHardTimeoutSeconds=e.hardTimeout===void 0?void 0:L(e.hardTimeout),this.lunoraSecretsStore=e.secretsStore,this.lunoraDurableObjectScheduled=e.schedulingPolicy==="durable_object",this.lunoraDefaultSelection=e.schedulingPolicy==="durable_object"?{image:e.image,instanceType:e.instanceType}:{}}async lunoraSnapshot(t={}){if(!this.lunoraDurableObjectScheduled)throw new u("BAD_REQUEST",`container "${this.lunoraName}": snapshots need schedulingPolicy "durable_object"`);const{container:n}=this.ctx;if(n?.running!==!0||typeof n.snapshotContainer!="function")throw new u("BAD_REQUEST",`container "${this.lunoraName}": snapshot() needs a running container — start it first`);const{id:e,name:r,size:s}=await n.snapshotContainer({...t.name===void 0?{}:{name:t.name}});return{id:e,size:s,...r===void 0?{}:{name:r}}}async fetch(t){return this.refuseReserved(new URL(t.url).pathname)??super.fetch(t)}async containerFetch(...t){const n=this.refuseReserved(Z(t[0]));return n!==void 0?n:(await this.awaitReadinessGate(),super.containerFetch(...t))}async lunoraExec(t){const{pathname:n}=new URL(t.url);if(t.method!=="POST"||n!==O)return new Response(`container "${this.lunoraName}": lunoraExec only serves POST ${O}.`,{status:400});const e=t.headers.get(tt),r=e===null?this.defaultPort:Number.parseInt(e,10);if(r===void 0||Number.isNaN(r))return new Response(`container "${this.lunoraName}": exec needs a port — set \`defaultPort\` or exec through \`.port(n)\`.`,{status:400});await this.awaitReadinessGate();const{container:s}=this.ctx;if(typeof s?.exec!="function")return super.containerFetch(t,r);await this.startAndWaitForPorts(r),this.inflightRequests+=1,this.renewActivityTimeout();try{return await k(s.exec.bind(s),t,this.envVars??{},this.lunoraName)}finally{this.decrementInflight()}}async startAndWaitForPorts(...t){await this.applyStartEnv(),await this.applyStartSelection();const n=this.lunoraStops,e=this.beginStart();await super.startAndWaitForPorts(...t),await this.afterContainerStart(e&&this.lunoraStops===n)}async start(t,n){const{image:e,instanceType:r,snapshot:s,...o}=t??{};await this.recordStartOverride({envVars:t?.envVars,image:e,instanceType:r},s),await this.applyStartEnv(),await this.applyStartSelection(s!==void 0);const i=this.lunoraStops,c=this.beginStart();await super.start({...o,...s===void 0?{}:{containerSnapshot:s}},n),await this.afterContainerStart(c&&this.lunoraStops===i)}async destroy(){await super.destroy(),await this.ctx.storage.delete(g),this.lunoraOverride=void 0}async onActivityExpired(){const t=p(this.lunoraName,this.instanceId(),"sleep");this.surfaceInStudioLogs(t),await super.onActivityExpired()}onError(t){const n=p(this.lunoraName,this.instanceId(),"error",t instanceof Error?t.message:String(t));return this.surfaceInStudioLogs(n),super.onError(t)}async onStart(){const t=p(this.lunoraName,this.instanceId(),"start");this.surfaceInStudioLogs(t),await super.onStart()}async onHardTimeoutExpired(t){const n=await this.ctx.storage.get(w);if(t?.generation!==void 0&&t.generation!==n||this.ctx.container?.running!==!0)return;const e=p(this.lunoraName,this.instanceId(),"stop","hard timeout reached");this.surfaceInStudioLogs(e),await this.stop()}async onStop(t){const n=p(this.lunoraName,this.instanceId(),"stop",`${t.reason} (exit ${String(t.exitCode)})`);this.surfaceInStudioLogs(n),this.lunoraReadiness=void 0,this.lunoraStops+=1,await super.onStop(t)}refuseReserved(t){if(q(t))return new Response(`container "${this.lunoraName}": /${b}/* is reserved for Lunora's own container routes; use exec.`,{status:403})}async afterContainerStart(t){const n=this.lunoraReadiness;if(n!==void 0){await n;return}const e=(async()=>{t||await this.armHardTimeout(),await this.awaitContainerReadiness()})();this.lunoraReadiness=e;try{await e}catch(r){throw this.lunoraReadiness===e&&(this.lunoraReadiness=void 0),r}}beginStart(){const t=this.ctx.container?.running===!0;return t||(this.lunoraReadiness=void 0),t}async awaitReadinessGate(){if(this.lunoraReadyOn.length===0)return;if(this.lunoraReadiness===void 0){const{status:n}=await this.getState();if(n!=="healthy")return;this.lunoraReadiness=this.awaitContainerReadiness()}const t=this.lunoraReadiness;try{await t}catch(n){throw this.lunoraReadiness===t&&(this.lunoraReadiness=void 0),n}}async armHardTimeout(){if(this.lunoraHardTimeoutSeconds===void 0)return;const t=(await this.ctx.storage.get(w)??0)+1;await this.ctx.storage.put(w,t),await this.schedule(this.lunoraHardTimeoutSeconds,"onHardTimeoutExpired",{generation:t})}async resolveSecretsStoreEnv(){const t=this.lunoraSecretsStore;return t===void 0?{}:(this.lunoraSecretsStoreResolved??=(async()=>{const n=this.env,e={};for(const[r,s]of Object.entries(t)){const o=n[s];if(o===void 0||typeof o.get!="function")throw new u("INTERNAL",`container "${this.lunoraName}": secretsStore env "${r}" points at binding "${s}", which is not a Secrets Store binding on the Worker env. Add a \`secrets_store_secrets\` entry binding "${s}".`);const i=await o.get();if(typeof i!="string")throw new TypeError(`container "${this.lunoraName}": Secrets Store binding "${s}" (env "${r}") did not resolve to a string value.`);e[r]=i}return e})().catch(n=>{throw this.lunoraSecretsStoreResolved=void 0,n}),this.lunoraSecretsStoreResolved)}async readOverride(){return this.lunoraOverrideLoaded||(this.lunoraOverride=await this.ctx.storage.get(g),this.lunoraOverrideLoaded=!0),this.lunoraOverride??{}}async recordStartOverride(t,n){const e=t.image!==void 0||t.instanceType!==void 0;if((e||n!==void 0)&&!this.lunoraDurableObjectScheduled)throw new u("BAD_REQUEST",`container "${this.lunoraName}": start({ image | instanceType | snapshot }) needs schedulingPolicy "durable_object" — a default-policy container's image and size are set in lunora/containers.ts`);if(t.image!==void 0&&n!==void 0)throw new u("BAD_REQUEST",`container "${this.lunoraName}": start() takes an image or a snapshot, not both — the snapshot is the filesystem`);t.image!==void 0&&this.resolveImage(t.image);const r=await this.readOverride(),s={...r,...t.envVars===void 0?{}:{envVars:{...t.envVars}},...t.image===void 0?{}:{image:t.image},...t.instanceType===void 0?{}:{instanceType:t.instanceType}};if((this.ctx.container?.running===!0||this.startInFlight!==void 0)&&(n!==void 0||!W({...this.lunoraDefaultSelection,...r},{...this.lunoraDefaultSelection,...s})))throw new u("CONFLICT",`container "${this.lunoraName}": start() on an instance that is already running with a different env, image or size (or a snapshot to restore) — the running container keeps its own. stop() it first (or destroy() it to also drop the override), then start again.`);(e||t.envVars!==void 0)&&(await this.ctx.storage.put(g,s),this.lunoraOverride=s)}async applyStartEnv(){const{envVars:t}=await this.readOverride();this.envVars=t===void 0?{...this.lunoraDeclaredEnv,...await this.resolveSecretsStoreEnv()}:{...t}}async applyStartSelection(t=!1){if(!this.lunoraDurableObjectScheduled)return;const n={...this.lunoraDefaultSelection,...await this.readOverride()};if(n.image===void 0&&!t&&this.ctx.container?.running!==!0)throw new u("BAD_REQUEST",`container "${this.lunoraName}": no image to start — pass start({ image }) or start({ snapshot }), or set a default \`image\` in lunora/containers.ts`);this.instance=n.instanceType,this.image=n.image===void 0?void 0:this.resolveImage(n.image)}resolveImage(t){return z(this.ctx.container?.images??{},t,this.lunoraName)}async awaitContainerReadiness(){if(this.lunoraReadyOn.length===0)return;const{container:t}=this.ctx;if(t===void 0)return;const n=Date.now()+x;await Promise.all(this.lunoraReadyOn.map(async e=>this.awaitReadinessCheck(t,e,n)))}async awaitReadinessCheck(t,n,e){const r=n.port??this.lunoraDefaultPort;if(r===void 0)throw new u("INTERNAL",`container "${this.lunoraName}": readyOn check "${n.path}" has no port — set the check's \`port\` or the container \`defaultPort\`.`);const s=n.status??200,o=n.path.startsWith("/")?n.path:`/${n.path}`,i=t.getTcpPort(r);for(;;){const c=Math.max(_,e-Date.now()),m=$(void 0,c,()=>new DOMException(`readiness probe timed out after ${String(c)}ms`,"TimeoutError"));try{if((await i.fetch(`http://container${o}`,{signal:m.signal})).status===s)return}catch{}finally{m.dispose()}if(Date.now()>=e)throw new u("INTERNAL",`container "${this.lunoraName}": readiness check "${n.path}" (port ${String(r)}) did not return ${String(s)} within ${String(x)}ms`);await new Promise(v=>{setTimeout(v,_)})}}surfaceInStudioLogs(t){Q(this.env,t,this.lunoraJurisdiction).catch(()=>{})}instanceId(){try{const{id:t}=this.ctx;return typeof t?.toString=="function"?t.toString():"unknown"}catch{return"unknown"}}}export{dt as ContainerProxy,it as LunoraContainer,lt as outboundParams};
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { D as DurableObjectJurisdiction,
|
|
2
|
-
export type { B as BuildImageSource,
|
|
1
|
+
import { C as ContainerSnapshot, a as ContainerRuntimeInstanceType, D as DurableObjectJurisdiction, b as DefaultScheduledContainerConfig, c as DurableObjectScheduledContainerConfig, d as ContainerDefinition, e as ContainerImageSource, N as NormalizedContainerImage } from "./packem_shared/jurisdiction.d-CoLtDVr_.mjs";
|
|
2
|
+
export type { B as BuildImageSource, f as ContainerConfig, g as ContainerConfigBase, h as ContainerInstanceType, i as ContainerNamedImageSource, j as ContainerReadinessCheck, k as ContainerRollout, l as CustomContainerInstanceType, m as NamedContainerInstanceType, R as RegistryImageSource } from "./packem_shared/jurisdiction.d-CoLtDVr_.mjs";
|
|
3
3
|
/**
|
|
4
4
|
* Per-call options for `ContainerHandle.exec`.
|
|
5
5
|
*/
|
|
@@ -89,8 +89,26 @@ interface ContainerStartOptions {
|
|
|
89
89
|
* for as long as the instance exists.
|
|
90
90
|
*/
|
|
91
91
|
envVars?: Record<string, string>;
|
|
92
|
+
/**
|
|
93
|
+
* The image to start (`schedulingPolicy: "durable_object"` only): a key of
|
|
94
|
+
* the definition's `images`, or a Cloudflare-managed `"cloudflare/…"` image.
|
|
95
|
+
* Persisted like `envVars`, so the implicit restart after a sleep boots the
|
|
96
|
+
* same image. Mutually exclusive with `snapshot`.
|
|
97
|
+
*/
|
|
98
|
+
image?: string;
|
|
99
|
+
/**
|
|
100
|
+
* The instance size (`schedulingPolicy: "durable_object"` only). Persisted
|
|
101
|
+
* with `image`; defaults to the definition's `instanceType`, else `"lite"`.
|
|
102
|
+
*/
|
|
103
|
+
instanceType?: ContainerRuntimeInstanceType;
|
|
92
104
|
/** Metadata labels attached for metrics/observability. */
|
|
93
105
|
labels?: Record<string, string>;
|
|
106
|
+
/**
|
|
107
|
+
* Restore a filesystem saved by `snapshot()` instead of starting from an
|
|
108
|
+
* image (`schedulingPolicy: "durable_object"` only). Applies to this start
|
|
109
|
+
* only — a later restart after a sleep boots the image again.
|
|
110
|
+
*/
|
|
111
|
+
snapshot?: ContainerSnapshot;
|
|
94
112
|
}
|
|
95
113
|
/**
|
|
96
114
|
* A container instance's runtime state, as returned by `getState()`. Structural — the platform adds fields over time.
|
|
@@ -113,6 +131,10 @@ interface ContainerStubLike {
|
|
|
113
131
|
getState?: () => Promise<ContainerInstanceState>;
|
|
114
132
|
/** The container DO's exec entry (`LunoraContainer.lunoraExec`), the only way a request reaches `/__lunora/exec`. */
|
|
115
133
|
lunoraExec?: (request: Request) => Promise<Response>;
|
|
134
|
+
/** The container DO's snapshot entry (`LunoraContainer.lunoraSnapshot`). */
|
|
135
|
+
lunoraSnapshot?: (options?: {
|
|
136
|
+
name?: string;
|
|
137
|
+
}) => Promise<ContainerSnapshot>;
|
|
116
138
|
removeAllowedHost?: (hostname: string) => Promise<void>;
|
|
117
139
|
removeDeniedHost?: (hostname: string) => Promise<void>;
|
|
118
140
|
renewActivityTimeout?: () => Promise<void>;
|
|
@@ -199,6 +221,15 @@ interface ContainerInstanceHandle extends ContainerHandle {
|
|
|
199
221
|
* nor a WS message — e.g. a long out-of-band job running inside it.
|
|
200
222
|
*/
|
|
201
223
|
renewActivityTimeout: () => Promise<void>;
|
|
224
|
+
/**
|
|
225
|
+
* Save the running container's filesystem and return a handle to restore it
|
|
226
|
+
* with `start({ snapshot })` — here or in another instance of the same
|
|
227
|
+
* container (`schedulingPolicy: "durable_object"` only; Cloudflare beta).
|
|
228
|
+
* Memory and processes are not captured.
|
|
229
|
+
*/
|
|
230
|
+
snapshot: (options?: {
|
|
231
|
+
name?: string;
|
|
232
|
+
}) => Promise<ContainerSnapshot>;
|
|
202
233
|
/** Explicitly start the instance, optionally with per-instance env/entrypoint. */
|
|
203
234
|
start: (options?: ContainerStartOptions) => Promise<void>;
|
|
204
235
|
/** Stop the instance (optionally with a signal); it can start again on the next request. */
|
|
@@ -358,6 +389,14 @@ type ContainerTestHandler = (request: Request, instance: {
|
|
|
358
389
|
* ```
|
|
359
390
|
*/
|
|
360
391
|
declare const createContainerTestContext: (handlers: Record<string, ContainerTestHandler>) => Record<string, ContainerAccessor>;
|
|
392
|
+
/** Whether an image name is a Cloudflare-managed image (`cloudflare/debian-trixie`), which needs no `images` entry. */
|
|
393
|
+
declare const isManagedImage: (name: string) => boolean;
|
|
394
|
+
/**
|
|
395
|
+
* The default-policy keys the `durable_object` policy refuses — wrangler rejects
|
|
396
|
+
* `max_instances` under it, and a rollout has nothing to roll. One table, read by
|
|
397
|
+
* `defineContainer` and by codegen's static discovery.
|
|
398
|
+
*/
|
|
399
|
+
declare const DURABLE_OBJECT_POLICY_FORBIDDEN_KEYS: readonly ["maxInstances", "rollout"];
|
|
361
400
|
/**
|
|
362
401
|
* Normalize a `ContainerImageSource` into the shape wrangler wants: a
|
|
363
402
|
* Dockerfile path + build context for local builds, or a fully-qualified
|
|
@@ -391,10 +430,19 @@ declare const containerBindingName: (exportName: string) => string;
|
|
|
391
430
|
* three derive the tag from this one helper and can never disagree.
|
|
392
431
|
*/
|
|
393
432
|
declare const containerBuildTag: (exportName: string) => string;
|
|
433
|
+
/** Whether a registry reference is digest-pinned in the Cloudflare registry — the only kind a named image accepts. */
|
|
434
|
+
declare const isCloudflareRegistryDigest: (reference: unknown) => boolean;
|
|
394
435
|
/**
|
|
395
436
|
* `defineContainer` is part of the experimental `@lunora/container` API and may change without a major version bump.
|
|
396
437
|
*/
|
|
397
|
-
declare const defineContainer:
|
|
438
|
+
declare const defineContainer: {
|
|
439
|
+
(config: DefaultScheduledContainerConfig): DefaultScheduledContainerConfig & {
|
|
440
|
+
readonly isLunoraContainer: true;
|
|
441
|
+
};
|
|
442
|
+
(config: DurableObjectScheduledContainerConfig): DurableObjectScheduledContainerConfig & {
|
|
443
|
+
readonly isLunoraContainer: true;
|
|
444
|
+
};
|
|
445
|
+
};
|
|
398
446
|
/**
|
|
399
447
|
* True when a value is a `defineContainer` result (the runtime brand check).
|
|
400
448
|
*/
|
|
@@ -428,7 +476,7 @@ type ContainerAccessor,
|
|
|
428
476
|
* (which pulls in `@cloudflare/containers` → `cloudflare:workers`) lives
|
|
429
477
|
* behind the `@lunora/container/do` subpath.
|
|
430
478
|
*/
|
|
431
|
-
type ContainerBindingSpec, type
|
|
479
|
+
type ContainerBindingSpec, type ContainerDefinition,
|
|
432
480
|
/**
|
|
433
481
|
* `@lunora/container` — Cloudflare Containers for Lunora.
|
|
434
482
|
*
|
|
@@ -483,7 +531,7 @@ type ContainerInstanceState,
|
|
|
483
531
|
* (which pulls in `@cloudflare/containers` → `cloudflare:workers`) lives
|
|
484
532
|
* behind the `@lunora/container/do` subpath.
|
|
485
533
|
*/
|
|
486
|
-
type ContainerNamespaceLike,
|
|
534
|
+
type ContainerNamespaceLike, type ContainerRuntimeInstanceType, type ContainerSnapshot,
|
|
487
535
|
/**
|
|
488
536
|
* `@lunora/container` — Cloudflare Containers for Lunora.
|
|
489
537
|
*
|
|
@@ -505,7 +553,7 @@ type ContainerStartOptions,
|
|
|
505
553
|
* (which pulls in `@cloudflare/containers` → `cloudflare:workers`) lives
|
|
506
554
|
* behind the `@lunora/container/do` subpath.
|
|
507
555
|
*/
|
|
508
|
-
type ContainerTestHandler,
|
|
556
|
+
type ContainerTestHandler, DURABLE_OBJECT_POLICY_FORBIDDEN_KEYS, type DefaultScheduledContainerConfig,
|
|
509
557
|
/**
|
|
510
558
|
* `@lunora/container` — Cloudflare Containers for Lunora.
|
|
511
559
|
*
|
|
@@ -516,7 +564,7 @@ type ContainerTestHandler,
|
|
|
516
564
|
* (which pulls in `@cloudflare/containers` → `cloudflare:workers`) lives
|
|
517
565
|
* behind the `@lunora/container/do` subpath.
|
|
518
566
|
*/
|
|
519
|
-
type DurableObjectJurisdiction,
|
|
567
|
+
type DurableObjectJurisdiction, type DurableObjectScheduledContainerConfig,
|
|
520
568
|
/**
|
|
521
569
|
* `@lunora/container` — Cloudflare Containers for Lunora.
|
|
522
570
|
*
|
|
@@ -538,4 +586,4 @@ type InstanceRetryOptions, type NormalizedContainerImage,
|
|
|
538
586
|
* (which pulls in `@cloudflare/containers` → `cloudflare:workers`) lives
|
|
539
587
|
* behind the `@lunora/container/do` subpath.
|
|
540
588
|
*/
|
|
541
|
-
type PoolOptions, containerBindingName, containerBuildTag, containerClassName, createContainerContext, createContainerTestContext, defineContainer, isContainerDefinition, normalizeContainerImage, resolveContainerEnvVariables as resolveContainerEnvVars };
|
|
589
|
+
type PoolOptions, containerBindingName, containerBuildTag, containerClassName, createContainerContext, createContainerTestContext, defineContainer, isCloudflareRegistryDigest, isContainerDefinition, isManagedImage, normalizeContainerImage, resolveContainerEnvVariables as resolveContainerEnvVars };
|