@lunora/container 1.0.0-alpha.9 → 1.0.0-alpha.91

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 (37) hide show
  1. package/README.md +24 -5
  2. package/dist/bridge.d.mts +43 -33
  3. package/dist/bridge.d.ts +43 -33
  4. package/dist/bridge.mjs +1 -79
  5. package/dist/do/index.d.mts +354 -78
  6. package/dist/do/index.d.ts +354 -78
  7. package/dist/do/index.mjs +1 -321
  8. package/dist/index.d.mts +585 -148
  9. package/dist/index.d.ts +585 -148
  10. package/dist/index.mjs +1 -2
  11. package/dist/otel.d.mts +217 -0
  12. package/dist/otel.d.ts +217 -0
  13. package/dist/otel.mjs +1 -0
  14. package/dist/packem_shared/CONTAINER_EXEC_PATH-7Hcd6-zs.mjs +1 -0
  15. package/dist/packem_shared/CUSTOM_INSTANCE_MIN_MEMORY_MIB_PER_VCPU-D8Hy9Rxv.mjs +1 -0
  16. package/dist/packem_shared/ContainerProxy-CfXjS6ZI.mjs +27 -0
  17. package/dist/packem_shared/DURABLE_OBJECT_POLICY_FORBIDDEN_KEYS-gYi8RBUO.mjs +1 -0
  18. package/dist/packem_shared/LunoraSandboxContainer-DPnhJUu1.mjs +1 -0
  19. package/dist/packem_shared/abort-deadline-jwbW9Ala.mjs +1 -0
  20. package/dist/packem_shared/createContainerContext-p6QxWjsm.mjs +1 -0
  21. package/dist/packem_shared/exec-BYLW70sR.mjs +1 -0
  22. package/dist/packem_shared/jurisdiction-DtE9s70w.mjs +1 -0
  23. package/dist/packem_shared/jurisdiction.d-BwpauCoI.d.mts +471 -0
  24. package/dist/packem_shared/jurisdiction.d-BwpauCoI.d.ts +471 -0
  25. package/dist/packem_shared/sampling-D21rpu_s.mjs +1 -0
  26. package/dist/packem_shared/sandbox-types.d-DXlHnsEg.d.mts +110 -0
  27. package/dist/packem_shared/sandbox-types.d-DXlHnsEg.d.ts +110 -0
  28. package/dist/sandbox/index.d.mts +66 -0
  29. package/dist/sandbox/index.d.ts +66 -0
  30. package/dist/sandbox/index.mjs +1 -0
  31. package/package.json +12 -2
  32. package/dist/packem_shared/ContainerProxy-DWqUX_re.mjs +0 -1474
  33. package/dist/packem_shared/containerBindingName-BiTrAF1J.mjs +0 -224
  34. package/dist/packem_shared/createContainerContext-CIVzsY5m.mjs +0 -220
  35. package/dist/packem_shared/jurisdiction-CuPNcLDt.mjs +0 -13
  36. package/dist/packem_shared/jurisdiction.d-TwTGkgTg.d.mts +0 -266
  37. package/dist/packem_shared/jurisdiction.d-TwTGkgTg.d.ts +0 -266
package/dist/do/index.mjs CHANGED
@@ -1,321 +1 @@
1
- import { Container } from '../packem_shared/ContainerProxy-DWqUX_re.mjs';
2
- export { ContainerProxy, outboundParams } from '../packem_shared/ContainerProxy-DWqUX_re.mjs';
3
- import { LunoraError } from '@lunora/errors';
4
- import { resolveContainerEnvVars as resolveContainerEnvVariables, parseDurationSeconds } from '../packem_shared/containerBindingName-BiTrAF1J.mjs';
5
- import { a as applyJurisdiction } from '../packem_shared/jurisdiction-CuPNcLDt.mjs';
6
-
7
- const LUNORA_EVENT_SOURCE = "lunora";
8
- const buildContainerLifecycleEvent = (container, instance, event, message) => {
9
- return {
10
- container,
11
- event,
12
- instance,
13
- level: event === "error" ? "error" : "info",
14
- message,
15
- source: LUNORA_EVENT_SOURCE,
16
- ts: Date.now(),
17
- type: "container"
18
- };
19
- };
20
- const emitContainerLifecycle = (container, instance, event, message) => {
21
- const envelope = buildContainerLifecycleEvent(container, instance, event, message);
22
- const line = JSON.stringify(envelope);
23
- if (event === "error") {
24
- console.error(line);
25
- } else {
26
- console.log(line);
27
- }
28
- return envelope;
29
- };
30
-
31
- const RECORD_CONTAINER_EVENT_OP = "__lunora_admin__:recordContainerEvent";
32
- const ROOT_SHARD_NAME = "__root__";
33
- const isShardNamespace = (value) => {
34
- if (value === null || typeof value !== "object") {
35
- return false;
36
- }
37
- const candidate = value;
38
- return typeof candidate.get === "function" && typeof candidate.idFromName === "function";
39
- };
40
- const resolveRootShard = (namespace, jurisdiction) => {
41
- const pinned = applyJurisdiction(namespace, jurisdiction);
42
- if (typeof pinned.getByName === "function") {
43
- return pinned.getByName(ROOT_SHARD_NAME);
44
- }
45
- return pinned.get(pinned.idFromName(ROOT_SHARD_NAME));
46
- };
47
- const reportContainerLifecycle = async (env, envelope, jurisdiction) => {
48
- try {
49
- const envRecord = env ?? {};
50
- const namespace = envRecord["SHARD"];
51
- if (!isShardNamespace(namespace)) {
52
- return;
53
- }
54
- const adminBearer = typeof envRecord["LUNORA_ADMIN_TOKEN"] === "string" ? envRecord["LUNORA_ADMIN_TOKEN"] : void 0;
55
- if (!adminBearer || adminBearer.length === 0) {
56
- return;
57
- }
58
- const request = new Request("https://shard.internal/rpc", {
59
- body: JSON.stringify({ args: { event: envelope }, functionPath: RECORD_CONTAINER_EVENT_OP }),
60
- headers: { authorization: `Bearer ${adminBearer}`, "content-type": "application/json" },
61
- method: "POST"
62
- });
63
- await resolveRootShard(namespace, jurisdiction).fetch(request);
64
- } catch {
65
- }
66
- };
67
-
68
- const READINESS_POLL_INTERVAL_MS = 500;
69
- const READINESS_TIMEOUT_MS = 3e4;
70
- const HARD_TIMEOUT_GENERATION_KEY = "__lunoraHardTimeoutGeneration";
71
- class LunoraContainer extends Container {
72
- /**
73
- * Data-residency jurisdiction the app's DOs are pinned to (codegen passes the
74
- * schema's `.jurisdiction("…")`). Used to pin the best-effort lifecycle report
75
- * to the same region as the root shard. `undefined` ⇒ un-pinned.
76
- */
77
- lunoraJurisdiction;
78
- /** The `lunora/containers.ts` export name, for lifecycle log correlation. */
79
- lunoraName;
80
- /** Default port the readiness probes target when a check omits its own `port`. */
81
- lunoraDefaultPort;
82
- /** Hard-cap lifetime in whole seconds (from the `hardTimeout` config), or `undefined`. */
83
- lunoraHardTimeoutSeconds;
84
- /** Declarative readiness probes that gate request proxying (from the `readyOn` config). */
85
- lunoraReadyOn;
86
- /** Map of container env-var name → Worker Secrets Store binding name (from the `secretsStore` config). */
87
- lunoraSecretsStore;
88
- /** Memoised Secrets Store resolution: run once, then merged into `envVars` before the first start. */
89
- lunoraSecretsStoreResolved;
90
- constructor(context, env, definition, exportName, jurisdiction) {
91
- super(context, env, {
92
- defaultPort: definition.defaultPort,
93
- entrypoint: definition.entrypoint ? [...definition.entrypoint] : void 0,
94
- envVars: resolveContainerEnvVariables(definition, env, exportName),
95
- sleepAfter: definition.sleepAfter
96
- });
97
- if (definition.enableInternet !== void 0) {
98
- this.enableInternet = definition.enableInternet;
99
- }
100
- if (definition.requiredPorts !== void 0) {
101
- this.requiredPorts = [...definition.requiredPorts];
102
- }
103
- if (definition.interceptHttps !== void 0) {
104
- this.interceptHttps = definition.interceptHttps;
105
- }
106
- if (definition.allowedHosts !== void 0) {
107
- this.allowedHosts = [...definition.allowedHosts];
108
- }
109
- if (definition.deniedHosts !== void 0) {
110
- this.deniedHosts = [...definition.deniedHosts];
111
- }
112
- if (definition.pingEndpoint !== void 0) {
113
- this.pingEndpoint = definition.pingEndpoint;
114
- }
115
- if (definition.labels !== void 0) {
116
- this.labels = { ...definition.labels };
117
- }
118
- this.lunoraName = exportName ?? "container";
119
- this.lunoraJurisdiction = jurisdiction;
120
- this.lunoraDefaultPort = definition.defaultPort;
121
- this.lunoraReadyOn = definition.readyOn ? [...definition.readyOn] : [];
122
- this.lunoraHardTimeoutSeconds = definition.hardTimeout === void 0 ? void 0 : parseDurationSeconds(definition.hardTimeout);
123
- this.lunoraSecretsStore = definition.secretsStore;
124
- }
125
- /**
126
- * Proxy entry for every `ctx.containers.<name>` fetch. Resolves the
127
- * `secretsStore` bindings into `envVars` before delegating, so the values
128
- * are present when the base implicitly starts the container for this
129
- * request — a no-op when `secretsStore` is unset.
130
- */
131
- async containerFetch(...args) {
132
- await this.resolveSecretsStoreEnv();
133
- return super.containerFetch(...args);
134
- }
135
- /**
136
- * Explicit start (`ctx.containers.<name>.get(id).start()`). Resolves the
137
- * `secretsStore` bindings into `envVars` first, mirroring
138
- * {@link containerFetch}. A per-instance `start({ envVars })` replaces the
139
- * env set wholesale (base behavior), so the injected values only apply to a
140
- * bare `start()` — same as the static `env`/`secrets`. When the caller
141
- * supplies its own `envVars` we skip resolution entirely: those values would
142
- * be discarded anyway, so a missing/unreadable binding shouldn't fail a start
143
- * that never uses them.
144
- */
145
- async start(...args) {
146
- const [options] = args;
147
- if (options?.envVars === void 0) {
148
- await this.resolveSecretsStoreEnv();
149
- }
150
- return super.start(...args);
151
- }
152
- async onActivityExpired() {
153
- const envelope = emitContainerLifecycle(this.lunoraName, this.instanceId(), "sleep");
154
- this.surfaceInStudioLogs(envelope);
155
- await super.onActivityExpired();
156
- }
157
- onError(error) {
158
- const envelope = emitContainerLifecycle(this.lunoraName, this.instanceId(), "error", error instanceof Error ? error.message : String(error));
159
- this.surfaceInStudioLogs(envelope);
160
- return super.onError(error);
161
- }
162
- async onStart() {
163
- const envelope = emitContainerLifecycle(this.lunoraName, this.instanceId(), "start");
164
- this.surfaceInStudioLogs(envelope);
165
- await super.onStart();
166
- await this.armHardTimeout();
167
- await this.awaitContainerReadiness();
168
- }
169
- /**
170
- * Hook run when the container's `hardTimeout` elapses (dispatched by the base
171
- * scheduler via the run-generation-stamped schedule armed in
172
- * {@link onStart}). Default: stop the instance. Override to drain/checkpoint
173
- * first. A stale schedule from a previous run, or an already-stopped
174
- * instance, is ignored (upstream cloudflare/containers#85).
175
- */
176
- async onHardTimeoutExpired(payload) {
177
- const current = await this.ctx.storage.get(HARD_TIMEOUT_GENERATION_KEY);
178
- if (payload?.generation !== void 0 && payload.generation !== current) {
179
- return;
180
- }
181
- if (this.ctx.container?.running !== true) {
182
- return;
183
- }
184
- const envelope = emitContainerLifecycle(this.lunoraName, this.instanceId(), "stop", "hard timeout reached");
185
- this.surfaceInStudioLogs(envelope);
186
- await this.stop();
187
- }
188
- async onStop(parameters) {
189
- const envelope = emitContainerLifecycle(this.lunoraName, this.instanceId(), "stop", `${parameters.reason} (exit ${String(parameters.exitCode)})`);
190
- this.surfaceInStudioLogs(envelope);
191
- await super.onStop(parameters);
192
- }
193
- /**
194
- * Arm the hard-timeout kill via the base scheduler (so it integrates with
195
- * the container's own alarm machinery instead of fighting it). Bumps the run
196
- * generation and stamps the schedule with it, so {@link onHardTimeoutExpired}
197
- * can tell a fresh schedule from a stale one. No-op without a `hardTimeout`.
198
- */
199
- async armHardTimeout() {
200
- if (this.lunoraHardTimeoutSeconds === void 0) {
201
- return;
202
- }
203
- const generation = (await this.ctx.storage.get(HARD_TIMEOUT_GENERATION_KEY) ?? 0) + 1;
204
- await this.ctx.storage.put(HARD_TIMEOUT_GENERATION_KEY, generation);
205
- await this.schedule(this.lunoraHardTimeoutSeconds, "onHardTimeoutExpired", { generation });
206
- }
207
- /**
208
- * Resolve the `secretsStore` bindings (async `.get()`) once and merge the
209
- * values into `envVars`, so they're present when the base starts the
210
- * container. Memoised on the first call — every later start reuses the
211
- * resolved promise. A missing binding or a non-string value fails fast (the
212
- * start surfaces the error), the same fail-closed stance the static
213
- * `secrets` resolution takes for a missing Worker secret. No-op without
214
- * `secretsStore`.
215
- */
216
- async resolveSecretsStoreEnv() {
217
- const secretsStore = this.lunoraSecretsStore;
218
- if (secretsStore === void 0) {
219
- return;
220
- }
221
- this.lunoraSecretsStoreResolved ??= (async () => {
222
- const workerEnv = this.env;
223
- const resolved = {};
224
- for (const [envName, binding] of Object.entries(secretsStore)) {
225
- const store = workerEnv[binding];
226
- if (store === void 0 || typeof store.get !== "function") {
227
- throw new LunoraError(
228
- "INTERNAL",
229
- `container "${this.lunoraName}": secretsStore env "${envName}" points at binding "${binding}", which is not a Secrets Store binding on the Worker env. Add a \`secrets_store_secrets\` entry binding "${binding}".`
230
- );
231
- }
232
- const value = await store.get();
233
- if (typeof value !== "string") {
234
- throw new TypeError(
235
- `container "${this.lunoraName}": Secrets Store binding "${binding}" (env "${envName}") did not resolve to a string value.`
236
- );
237
- }
238
- resolved[envName] = value;
239
- }
240
- this.envVars = { ...this.envVars, ...resolved };
241
- })().catch((error) => {
242
- this.lunoraSecretsStoreResolved = void 0;
243
- throw error;
244
- });
245
- await this.lunoraSecretsStoreResolved;
246
- }
247
- /**
248
- * Block until every `readyOn` probe responds with its expected status, or
249
- * throw once the readiness budget is spent. Probes run in parallel and hit
250
- * the container's TCP port directly (NOT `containerFetch`, which would
251
- * recurse back into the start path). No-op without `readyOn`.
252
- */
253
- async awaitContainerReadiness() {
254
- if (this.lunoraReadyOn.length === 0) {
255
- return;
256
- }
257
- const { container } = this.ctx;
258
- if (container === void 0) {
259
- return;
260
- }
261
- const deadline = Date.now() + READINESS_TIMEOUT_MS;
262
- await Promise.all(this.lunoraReadyOn.map(async (check) => this.awaitReadinessCheck(container, check, deadline)));
263
- }
264
- /** Poll one readiness probe until it returns its expected status or the shared deadline passes. */
265
- async awaitReadinessCheck(container, check, deadline) {
266
- const port = check.port ?? this.lunoraDefaultPort;
267
- if (port === void 0) {
268
- throw new LunoraError(
269
- "INTERNAL",
270
- `container "${this.lunoraName}": readyOn check "${check.path}" has no port — set the check's \`port\` or the container \`defaultPort\`.`
271
- );
272
- }
273
- const expectedStatus = check.status ?? 200;
274
- const path = check.path.startsWith("/") ? check.path : `/${check.path}`;
275
- const tcpPort = container.getTcpPort(port);
276
- for (; ; ) {
277
- try {
278
- const response = await tcpPort.fetch(`http://container${path}`);
279
- if (response.status === expectedStatus) {
280
- return;
281
- }
282
- } catch {
283
- }
284
- if (Date.now() >= deadline) {
285
- throw new LunoraError(
286
- "INTERNAL",
287
- `container "${this.lunoraName}": readiness check "${check.path}" (port ${String(port)}) did not return ${String(expectedStatus)} within ${String(READINESS_TIMEOUT_MS)}ms`
288
- );
289
- }
290
- await new Promise((resolve) => {
291
- setTimeout(resolve, READINESS_POLL_INTERVAL_MS);
292
- });
293
- }
294
- }
295
- /**
296
- * Best-effort push of `envelope` into the root ShardDO's log buffer so it
297
- * also appears in the Studio Logs panel (the terminal already has it via
298
- * `emitContainerLifecycle`). Fire-and-forget and fully swallowed: a missing
299
- * `SHARD` binding, a missing admin token, or a fetch failure NEVER throws
300
- * out of a lifecycle hook — the `console` path stays the source of truth.
301
- */
302
- surfaceInStudioLogs(envelope) {
303
- reportContainerLifecycle(this.env, envelope, this.lunoraJurisdiction).catch(() => {
304
- });
305
- }
306
- /**
307
- * Per-instance correlation id: the Durable Object id, which Cloudflare also
308
- * injects into the container as `CLOUDFLARE_DURABLE_OBJECT_ID`. Read
309
- * defensively — the id shape varies and isn't worth crashing a hook over.
310
- */
311
- instanceId() {
312
- try {
313
- const { id } = this.ctx;
314
- return typeof id?.toString === "function" ? id.toString() : "unknown";
315
- } catch {
316
- return "unknown";
317
- }
318
- }
319
- }
320
-
321
- export { LunoraContainer };
1
+ import{LunoraError as u}from"@lunora/errors";import{a as A}from"../packem_shared/abort-deadline-jwbW9Ala.mjs";import{isManagedImage as C,resolveContainerEnvVars as I,parseDurationSeconds as L}from"../packem_shared/DURABLE_OBJECT_POLICY_FORBIDDEN_KEYS-gYi8RBUO.mjs";import{D as H,C as O,p as M}from"../packem_shared/exec-BYLW70sR.mjs";import{a as j}from"../packem_shared/jurisdiction-DtE9s70w.mjs";import{RpcTarget as V}from"cloudflare:workers";import{Container as U}from"../packem_shared/ContainerProxy-CfXjS6ZI.mjs";import{ContainerProxy as wt,outboundParams as ft}from"../packem_shared/ContainerProxy-CfXjS6ZI.mjs";const k="lunora",B=(s,t,e,n)=>({container:s,event:e,instance:t,level:e==="error"?"error":"info",message:n,source:k,ts:Date.now(),type:"container"}),v=(s,t,e,n)=>{const r=B(s,t,e,n),a=JSON.stringify(r);return e==="error"?console.error(a):console.log(a),r},F=async(s,t,e,n)=>{const r=t.maxOutputBytes??H,a=await s([t.command,...t.args??[]],{...t.cwd===void 0?{}:{cwd:t.cwd},env:{...e,...t.env},stderr:"pipe",stdout:"pipe"}),o=new AbortController;let i=!1;const c={overflowed:!1,timedOut:!1},m=()=>c.timedOut,h=a.exitCode.finally(()=>{i=!0});h.catch(()=>{});const y=()=>{o.abort(),i||(i=!0,a.kill())},$=t.timeoutMs===void 0?void 0:setTimeout(()=>{c.timedOut=!0,y()},t.timeoutMs);let S=0;const R=async l=>{if(l===null)return"";const d=l.getReader(),w=new TextDecoder,p=()=>{d.cancel().catch(()=>{})};let E="";o.signal.addEventListener("abort",p);try{for(;!o.signal.aborted;){const{done:P,value:T}=await d.read();if(P)break;if(S+=T.byteLength,S>r){c.overflowed=!0,y();break}E+=w.decode(T,{stream:!0})}return E+w.decode()}finally{o.signal.removeEventListener("abort",p),d.cancel().catch(()=>{})}};try{const[l,d]=await Promise.allSettled([R(a.stdout),R(a.stderr)]);if(c.overflowed)return new Response(`container "${n}": exec output exceeded ${String(r)} bytes; the process was killed`,{status:413});if(c.timedOut)return new Response(`container "${n}": 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 w=await h;if(m())return new Response(`container "${n}": exec timed out after ${String(t.timeoutMs)}ms`,{status:504});const p=JSON.stringify({code:w,stderr:d.value,stdout:l.value});return new TextEncoder().encode(p).byteLength>r?new Response(`container "${n}": exec output exceeded ${String(r)} bytes once encoded`,{status:413}):new Response(p,{headers:{"content-type":"application/json"}})}finally{clearTimeout($)}},J=async(s,t,e,n)=>{let r;try{r=await t.json()}catch{return new Response(`container "${n}": exec body must be JSON`,{status:400})}return typeof r.command!="string"||r.command.length===0?new Response(`container "${n}": exec requires a non-empty \`command\``,{status:400}):F(s,r,e,n)},z="__lunora_admin__:recordContainerEvent",N="__root__",G=s=>{if(s===null||typeof s!="object")return!1;const t=s;return typeof t.get=="function"&&typeof t.idFromName=="function"},Q=(s,t)=>{const e=j(s,t);return typeof e.getByName=="function"?e.getByName(N):e.get(e.idFromName(N))},W=async(s,t,e)=>{try{const n=s??{},r=n.SHARD;if(!G(r))return;const a=typeof n.LUNORA_ADMIN_TOKEN=="string"?n.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:z}),headers:{authorization:`Bearer ${a}`,"content-type":"application/json"},method:"POST"});await Q(r,e).fetch(o)}catch{}},K=1048576,_=s=>{if(s==null)return null;const{readable:t,writable:e}=new TransformStream(void 0,new ByteLengthQueuingStrategy({highWaterMark:K}));return s.pipeTo(e).catch(()=>{}),t};class Y extends V{#t;#e;constructor(t,e){super(),this.#t=t,this.#e=e}async exitCode(){return this.#e}kill(t){this.#t.kill(t)}resize(t,e){if(this.#t.isPty!==!0||typeof this.#t.resize!="function")throw new TypeError("resize() needs a process spawned with `pty`");this.#t.resize(t,e)}}const X=async(s,t,e,n)=>{const r=t.pty===void 0?void 0:{cols:t.pty.cols??80,rows:t.pty.rows??24};let a;try{a=await s([t.command,...t.args??[]],{...t.cwd===void 0?{}:{cwd:t.cwd},env:{...e,...t.env},...r===void 0?{stderr:"pipe"}:{pty:r},...t.stdin===void 0||t.stdin===!1?{}:{stdin:t.stdin===!0?"pipe":t.stdin},stdout:"pipe"})}catch(c){throw n(),c}const o=A(void 0,t.timeoutMs,()=>new DOMException(`spawn timed out after ${String(t.timeoutMs)}ms`,"TimeoutError"));o.signal?.addEventListener("abort",()=>{a.kill()});const i=a.exitCode.finally(()=>{o.dispose(),n()});return i.catch(()=>{}),{control:new Y(a,i),isPty:a.isPty===!0,pid:a.pid??-1,stderr:_(a.stderr),stdin:t.stdin===!0?a.stdin??null:null,stdout:_(a.stdout)}},f="__lunoraStartOverride",Z=(s,t)=>{if(s===void 0||t===void 0)return s===t;const e=Object.keys(s);return e.length===Object.keys(t).length&&e.every(n=>Object.hasOwn(t,n)&&s[n]===t[n])},q=(s,t)=>Z(s.envVars,t.envVars)&&s.image===t.image&&JSON.stringify(s.instanceType)===JSON.stringify(t.instanceType),tt=(s,t,e)=>{if(C(t))return t;const n=s[t];if(n===void 0){const r=Object.keys(s);throw new u("BAD_REQUEST",`container "${e}": no image named "${t}" — ${r.length===0?"the container declares no images":`declared images: ${r.join(", ")}`}`)}return n},x=500,b=3e4,g="__lunoraHardTimeoutGeneration",D="__lunora",et=/[/\\]/u,nt=s=>{const t=s.indexOf(";");return t===-1?s:s.slice(0,t)},rt=s=>M(s,e=>e.split(et).some(n=>nt(n).toLowerCase()===D)),at=s=>{const t=s instanceof Request?s.url:String(s);return URL.parse(t,"https://container")?.pathname??t},st="cf-container-target-port";class pt extends U{lunoraName;lunoraJurisdiction;lunoraDefaultPort;lunoraHardTimeoutSeconds;lunoraReadiness;lunoraReadyOn;lunoraSecretsStore;lunoraDeclaredEnv;lunoraOverride;lunoraOverrideLoaded=!1;lunoraSecretsStoreResolved;lunoraDurableObjectScheduled;lunoraDefaultSelection;lunoraStops=0;constructor(t,e,n,r,a){const o=I(n,e,r);super(t,e,{defaultPort:n.defaultPort,entrypoint:n.entrypoint?[...n.entrypoint]:void 0,envVars:o,sleepAfter:n.sleepAfter}),this.lunoraDeclaredEnv={...o},n.enableInternet!==void 0&&(this.enableInternet=n.enableInternet),n.requiredPorts!==void 0&&(this.requiredPorts=[...n.requiredPorts]),n.interceptHttps!==void 0&&(this.interceptHttps=n.interceptHttps),n.allowedHosts!==void 0&&(this.allowedHosts=[...n.allowedHosts]),n.deniedHosts!==void 0&&(this.deniedHosts=[...n.deniedHosts]),n.pingEndpoint!==void 0&&(this.pingEndpoint=n.pingEndpoint),n.labels!==void 0&&(this.labels={...n.labels}),this.lunoraName=r??"container",this.lunoraJurisdiction=a,this.lunoraDefaultPort=n.defaultPort,this.lunoraReadyOn=n.readyOn?[...n.readyOn]:[],this.lunoraHardTimeoutSeconds=n.hardTimeout===void 0?void 0:L(n.hardTimeout),this.lunoraSecretsStore=n.secretsStore,this.lunoraDurableObjectScheduled=n.schedulingPolicy==="durable_object",this.lunoraDefaultSelection=n.schedulingPolicy==="durable_object"?{image:n.image,instanceType:n.instanceType}:{}}async lunoraSnapshot(t={}){if(!this.lunoraDurableObjectScheduled)throw new u("BAD_REQUEST",`container "${this.lunoraName}": snapshots need schedulingPolicy "durable_object"`);const{container:e}=this.ctx;if(e?.running!==!0||typeof e.snapshotContainer!="function")throw new u("BAD_REQUEST",`container "${this.lunoraName}": snapshot() needs a running container — start it first`);const{id:n,name:r,size:a}=await e.snapshotContainer({...t.name===void 0?{}:{name:t.name}});return{id:n,size:a,...r===void 0?{}:{name:r}}}async fetch(t){return this.refuseReserved(new URL(t.url).pathname)??super.fetch(t)}async containerFetch(...t){const e=this.refuseReserved(at(t[0]));return e!==void 0?e:(await this.awaitReadinessGate(),super.containerFetch(...t))}async lunoraExec(t){const{pathname:e}=new URL(t.url);if(t.method!=="POST"||e!==O)return new Response(`container "${this.lunoraName}": lunoraExec only serves POST ${O}.`,{status:400});const n=t.headers.get(st),r=n===null?this.defaultPort:Number.parseInt(n,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:a}=this.ctx;if(typeof a?.exec!="function")return super.containerFetch(t,r);await this.startAndWaitForPorts(r),this.inflightRequests+=1,this.renewActivityTimeout();try{return await J(a.exec.bind(a),t,this.envVars??{},this.lunoraName)}finally{this.decrementInflight()}}async lunoraSpawn(t){const{container:e,release:n}=await this.lunoraAcquire("spawn");return X(e.exec.bind(e),t,this.envVars??{},n)}async startAndWaitForPorts(...t){await this.applyStartEnv(),await this.applyStartSelection(),await this.beforeContainerStart();const e=this.lunoraStops,n=this.beginStart();await super.startAndWaitForPorts(...t),await this.afterContainerStart(n&&this.lunoraStops===e)}async start(t,e){const{image:n,instanceType:r,snapshot:a,...o}=t??{};await this.recordStartOverride({envVars:t?.envVars,image:n,instanceType:r},a),await this.applyStartEnv(),await this.applyStartSelection(a!==void 0),await this.beforeContainerStart();const i=this.lunoraStops,c=this.beginStart();await super.start({...o,...a===void 0?{}:{containerSnapshot:a}},e),await this.afterContainerStart(c&&this.lunoraStops===i)}async destroy(){await super.destroy(),await this.ctx.storage.delete(f),this.lunoraOverride=void 0}async onActivityExpired(){const t=v(this.lunoraName,this.instanceId(),"sleep");this.surfaceInStudioLogs(t),await super.onActivityExpired()}onError(t){const e=v(this.lunoraName,this.instanceId(),"error",t instanceof Error?t.message:String(t));return this.surfaceInStudioLogs(e),super.onError(t)}async onStart(){const t=v(this.lunoraName,this.instanceId(),"start");this.surfaceInStudioLogs(t),await super.onStart()}async onHardTimeoutExpired(t){const e=await this.ctx.storage.get(g);if(t?.generation!==void 0&&t.generation!==e||this.ctx.container?.running!==!0)return;const n=v(this.lunoraName,this.instanceId(),"stop","hard timeout reached");this.surfaceInStudioLogs(n),await this.stop()}async onStop(t){const e=v(this.lunoraName,this.instanceId(),"stop",`${t.reason} (exit ${String(t.exitCode)})`);this.surfaceInStudioLogs(e),this.lunoraReadiness=void 0,this.lunoraStops+=1,await super.onStop(t)}async lunoraAcquire(t){const{container:e}=this.ctx;if(typeof e?.exec!="function")throw new u("NOT_IMPLEMENTED",`container "${this.lunoraName}": ${t}() needs the runtime's native ctx.container.exec(), which this host does not provide`);await this.awaitReadinessGate(),e.running||await this.start(),this.inflightRequests+=1,this.renewActivityTimeout();let n=!1;return{container:e,release:()=>{n||(n=!0,this.decrementInflight())}}}async beforeContainerStart(){}refuseReserved(t){if(rt(t))return new Response(`container "${this.lunoraName}": /${D}/* is reserved for Lunora's own container routes; use exec.`,{status:403})}async afterContainerStart(t){const e=this.lunoraReadiness;if(e!==void 0){await e;return}const n=(async()=>{t||await this.armHardTimeout(),await this.awaitContainerReadiness()})();this.lunoraReadiness=n;try{await n}catch(r){throw this.lunoraReadiness===n&&(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:e}=await this.getState();if(e!=="healthy")return;this.lunoraReadiness=this.awaitContainerReadiness()}const t=this.lunoraReadiness;try{await t}catch(e){throw this.lunoraReadiness===t&&(this.lunoraReadiness=void 0),e}}async armHardTimeout(){if(this.lunoraHardTimeoutSeconds===void 0)return;const t=(await this.ctx.storage.get(g)??0)+1;await this.ctx.storage.put(g,t),await this.schedule(this.lunoraHardTimeoutSeconds,"onHardTimeoutExpired",{generation:t})}async resolveSecretsStoreEnv(){const t=this.lunoraSecretsStore;return t===void 0?{}:(this.lunoraSecretsStoreResolved??=(async()=>{const e=this.env,n={};for(const[r,a]of Object.entries(t)){const o=e[a];if(o===void 0||typeof o.get!="function")throw new u("INTERNAL",`container "${this.lunoraName}": secretsStore env "${r}" points at binding "${a}", which is not a Secrets Store binding on the Worker env. Add a \`secrets_store_secrets\` entry binding "${a}".`);const i=await o.get();if(typeof i!="string")throw new TypeError(`container "${this.lunoraName}": Secrets Store binding "${a}" (env "${r}") did not resolve to a string value.`);n[r]=i}return n})().catch(e=>{throw this.lunoraSecretsStoreResolved=void 0,e}),this.lunoraSecretsStoreResolved)}async readOverride(){return this.lunoraOverrideLoaded||(this.lunoraOverride=await this.ctx.storage.get(f),this.lunoraOverrideLoaded=!0),this.lunoraOverride??{}}async recordStartOverride(t,e){const n=t.image!==void 0||t.instanceType!==void 0;if((n||e!==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&&e!==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(),a={...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)&&(e!==void 0||!q({...this.lunoraDefaultSelection,...r},{...this.lunoraDefaultSelection,...a})))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.`);(n||t.envVars!==void 0)&&(await this.ctx.storage.put(f,a),this.lunoraOverride=a)}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 e={...this.lunoraDefaultSelection,...await this.readOverride()};if(e.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=e.instanceType,this.image=e.image===void 0?void 0:this.resolveImage(e.image)}resolveImage(t){return tt(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 e=Date.now()+b;await Promise.all(this.lunoraReadyOn.map(async n=>this.awaitReadinessCheck(t,n,e)))}async awaitReadinessCheck(t,e,n){const r=e.port??this.lunoraDefaultPort;if(r===void 0)throw new u("INTERNAL",`container "${this.lunoraName}": readyOn check "${e.path}" has no port — set the check's \`port\` or the container \`defaultPort\`.`);const a=e.status??200,o=e.path.startsWith("/")?e.path:`/${e.path}`,i=t.getTcpPort(r);for(;;){const c=Math.max(x,n-Date.now()),m=A(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===a)return}catch{}finally{m.dispose()}if(Date.now()>=n)throw new u("INTERNAL",`container "${this.lunoraName}": readiness check "${e.path}" (port ${String(r)}) did not return ${String(a)} within ${String(b)}ms`);await new Promise(h=>{setTimeout(h,x)})}}surfaceInStudioLogs(t){W(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{wt as ContainerProxy,pt as LunoraContainer,ft as outboundParams};