@lunora/container 1.0.0-alpha.22 → 1.0.0-alpha.24

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/dist/bridge.d.mts CHANGED
@@ -27,7 +27,7 @@ interface ContainerBridgeOptions {
27
27
  /** Injectable `fetch` (tests / non-global runtimes). Defaults to `globalThis.fetch`. */
28
28
  fetch?: FetchLike;
29
29
  /**
30
- * Bearer token sent as `Authorization: Bearer <token>`. Your Worker's
30
+ * Bearer token sent as `Authorization: Bearer <token>`. Your Worker's
31
31
  * `resolveIdentity` maps it to the identity the called functions run as.
32
32
  * Pass it to the container as a `secret`, never bake it into the image.
33
33
  */
package/dist/bridge.d.ts CHANGED
@@ -27,7 +27,7 @@ interface ContainerBridgeOptions {
27
27
  /** Injectable `fetch` (tests / non-global runtimes). Defaults to `globalThis.fetch`. */
28
28
  fetch?: FetchLike;
29
29
  /**
30
- * Bearer token sent as `Authorization: Bearer &lt;token>`. Your Worker's
30
+ * Bearer token sent as `Authorization: Bearer <token>`. Your Worker's
31
31
  * `resolveIdentity` maps it to the identity the called functions run as.
32
32
  * Pass it to the container as a `secret`, never bake it into the image.
33
33
  */
@@ -1,5 +1,5 @@
1
1
  import { DurableObject, WorkerEntrypoint } from 'cloudflare:workers';
2
- import { a as ContainerDefinition, D as DurableObjectJurisdiction } from "../packem_shared/jurisdiction.d-8oUUvrew.mjs";
2
+ import { a as ContainerDefinition, D as DurableObjectJurisdiction } from "../packem_shared/jurisdiction.d-DeQ3rtYK.mjs";
3
3
  /**
4
4
  * ContainerStartOptions as they come from worker types
5
5
  */
@@ -523,14 +523,14 @@ declare class LunoraContainer<Env = unknown> extends Container<Env> {
523
523
  private lunoraSecretsStoreResolved?;
524
524
  constructor(context: DurableObjectContext, env: Env, definition: ContainerDefinition, exportName?: string, jurisdiction?: DurableObjectJurisdiction);
525
525
  /**
526
- * Proxy entry for every `ctx.containers.&lt;name>` fetch. Resolves the
526
+ * Proxy entry for every `ctx.containers.<name>` fetch. Resolves the
527
527
  * `secretsStore` bindings into `envVars` before delegating, so the values
528
528
  * are present when the base implicitly starts the container for this
529
529
  * request — a no-op when `secretsStore` is unset.
530
530
  */
531
531
  override containerFetch(...args: Parameters<Container<Env>["containerFetch"]>): Promise<Response>;
532
532
  /**
533
- * Explicit start (`ctx.containers.&lt;name>.get(id).start()`). Resolves the
533
+ * Explicit start (`ctx.containers.<name>.get(id).start()`). Resolves the
534
534
  * `secretsStore` bindings into `envVars` first, mirroring
535
535
  * {@link containerFetch}. A per-instance `start({ envVars })` replaces the
536
536
  * env set wholesale (base behavior), so the injected values only apply to a
@@ -1,5 +1,5 @@
1
1
  import { DurableObject, WorkerEntrypoint } from 'cloudflare:workers';
2
- import { a as ContainerDefinition, D as DurableObjectJurisdiction } from "../packem_shared/jurisdiction.d-8oUUvrew.js";
2
+ import { a as ContainerDefinition, D as DurableObjectJurisdiction } from "../packem_shared/jurisdiction.d-DeQ3rtYK.js";
3
3
  /**
4
4
  * ContainerStartOptions as they come from worker types
5
5
  */
@@ -523,14 +523,14 @@ declare class LunoraContainer<Env = unknown> extends Container<Env> {
523
523
  private lunoraSecretsStoreResolved?;
524
524
  constructor(context: DurableObjectContext, env: Env, definition: ContainerDefinition, exportName?: string, jurisdiction?: DurableObjectJurisdiction);
525
525
  /**
526
- * Proxy entry for every `ctx.containers.&lt;name>` fetch. Resolves the
526
+ * Proxy entry for every `ctx.containers.<name>` fetch. Resolves the
527
527
  * `secretsStore` bindings into `envVars` before delegating, so the values
528
528
  * are present when the base implicitly starts the container for this
529
529
  * request — a no-op when `secretsStore` is unset.
530
530
  */
531
531
  override containerFetch(...args: Parameters<Container<Env>["containerFetch"]>): Promise<Response>;
532
532
  /**
533
- * Explicit start (`ctx.containers.&lt;name>.get(id).start()`). Resolves the
533
+ * Explicit start (`ctx.containers.<name>.get(id).start()`). Resolves the
534
534
  * `secretsStore` bindings into `envVars` first, mirroring
535
535
  * {@link containerFetch}. A per-instance `start({ envVars })` replaces the
536
536
  * env set wholesale (base behavior), so the injected values only apply to a
package/dist/do/index.mjs CHANGED
@@ -1 +1 @@
1
- import{Container as S}from"../packem_shared/ContainerProxy-BsQAwSNX.mjs";import{ContainerProxy as A,outboundParams as L}from"../packem_shared/ContainerProxy-BsQAwSNX.mjs";import{LunoraError as u}from"@lunora/errors";import{resolveContainerEnvVars as g,parseDurationSeconds as v}from"../packem_shared/containerBindingName-DP2NqQV-.mjs";import{e as y}from"../packem_shared/jurisdiction-BKRNOTip.mjs";const w="lunora",N=(i,t,n,e)=>({container:i,event:n,instance:t,level:n==="error"?"error":"info",message:e,source:w,ts:Date.now(),type:"container"}),a=(i,t,n,e)=>{const o=N(i,t,n,e),r=JSON.stringify(o);return n==="error"?console.error(r):console.log(r),o},$="__lunora_admin__:recordContainerEvent",h="__root__",E=i=>{if(i===null||typeof i!="object")return!1;const t=i;return typeof t.get=="function"&&typeof t.idFromName=="function"},I=(i,t)=>{const n=y(i,t);return typeof n.getByName=="function"?n.getByName(h):n.get(n.idFromName(h))},T=async(i,t,n)=>{try{const e=i??{},o=e.SHARD;if(!E(o))return;const r=typeof e.LUNORA_ADMIN_TOKEN=="string"?e.LUNORA_ADMIN_TOKEN:void 0;if(!r||r.length===0)return;const s=new Request("https://shard.internal/rpc",{body:JSON.stringify({args:{event:t},functionPath:$}),headers:{authorization:`Bearer ${r}`,"content-type":"application/json"},method:"POST"});await I(o,n).fetch(s)}catch{}},l=500,p=3e4,d="__lunoraHardTimeoutGeneration";class _ extends S{lunoraJurisdiction;lunoraName;lunoraDefaultPort;lunoraHardTimeoutSeconds;lunoraReadyOn;lunoraSecretsStore;lunoraSecretsStoreResolved;constructor(t,n,e,o,r){super(t,n,{defaultPort:e.defaultPort,entrypoint:e.entrypoint?[...e.entrypoint]:void 0,envVars:g(e,n,o),sleepAfter:e.sleepAfter}),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=o??"container",this.lunoraJurisdiction=r,this.lunoraDefaultPort=e.defaultPort,this.lunoraReadyOn=e.readyOn?[...e.readyOn]:[],this.lunoraHardTimeoutSeconds=e.hardTimeout===void 0?void 0:v(e.hardTimeout),this.lunoraSecretsStore=e.secretsStore}async containerFetch(...t){return await this.resolveSecretsStoreEnv(),super.containerFetch(...t)}async start(...t){const[n]=t;return n?.envVars===void 0&&await this.resolveSecretsStoreEnv(),super.start(...t)}async onActivityExpired(){const t=a(this.lunoraName,this.instanceId(),"sleep");this.surfaceInStudioLogs(t),await super.onActivityExpired()}onError(t){const n=a(this.lunoraName,this.instanceId(),"error",t instanceof Error?t.message:String(t));return this.surfaceInStudioLogs(n),super.onError(t)}async onStart(){const t=a(this.lunoraName,this.instanceId(),"start");this.surfaceInStudioLogs(t),await super.onStart(),await this.armHardTimeout(),await this.awaitContainerReadiness()}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=a(this.lunoraName,this.instanceId(),"stop","hard timeout reached");this.surfaceInStudioLogs(e),await this.stop()}async onStop(t){const n=a(this.lunoraName,this.instanceId(),"stop",`${t.reason} (exit ${String(t.exitCode)})`);this.surfaceInStudioLogs(n),await super.onStop(t)}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;t!==void 0&&(this.lunoraSecretsStoreResolved??=(async()=>{const n=this.env,e={};for(const[o,r]of Object.entries(t)){const s=n[r];if(s===void 0||typeof s.get!="function")throw new u("INTERNAL",`container "${this.lunoraName}": secretsStore env "${o}" points at binding "${r}", which is not a Secrets Store binding on the Worker env. Add a \`secrets_store_secrets\` entry binding "${r}".`);const c=await s.get();if(typeof c!="string")throw new TypeError(`container "${this.lunoraName}": Secrets Store binding "${r}" (env "${o}") did not resolve to a string value.`);e[o]=c}this.envVars={...this.envVars,...e}})().catch(n=>{throw this.lunoraSecretsStoreResolved=void 0,n}),await this.lunoraSecretsStoreResolved)}async awaitContainerReadiness(){if(this.lunoraReadyOn.length===0)return;const{container:t}=this.ctx;if(t===void 0)return;const n=Date.now()+p;await Promise.all(this.lunoraReadyOn.map(async e=>this.awaitReadinessCheck(t,e,n)))}async awaitReadinessCheck(t,n,e){const o=n.port??this.lunoraDefaultPort;if(o===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 r=n.status??200,s=n.path.startsWith("/")?n.path:`/${n.path}`,c=t.getTcpPort(o);for(;;){const f=Math.max(l,e-Date.now());try{if((await c.fetch(`http://container${s}`,{signal:AbortSignal.timeout(f)})).status===r)return}catch{}if(Date.now()>=e)throw new u("INTERNAL",`container "${this.lunoraName}": readiness check "${n.path}" (port ${String(o)}) did not return ${String(r)} within ${String(p)}ms`);await new Promise(m=>{setTimeout(m,l)})}}surfaceInStudioLogs(t){T(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{A as ContainerProxy,_ as LunoraContainer,L as outboundParams};
1
+ import{Container as S}from"../packem_shared/ContainerProxy-BsQAwSNX.mjs";import{ContainerProxy as O,outboundParams as L}from"../packem_shared/ContainerProxy-BsQAwSNX.mjs";import{LunoraError as u}from"@lunora/errors";import{resolveContainerEnvVars as g,parseDurationSeconds as v}from"../packem_shared/containerBindingName-CP4v-2Cw.mjs";import{e as y}from"../packem_shared/jurisdiction-BKRNOTip.mjs";const w="lunora",N=(i,t,n,e)=>({container:i,event:n,instance:t,level:n==="error"?"error":"info",message:e,source:w,ts:Date.now(),type:"container"}),a=(i,t,n,e)=>{const o=N(i,t,n,e),r=JSON.stringify(o);return n==="error"?console.error(r):console.log(r),o},$="__lunora_admin__:recordContainerEvent",h="__root__",E=i=>{if(i===null||typeof i!="object")return!1;const t=i;return typeof t.get=="function"&&typeof t.idFromName=="function"},I=(i,t)=>{const n=y(i,t);return typeof n.getByName=="function"?n.getByName(h):n.get(n.idFromName(h))},T=async(i,t,n)=>{try{const e=i??{},o=e.SHARD;if(!E(o))return;const r=typeof e.LUNORA_ADMIN_TOKEN=="string"?e.LUNORA_ADMIN_TOKEN:void 0;if(!r||r.length===0)return;const s=new Request("https://shard.internal/rpc",{body:JSON.stringify({args:{event:t},functionPath:$}),headers:{authorization:`Bearer ${r}`,"content-type":"application/json"},method:"POST"});await I(o,n).fetch(s)}catch{}},l=500,p=3e4,d="__lunoraHardTimeoutGeneration";class _ extends S{lunoraJurisdiction;lunoraName;lunoraDefaultPort;lunoraHardTimeoutSeconds;lunoraReadyOn;lunoraSecretsStore;lunoraSecretsStoreResolved;constructor(t,n,e,o,r){super(t,n,{defaultPort:e.defaultPort,entrypoint:e.entrypoint?[...e.entrypoint]:void 0,envVars:g(e,n,o),sleepAfter:e.sleepAfter}),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=o??"container",this.lunoraJurisdiction=r,this.lunoraDefaultPort=e.defaultPort,this.lunoraReadyOn=e.readyOn?[...e.readyOn]:[],this.lunoraHardTimeoutSeconds=e.hardTimeout===void 0?void 0:v(e.hardTimeout),this.lunoraSecretsStore=e.secretsStore}async containerFetch(...t){return await this.resolveSecretsStoreEnv(),super.containerFetch(...t)}async start(...t){const[n]=t;return n?.envVars===void 0&&await this.resolveSecretsStoreEnv(),super.start(...t)}async onActivityExpired(){const t=a(this.lunoraName,this.instanceId(),"sleep");this.surfaceInStudioLogs(t),await super.onActivityExpired()}onError(t){const n=a(this.lunoraName,this.instanceId(),"error",t instanceof Error?t.message:String(t));return this.surfaceInStudioLogs(n),super.onError(t)}async onStart(){const t=a(this.lunoraName,this.instanceId(),"start");this.surfaceInStudioLogs(t),await super.onStart(),await this.armHardTimeout(),await this.awaitContainerReadiness()}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=a(this.lunoraName,this.instanceId(),"stop","hard timeout reached");this.surfaceInStudioLogs(e),await this.stop()}async onStop(t){const n=a(this.lunoraName,this.instanceId(),"stop",`${t.reason} (exit ${String(t.exitCode)})`);this.surfaceInStudioLogs(n),await super.onStop(t)}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;t!==void 0&&(this.lunoraSecretsStoreResolved??=(async()=>{const n=this.env,e={};for(const[o,r]of Object.entries(t)){const s=n[r];if(s===void 0||typeof s.get!="function")throw new u("INTERNAL",`container "${this.lunoraName}": secretsStore env "${o}" points at binding "${r}", which is not a Secrets Store binding on the Worker env. Add a \`secrets_store_secrets\` entry binding "${r}".`);const c=await s.get();if(typeof c!="string")throw new TypeError(`container "${this.lunoraName}": Secrets Store binding "${r}" (env "${o}") did not resolve to a string value.`);e[o]=c}this.envVars={...this.envVars,...e}})().catch(n=>{throw this.lunoraSecretsStoreResolved=void 0,n}),await this.lunoraSecretsStoreResolved)}async awaitContainerReadiness(){if(this.lunoraReadyOn.length===0)return;const{container:t}=this.ctx;if(t===void 0)return;const n=Date.now()+p;await Promise.all(this.lunoraReadyOn.map(async e=>this.awaitReadinessCheck(t,e,n)))}async awaitReadinessCheck(t,n,e){const o=n.port??this.lunoraDefaultPort;if(o===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 r=n.status??200,s=n.path.startsWith("/")?n.path:`/${n.path}`,c=t.getTcpPort(o);for(;;){const f=Math.max(l,e-Date.now());try{if((await c.fetch(`http://container${s}`,{signal:AbortSignal.timeout(f)})).status===r)return}catch{}if(Date.now()>=e)throw new u("INTERNAL",`container "${this.lunoraName}": readiness check "${n.path}" (port ${String(o)}) did not return ${String(r)} within ${String(p)}ms`);await new Promise(m=>{setTimeout(m,l)})}}surfaceInStudioLogs(t){T(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{O as ContainerProxy,_ as LunoraContainer,L as outboundParams};
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { D as DurableObjectJurisdiction, C as ContainerConfig, a as ContainerDefinition, b as ContainerImageSource, N as NormalizedContainerImage } from "./packem_shared/jurisdiction.d-8oUUvrew.mjs";
2
- export type { B as BuildImageSource, c as ContainerInstanceType, d as ContainerReadinessCheck, e as ContainerRollout, f as CustomContainerInstanceType, g as NamedContainerInstanceType, R as RegistryImageSource } from "./packem_shared/jurisdiction.d-8oUUvrew.mjs";
1
+ import { D as DurableObjectJurisdiction, C as ContainerConfig, a as ContainerDefinition, b as ContainerImageSource, N as NormalizedContainerImage } from "./packem_shared/jurisdiction.d-DeQ3rtYK.mjs";
2
+ export type { B as BuildImageSource, c as ContainerInstanceType, d as ContainerReadinessCheck, e as ContainerRollout, f as CustomContainerInstanceType, g as NamedContainerInstanceType, R as RegistryImageSource } from "./packem_shared/jurisdiction.d-DeQ3rtYK.mjs";
3
3
  /**
4
4
  * Options for explicitly starting an instance (mirrors `@cloudflare/containers`).
5
5
  * @experimental
@@ -134,7 +134,7 @@ interface ContainerEgressControls {
134
134
  setDenied: (hosts: ReadonlyArray<string>) => Promise<void>;
135
135
  }
136
136
  /**
137
- * The per-definition accessor exposed as `ctx.containers.&lt;exportName>`.
137
+ * The per-definition accessor exposed as `ctx.containers.<exportName>`.
138
138
  * @experimental
139
139
  */
140
140
  interface ContainerAccessor {
@@ -271,7 +271,7 @@ declare const createContainerTestContext: (handlers: Record<string, ContainerTes
271
271
  * A local-path string whose basename starts with `Dockerfile` (so
272
272
  * `Dockerfile.dev` also counts) is used as-is with its directory as the build
273
273
  * context; any other path is treated as the build-context directory and the
274
- * Dockerfile is expected at `&lt;dir>/Dockerfile`.
274
+ * Dockerfile is expected at `<dir>/Dockerfile`.
275
275
  * @experimental
276
276
  */
277
277
  declare const normalizeContainerImage: (image: ContainerImageSource) => NormalizedContainerImage;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { D as DurableObjectJurisdiction, C as ContainerConfig, a as ContainerDefinition, b as ContainerImageSource, N as NormalizedContainerImage } from "./packem_shared/jurisdiction.d-8oUUvrew.js";
2
- export type { B as BuildImageSource, c as ContainerInstanceType, d as ContainerReadinessCheck, e as ContainerRollout, f as CustomContainerInstanceType, g as NamedContainerInstanceType, R as RegistryImageSource } from "./packem_shared/jurisdiction.d-8oUUvrew.js";
1
+ import { D as DurableObjectJurisdiction, C as ContainerConfig, a as ContainerDefinition, b as ContainerImageSource, N as NormalizedContainerImage } from "./packem_shared/jurisdiction.d-DeQ3rtYK.js";
2
+ export type { B as BuildImageSource, c as ContainerInstanceType, d as ContainerReadinessCheck, e as ContainerRollout, f as CustomContainerInstanceType, g as NamedContainerInstanceType, R as RegistryImageSource } from "./packem_shared/jurisdiction.d-DeQ3rtYK.js";
3
3
  /**
4
4
  * Options for explicitly starting an instance (mirrors `@cloudflare/containers`).
5
5
  * @experimental
@@ -134,7 +134,7 @@ interface ContainerEgressControls {
134
134
  setDenied: (hosts: ReadonlyArray<string>) => Promise<void>;
135
135
  }
136
136
  /**
137
- * The per-definition accessor exposed as `ctx.containers.&lt;exportName>`.
137
+ * The per-definition accessor exposed as `ctx.containers.<exportName>`.
138
138
  * @experimental
139
139
  */
140
140
  interface ContainerAccessor {
@@ -271,7 +271,7 @@ declare const createContainerTestContext: (handlers: Record<string, ContainerTes
271
271
  * A local-path string whose basename starts with `Dockerfile` (so
272
272
  * `Dockerfile.dev` also counts) is used as-is with its directory as the build
273
273
  * context; any other path is treated as the build-context directory and the
274
- * Dockerfile is expected at `&lt;dir>/Dockerfile`.
274
+ * Dockerfile is expected at `<dir>/Dockerfile`.
275
275
  * @experimental
276
276
  */
277
277
  declare const normalizeContainerImage: (image: ContainerImageSource) => NormalizedContainerImage;
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{createContainerContext as t,createContainerTestContext as a}from"./packem_shared/createContainerContext-Df9Ev-Fp.mjs";import{containerBindingName as o,containerBuildTag as r,containerClassName as C,defineContainer as m,isContainerDefinition as s,normalizeContainerImage as c,resolveContainerEnvVars as f}from"./packem_shared/containerBindingName-DP2NqQV-.mjs";export{o as containerBindingName,r as containerBuildTag,C as containerClassName,t as createContainerContext,a as createContainerTestContext,m as defineContainer,s as isContainerDefinition,c as normalizeContainerImage,f as resolveContainerEnvVars};
1
+ import{createContainerContext as t,createContainerTestContext as a}from"./packem_shared/createContainerContext-BXarJmWd.mjs";import{containerBindingName as o,containerBuildTag as r,containerClassName as C,defineContainer as m,isContainerDefinition as s,normalizeContainerImage as c,resolveContainerEnvVars as f}from"./packem_shared/containerBindingName-CP4v-2Cw.mjs";export{o as containerBindingName,r as containerBuildTag,C as containerClassName,t as createContainerContext,a as createContainerTestContext,m as defineContainer,s as isContainerDefinition,c as normalizeContainerImage,f as resolveContainerEnvVars};
package/dist/otel.d.mts CHANGED
@@ -105,7 +105,7 @@ interface ContainerTelemetryOptions {
105
105
  * under the Worker's trace instead of forming a fresh, disconnected trace.
106
106
  *
107
107
  * `@lunora/container` stamps this trace context as the **`traceparent` request
108
- * header** on every proxied fetch (`ctx.containers.&lt;name>.…`), so a container
108
+ * header** on every proxied fetch (`ctx.containers.<name>.…`), so a container
109
109
  * that serves many requests should read it per request and create a telemetry
110
110
  * instance scoped to that request — the trace context differs each call, so a
111
111
  * single process-lifetime instance can't carry it:
package/dist/otel.d.ts CHANGED
@@ -105,7 +105,7 @@ interface ContainerTelemetryOptions {
105
105
  * under the Worker's trace instead of forming a fresh, disconnected trace.
106
106
  *
107
107
  * `@lunora/container` stamps this trace context as the **`traceparent` request
108
- * header** on every proxied fetch (`ctx.containers.&lt;name>.…`), so a container
108
+ * header** on every proxied fetch (`ctx.containers.<name>.…`), so a container
109
109
  * that serves many requests should read it per request and create a telemetry
110
110
  * instance scoped to that request — the trace context differs each call, so a
111
111
  * single process-lifetime instance can't carry it:
package/dist/otel.mjs CHANGED
@@ -1 +1 @@
1
- const L={debug:5,error:17,fatal:21,info:9,log:9,trace:1,warn:13},b=e=>`${String(Math.round(e))}000000`,A=e=>{const t=new Uint8Array(e);crypto.getRandomValues(t);let r="";for(const o of t)r+=o.toString(16).padStart(2,"0");return r},y=/^[0-9a-f]+$/,C=e=>{if(e==null)return;const t=e.trim().toLowerCase().split("-"),[r,o,n,s]=t;if(!(t.length<4||r===void 0||r.length!==2||!y.test(r)||r==="ff"||r==="00"&&t.length!==4||o===void 0||n===void 0||s===void 0||s.length!==2||!y.test(s)||o.length!==32||n.length!==16||!y.test(o)||!y.test(n)||o==="00000000000000000000000000000000"||n==="0000000000000000"))return{parentSpanId:n,sampled:(Number.parseInt(s,16)&1)===1,traceId:o}},v=(e,t)=>typeof t=="boolean"?{key:e,value:{boolValue:t}}:typeof t=="number"?Number.isFinite(t)?Number.isSafeInteger(t)?{key:e,value:{intValue:String(t)}}:{key:e,value:{doubleValue:t}}:{key:e,value:{stringValue:String(t)}}:{key:e,value:{stringValue:t}},M=e=>e===void 0?[]:Object.entries(e).map(([t,r])=>v(t,r)),U=(e,t,r)=>{const o={},n=new Map,s=(a,i)=>{const E=a.toLowerCase(),f=n.get(E);f===void 0?(n.set(E,a),o[a]=i):o[f]=i};for(const[a,i]of Object.entries(e))s(a,i);for(const[a,i]of Object.entries(t??{}))s(a,i);return r!==void 0&&r.length>0&&s("authorization",`Bearer ${r}`),o},R=e=>Array.isArray(e)?e:[e],_=(e,t)=>{const r={"service.name":e};for(const[o,n]of Object.entries(t??{}))r[o]=n;return Object.entries(r).map(([o,n])=>v(o,n))},$=(e,t,r,o)=>({resourceSpans:[{resource:{attributes:_(r,o)},scopeSpans:[{scope:{name:t},spans:R(e)}]}]}),x=(e,t,r,o)=>({resourceLogs:[{resource:{attributes:_(r,o)},scopeLogs:[{logRecords:R(e),scope:{name:t}}]}]}),D=e=>{const t={},r=e("SERVICE_VERSION")??e("CF_VERSION_METADATA")??e("VERCEL_GIT_COMMIT_SHA")??e("GITHUB_SHA")??e("COMMIT_SHA");r!==void 0&&(t["service.version"]=r);const o=e("DEPLOYMENT_ENVIRONMENT")??e("ENVIRONMENT")??e("NODE_ENV");return o!==void 0&&(t["deployment.environment"]=o),t},P=(e,t)=>{const r={},o=e("HOSTNAME")??e("COMPUTERNAME");o!==void 0&&(r["host.name"]=o);const n=e("KUBERNETES_POD_NAME")??e("HOSTNAME");return n!==void 0&&e("KUBERNETES_SERVICE_HOST")!==void 0&&(r["k8s.pod.name"]=n),t!==void 0&&Number.isFinite(t)&&(r["process.pid"]=t),r},I=(...e)=>{const t={};for(const r of e)if(r!==void 0)for(const[o,n]of Object.entries(r))t[o]=n;return t},j=1e4,l=e=>process.env[e],k=()=>{const e=typeof process.pid!="number"?void 0:process.pid;return I(D(l),P(l,e))},H=e=>{if(e!==void 0)return e;if(typeof globalThis.fetch=="function")return globalThis.fetch},B=(e,t,r,o)=>{const n=M(e.attributes);e.error?.type!==void 0&&n.push(v("error.type",e.error.type));const s={attributes:n,endTimeUnixNano:b(e.endMs),kind:1,name:e.name,...r===void 0?{}:{parentSpanId:r.parentSpanId},spanId:A(8),startTimeUnixNano:b(e.startMs),status:e.error===void 0?{code:1}:{code:2,message:e.error.message},traceId:r?.traceId??A(16)};return e.error&&(s.events=[{attributes:[v("exception.type",e.error.type??"Error"),v("exception.message",e.error.message)],name:"exception",timeUnixNano:b(e.endMs)}]),$(s,"@lunora/container",t,o)},F=(e,t,r,o)=>{const n=e.level??"info",s={attributes:M(e.attributes),body:{stringValue:e.message},severityNumber:L[n],severityText:n.toUpperCase(),timeUnixNano:b(e.ts??r)};return x(s,"@lunora/container",t,o)},K=(e={})=>{const t=e.endpoint??l("LUNORA_OTLP_ENDPOINT"),r=t!==void 0&&t.length>0,o=e.token??l("LUNORA_OTLP_TOKEN"),n=e.serviceName??l("LUNORA_SERVICE_NAME")??"lunora-container",s=C(e.traceparent??l("LUNORA_TRACEPARENT")),a=e.timeoutMs??j,i=H(e.fetch),E=U({"content-type":"application/json"},e.headers,o),f=e.detectResources===!0?k():void 0,g={};e.serviceVersion!==void 0&&(g["service.version"]=e.serviceVersion),e.deploymentEnvironment!==void 0&&(g["deployment.environment"]=e.deploymentEnvironment),e.resourceAttributes!==void 0&&Object.assign(g,e.resourceAttributes);const T=I(f,g);let p=t??"";for(;p.endsWith("/");)p=p.slice(0,-1);const w=`${p}/v1/traces`,V=`${p}/v1/logs`,N=new Set,h=(c,S)=>{if(i===void 0){e.onError?.(new TypeError("createContainerTelemetry: no `fetch` available — pass `fetch` in options for this runtime."));return}const m=(async()=>{try{const d=await i(c,{body:JSON.stringify(S),headers:E,method:"POST",signal:AbortSignal.timeout(a)});d.ok||e.onError?.(new Error(`createContainerTelemetry: OTLP export to ${c} failed with status ${String(d.status)}.`));try{await d.body?.cancel()}catch{}}catch(d){e.onError?.(d)}})().finally(()=>{N.delete(m)});N.add(m)},O=c=>{r&&h(w,B(c,n,s,T))};return{emitLog:c=>{r&&h(V,F(c,n,Date.now(),T))},emitSpan:O,enabled:r,flush:async()=>{await Promise.allSettled(N)},trace:async(c,S,m)=>{const d=Date.now();try{const u=await S();return O({attributes:m,endMs:Date.now(),name:c,startMs:d}),u}catch(u){throw O({attributes:m,endMs:Date.now(),error:{message:u instanceof Error?u.message:String(u),type:u instanceof Error?u.name:void 0},name:c,startMs:d}),u}}}};export{K as createContainerTelemetry};
1
+ const L={debug:5,error:17,fatal:21,info:9,log:9,trace:1,warn:13},b=e=>`${String(Math.round(e))}000000`,A=e=>{const t=new Uint8Array(e);crypto.getRandomValues(t);let r="";for(const o of t)r+=o.toString(16).padStart(2,"0");return r},y=/^[0-9a-f]+$/,C=e=>{if(e==null)return;const t=e.trim().toLowerCase().split("-"),[r,o,n,s]=t;if(!(t.length<4||r===void 0||r.length!==2||!y.test(r)||r==="ff"||r==="00"&&t.length!==4||o===void 0||n===void 0||s===void 0||s.length!==2||!y.test(s)||o.length!==32||n.length!==16||!y.test(o)||!y.test(n)||o==="00000000000000000000000000000000"||n==="0000000000000000"))return{parentSpanId:n,sampled:(Number.parseInt(s,16)&1)===1,traceId:o}},v=(e,t)=>typeof t=="boolean"?{key:e,value:{boolValue:t}}:typeof t=="number"?Number.isFinite(t)?Number.isSafeInteger(t)?{key:e,value:{intValue:String(t)}}:{key:e,value:{doubleValue:t}}:{key:e,value:{stringValue:String(t)}}:{key:e,value:{stringValue:t}},M=e=>e===void 0?[]:Object.entries(e).map(([t,r])=>v(t,r)),U=(e,t,r)=>{const o={},n=new Map,s=(a,i)=>{const E=a.toLowerCase(),f=n.get(E);f===void 0?(n.set(E,a),o[a]=i):o[f]=i};for(const[a,i]of Object.entries(e))s(a,i);for(const[a,i]of Object.entries(t??{}))s(a,i);return r!==void 0&&r.length>0&&s("authorization",`Bearer ${r}`),o},R=e=>Array.isArray(e)?e:[e],_=(e,t)=>{const r={"service.name":e};for(const[o,n]of Object.entries(t??{}))r[o]=n;return Object.entries(r).map(([o,n])=>v(o,n))},$=(e,t,r,o)=>({resourceSpans:[{resource:{attributes:_(r,o)},scopeSpans:[{scope:{name:t},spans:R(e)}]}]}),x=(e,t,r,o)=>({resourceLogs:[{resource:{attributes:_(r,o)},scopeLogs:[{logRecords:R(e),scope:{name:t}}]}]}),D=e=>{const t={},r=e("SERVICE_VERSION")??e("CF_VERSION_METADATA")??e("VERCEL_GIT_COMMIT_SHA")??e("GITHUB_SHA")??e("COMMIT_SHA");r!==void 0&&(t["service.version"]=r);const o=e("DEPLOYMENT_ENVIRONMENT")??e("ENVIRONMENT")??e("NODE_ENV");return o!==void 0&&(t["deployment.environment"]=o),t},P=(e,t)=>{const r={},o=e("HOSTNAME")??e("COMPUTERNAME");o!==void 0&&(r["host.name"]=o);const n=e("KUBERNETES_POD_NAME")??e("HOSTNAME");return n!==void 0&&e("KUBERNETES_SERVICE_HOST")!==void 0&&(r["k8s.pod.name"]=n),t!==void 0&&Number.isFinite(t)&&(r["process.pid"]=t),r},I=(...e)=>{const t={};for(const r of e)if(r!==void 0)for(const[o,n]of Object.entries(r))t[o]=n;return t},j=1e4,l=e=>process.env[e],k=()=>{const e=typeof process.pid!="number"?void 0:process.pid;return I(D(l),P(l,e))},H=e=>e??(typeof globalThis.fetch=="function"?globalThis.fetch:void 0),B=(e,t,r,o)=>{const n=M(e.attributes);e.error?.type!==void 0&&n.push(v("error.type",e.error.type));const s={attributes:n,endTimeUnixNano:b(e.endMs),kind:1,name:e.name,...r===void 0?{}:{parentSpanId:r.parentSpanId},spanId:A(8),startTimeUnixNano:b(e.startMs),status:e.error===void 0?{code:1}:{code:2,message:e.error.message},traceId:r?.traceId??A(16)};return e.error&&(s.events=[{attributes:[v("exception.type",e.error.type??"Error"),v("exception.message",e.error.message)],name:"exception",timeUnixNano:b(e.endMs)}]),$(s,"@lunora/container",t,o)},F=(e,t,r,o)=>{const n=e.level??"info",s={attributes:M(e.attributes),body:{stringValue:e.message},severityNumber:L[n],severityText:n.toUpperCase(),timeUnixNano:b(e.ts??r)};return x(s,"@lunora/container",t,o)},K=(e={})=>{const t=e.endpoint??l("LUNORA_OTLP_ENDPOINT"),r=t!==void 0&&t.length>0,o=e.token??l("LUNORA_OTLP_TOKEN"),n=e.serviceName??l("LUNORA_SERVICE_NAME")??"lunora-container",s=C(e.traceparent??l("LUNORA_TRACEPARENT")),a=e.timeoutMs??j,i=H(e.fetch),E=U({"content-type":"application/json"},e.headers,o),f=e.detectResources===!0?k():void 0,g={};e.serviceVersion!==void 0&&(g["service.version"]=e.serviceVersion),e.deploymentEnvironment!==void 0&&(g["deployment.environment"]=e.deploymentEnvironment),e.resourceAttributes!==void 0&&Object.assign(g,e.resourceAttributes);const T=I(f,g);let p=t??"";for(;p.endsWith("/");)p=p.slice(0,-1);const w=`${p}/v1/traces`,V=`${p}/v1/logs`,N=new Set,h=(c,S)=>{if(i===void 0){e.onError?.(new TypeError("createContainerTelemetry: no `fetch` available — pass `fetch` in options for this runtime."));return}const m=(async()=>{try{const d=await i(c,{body:JSON.stringify(S),headers:E,method:"POST",signal:AbortSignal.timeout(a)});d.ok||e.onError?.(new Error(`createContainerTelemetry: OTLP export to ${c} failed with status ${String(d.status)}.`));try{await d.body?.cancel()}catch{}}catch(d){e.onError?.(d)}})().finally(()=>{N.delete(m)});N.add(m)},O=c=>{r&&h(w,B(c,n,s,T))};return{emitLog:c=>{r&&h(V,F(c,n,Date.now(),T))},emitSpan:O,enabled:r,flush:async()=>{await Promise.allSettled(N)},trace:async(c,S,m)=>{const d=Date.now();try{const u=await S();return O({attributes:m,endMs:Date.now(),name:c,startMs:d}),u}catch(u){throw O({attributes:m,endMs:Date.now(),error:{message:u instanceof Error?u.message:String(u),type:u instanceof Error?u.name:void 0},name:c,startMs:d}),u}}}};export{K as createContainerTelemetry};
@@ -0,0 +1 @@
1
+ import{LunoraError as c}from"@lunora/errors";const d=new Set(["basic","lite","standard-1","standard-2","standard-3","standard-4"]),o=/^[A-Z_]\w*$/i,l=/^\d+[smh]$/,u={h:3600,m:60,s:1},E=e=>{if(typeof e=="number")return Math.floor(e);if(l.exec(e)===null)throw new TypeError(`Invalid duration "${e}" — expected a number of seconds or "<n>[smh]"`);return Number(e.slice(0,-1))*(u[e.slice(-1)]??1)},m=e=>{const t=e.endsWith("/")?e.slice(0,-1):e,n=t.lastIndexOf("/");return n===-1?t:t.slice(n+1)},y=e=>{const t=e.lastIndexOf("/");return t===-1?".":e.slice(0,t)||"/"},$=e=>{if(typeof e!="string")return"build"in e?{buildDir:e.build.endsWith("/")?e.build.slice(0,-1):e.build,kind:"build"}:{kind:"registry",reference:e.registry};if(m(e).startsWith("Dockerfile"))return{buildContext:y(e),dockerfilePath:e,kind:"dockerfile"};const t=e.endsWith("/")?e.slice(0,-1):e;return{buildContext:t,dockerfilePath:`${t}/Dockerfile`,kind:"dockerfile"}},k=e=>`${e.charAt(0).toUpperCase()}${e.slice(1)}Container`,I=e=>`CONTAINER_${e.replaceAll(/(?<=[a-z0-9])(?=[A-Z])/g,"_").toUpperCase()}`,S=e=>`lunora-${e.replaceAll(/(?<=[a-z0-9])(?=[A-Z])/g,"-").toLowerCase()}:build`,g=e=>{if(typeof e=="string"){if(e.length===0)throw new TypeError("defineContainer: `image` must be a non-empty path or a { registry } reference");if(e.includes(":"))throw new TypeError(`defineContainer: \`image\` string "${e}" looks like a registry reference — pass it as { registry: "${e}" } instead. Plain strings are local Dockerfile paths.`);return}if("build"in e){if(typeof e.build!="string"||e.build.length===0)throw new TypeError("defineContainer: `image.build` must be a non-empty source directory for Railpack to build");return}if(typeof e.registry!="string"||e.registry.length===0)throw new TypeError("defineContainer: `image.registry` must be a non-empty fully-qualified image reference")},h=(e,t,n)=>{for(const[r,i]of Object.entries(e.secretsStore??{})){if(!o.test(r))throw new TypeError(`defineContainer: secretsStore env name "${r}" is not a valid environment variable name`);if(typeof i!="string"||i.trim().length===0)throw new TypeError(`defineContainer: \`secretsStore["${r}"]\` must be a non-empty Secrets Store binding name`);if(t.has(r)||n.has(r))throw new TypeError(`defineContainer: "${r}" is declared in both \`secretsStore\` and \`env\`/\`secrets\` — pick one source for the value`)}},w=e=>{for(const r of Object.keys(e.env??{}))if(!o.test(r))throw new TypeError(`defineContainer: env variable name "${r}" is not a valid environment variable name`);for(const r of Object.keys(e.buildArgs??{}))if(!o.test(r))throw new TypeError(`defineContainer: buildArg name "${r}" is not a valid environment variable name`);const t=new Set(Object.keys(e.env??{})),n=new Set(e.secrets);for(const r of e.secrets??[]){if(!o.test(r))throw new TypeError(`defineContainer: secret name "${r}" is not a valid environment variable name`);if(t.has(r))throw new TypeError(`defineContainer: "${r}" is declared in both \`env\` and \`secrets\` — a secret would silently overwrite the static env value; pick one`)}h(e,t,n)},s=(e,t)=>{if(!Number.isInteger(e)||e<1||e>65535)throw new TypeError(`defineContainer: \`${t}\` must be an integer in 1–65535 (got ${String(e)})`)},b=e=>{for(const t of e.readyOn??[]){if(typeof t.path!="string"||t.path.trim().length===0)throw new TypeError("defineContainer: `readyOn[].path` must be a non-empty HTTP path string");if(t.path!==t.path.trim())throw new TypeError("defineContainer: `readyOn[].path` must not have leading or trailing whitespace");if(t.port!==void 0&&s(t.port,"readyOn[].port"),t.status!==void 0&&(!Number.isInteger(t.status)||t.status<100||t.status>599))throw new TypeError(`defineContainer: \`readyOn[].status\` must be an HTTP status code in 100–599 (got ${String(t.status)})`)}},f=(e,t)=>{if(e!==void 0){if(typeof e=="string"){if(!l.test(e))throw new TypeError(`defineContainer: \`${t}\` string "${e}" must be a number of seconds followed by a unit, e.g. "30s", "5m", or "1h"`)}else if(!Number.isInteger(e)||e<1)throw new TypeError(`defineContainer: \`${t}\` must be a positive integer number of seconds or a duration string like "5m" (got ${String(e)})`)}},v=e=>{for(const t of["allowedHosts","deniedHosts"])if(e[t]?.some(n=>typeof n!="string"||n.trim().length===0))throw new TypeError(`defineContainer: \`${t}\` must be an array of non-empty hostname patterns`);if(e.interceptHttps!==void 0&&typeof e.interceptHttps!="boolean")throw new TypeError("defineContainer: `interceptHttps` must be a boolean, or omitted")},C=e=>{if(e.requiredPorts!==void 0){if(e.requiredPorts.length===0)throw new TypeError("defineContainer: `requiredPorts` must be a non-empty array of ports, or omitted");for(const t of e.requiredPorts)s(t,"requiredPorts[]")}if(e.entrypoint!==void 0&&(e.entrypoint.length===0||e.entrypoint.some(t=>typeof t!="string"||t.trim().length===0)))throw new TypeError("defineContainer: `entrypoint` must be a non-empty array of non-empty strings, or omitted");if(v(e),e.pingEndpoint!==void 0&&(typeof e.pingEndpoint!="string"||e.pingEndpoint.trim().length===0))throw new TypeError("defineContainer: `pingEndpoint` must be a non-empty path string");for(const[t,n]of Object.entries(e.labels??{}))if(t.trim().length===0||typeof n!="string")throw new TypeError("defineContainer: `labels` must be a record of non-empty keys to string values");b(e)},P=e=>{g(e.image),e.defaultPort!==void 0&&s(e.defaultPort,"defaultPort");const t=e.rollout?.stepPercentage;if(t!==void 0&&(!Number.isInteger(t)||t<1||t>100))throw new TypeError(`defineContainer: \`rollout.stepPercentage\` must be an integer in 1–100 (got ${String(t)})`);if(e.maxInstances!==void 0&&(!Number.isInteger(e.maxInstances)||e.maxInstances<1))throw new TypeError(`defineContainer: \`maxInstances\` must be a positive integer (got ${String(e.maxInstances)})`);if(typeof e.instanceType=="string"&&!d.has(e.instanceType))throw new TypeError(`defineContainer: unknown \`instanceType\` "${e.instanceType}" — use one of ${[...d].join(", ")}, or a custom { vcpu, memoryMib, diskMb } object`);return f(e.sleepAfter,"sleepAfter"),f(e.hardTimeout,"hardTimeout"),w(e),C(e),{...e,isLunoraContainer:!0}},A=e=>typeof e=="object"&&e!==null&&e.isLunoraContainer===!0,N=(e,t,n)=>{const r={...e.env};for(const i of e.secrets??[]){const a=t[i];if(typeof a!="string"){const p=n===void 0?"container":`container "${n}"`;throw new c("INTERNAL",`${p}: declared secret "${i}" is not set on the Worker environment. Add it to .dev.vars for local dev and run \`wrangler secret put ${i}\` for production.`)}r[i]=a}return r};export{I as containerBindingName,S as containerBuildTag,k as containerClassName,P as defineContainer,A as isContainerDefinition,$ as normalizeContainerImage,E as parseDurationSeconds,N as resolveContainerEnvVars};
@@ -1 +1 @@
1
- import{LunoraError as D}from"@lunora/errors";import{containerBindingName as $}from"./containerBindingName-DP2NqQV-.mjs";import{e as E}from"./jurisdiction-BKRNOTip.mjs";const v=3,x=3e4,k=3,S=500,T="cf-container-target-port",b=(t,e,o,n)=>{const r=typeof t=="string"&&t.startsWith("/")?new Request(`http://container${t}`,e):new Request(t,e);return o!==void 0&&r.headers.set(T,String(o)),n!==void 0&&r.headers.set("traceparent",n),r},P=async t=>{t<=0||await new Promise(e=>{setTimeout(e,t)})},C=/no container instance|not listening|try again later|rate.?limit|provision/i,F="no Container instance available",R="Failed to start container:",j=1024,B=async t=>{const e=t.clone().body;if(e===null)return"";const o=e.getReader(),n=new TextDecoder;let r="";try{for(;r.length<j;){const{done:s,value:a}=await o.read();if(s)break;r+=n.decode(a,{stream:!0})}}finally{await o.cancel()}return r},I=t=>t instanceof Error&&C.test(t.message),O=async t=>{if(t.status===429)return!0;if(t.status!==500&&t.status!==503)return!1;try{const e=await B(t);return e.includes(F)||e.startsWith(R)}catch{return!1}},h=(t,e={},o,n)=>{const r=Math.max(1,e.attempts??k),s=e.backoffMs??S,a=e.maxBackoffMs??x;return{fetch:async(d,g)=>{const u=typeof d=="string"?r:1;let l;for(let m=0;m<u;m+=1){const f=m===u-1;m>0&&await P(Math.min(s*2**(m-1),a));try{const c=await t(b(d,g,o,n));if(f||!await O(c))return c}catch(c){if(l=c,f||!I(c))throw c}}throw l instanceof Error?l:new Error("ctx.containers: cold-start retry exhausted")},port:d=>h(t,e,d,n)}},w=(t,e,o,n)=>h(async r=>t.get(t.idFromName(e)).fetch(r),o,void 0,n),i=async(t,e,o,n)=>{const r=t[e];if(typeof r!="function")throw new TypeError(`ctx.containers: the "${o}" container DO does not expose ${e}() — is @lunora/container/do up to date?`);return r(n)},q=(t,e)=>({allow:async o=>i(t(),"allowHost",e,o),deny:async o=>i(t(),"denyHost",e,o),removeAllowed:async o=>i(t(),"removeAllowedHost",e,o),removeDenied:async o=>i(t(),"removeDeniedHost",e,o),setAllowed:async o=>i(t(),"setAllowedHosts",e,[...o]),setDenied:async o=>i(t(),"setDeniedHosts",e,[...o])}),H=(t,e,o,n,r)=>{const s=()=>t.get(t.idFromName(o));return{...h(async a=>s().fetch(a),n,void 0,r),destroy:async()=>i(s(),"destroy",e.binding),egress:q(s,e.binding),getState:async()=>i(s(),"getState",e.binding),renewActivityTimeout:async()=>i(s(),"renewActivityTimeout",e.binding),start:async a=>i(s(),"start",e.binding,a),stop:async a=>i(s(),"stop",e.binding,a)}},N=t=>`pool-${String(Math.floor(Math.random()*t))}`,L=t=>t.status>=500,A=(t,e,o={},n,r)=>{const s=o.size??e.maxInstances??v,a=Math.max(1,o.attempts??3),d=o.backoffMs??100,g=o.maxBackoffMs??x,u=o.retryOn??L;return{fetch:async(l,m)=>{const f=typeof l=="string"?a:1;let c;for(let y=0;y<f;y+=1){y>0&&await P(Math.min(d*2**(y-1),g));const M=b(l,m,n,r);try{const p=await t.get(t.idFromName(N(s))).fetch(M);if(y===f-1||!u(p))return p}catch(p){c=p}}throw c instanceof Error?c:new Error(`ctx.containers.${e.exportName}.pool(): all ${String(f)} attempts failed`)},port:l=>A(t,e,o,l,r)}},z=(t,e,o)=>({any:(n,r)=>w(t,N(n??e.maxInstances??v),r,o),get:(n,r)=>H(t,e,n,r,o),pool:n=>A(t,e,n,void 0,o)}),W=t=>{const e=()=>{throw new D("INTERNAL",`ctx.containers.${t.exportName}: no "${t.binding}" Durable Object binding found. Run \`lunora dev\` (or \`lunora deploy\`) to reconcile wrangler.jsonc, and make sure the worker entry re-exports the generated container classes.`)};return{any:e,get:e,pool:e}},G=(t,e,o,n)=>{const r={};for(const s of e){const a=t[s.binding];r[s.exportName]=a&&typeof a.idFromName=="function"&&typeof a.get=="function"?z(E(a,o),s,n):W(s)}return r},J=t=>{const e=o=>({allowHost:()=>Promise.resolve(),denyHost:()=>Promise.resolve(),destroy:()=>Promise.resolve(),fetch:n=>Promise.resolve(t(n,{name:o})),getState:()=>Promise.resolve({lastChange:0}),removeAllowedHost:()=>Promise.resolve(),removeDeniedHost:()=>Promise.resolve(),renewActivityTimeout:()=>Promise.resolve(),setAllowedHosts:()=>Promise.resolve(),setDeniedHosts:()=>Promise.resolve(),start:()=>Promise.resolve(),stop:()=>Promise.resolve()});return{get:o=>e(String(o)),idFromName:o=>o}},K=t=>{const e={};for(const[o,n]of Object.entries(t)){const r=J(n),s={binding:$(o)};e[o]={any:()=>w(r,"pool-0",{attempts:1}),get:a=>H(r,s,a,{attempts:1}),pool:()=>w(r,"pool-0",{attempts:1})}}return e};export{G as createContainerContext,K as createContainerTestContext};
1
+ import{LunoraError as D}from"@lunora/errors";import{containerBindingName as E}from"./containerBindingName-CP4v-2Cw.mjs";import{e as $}from"./jurisdiction-BKRNOTip.mjs";const v=3,x=3e4,k=3,S=500,T="cf-container-target-port",b=(t,e,o,n)=>{const r=typeof t=="string"&&t.startsWith("/")?new Request(`http://container${t}`,e):new Request(t,e);return o!==void 0&&r.headers.set(T,String(o)),n!==void 0&&r.headers.set("traceparent",n),r},P=async t=>{t<=0||await new Promise(e=>{setTimeout(e,t)})},C=/no container instance|not listening|try again later|rate.?limit|provision/i,F="no Container instance available",R="Failed to start container:",j=1024,B=async t=>{const e=t.clone().body;if(e===null)return"";const o=e.getReader(),n=new TextDecoder;let r="";try{for(;r.length<j;){const{done:s,value:a}=await o.read();if(s)break;r+=n.decode(a,{stream:!0})}}finally{await o.cancel()}return r},I=t=>t instanceof Error&&C.test(t.message),O=async t=>{if(t.status===429)return!0;if(t.status!==500&&t.status!==503)return!1;try{const e=await B(t);return e.includes(F)||e.startsWith(R)}catch{return!1}},h=(t,e={},o,n)=>{const r=Math.max(1,e.attempts??k),s=e.backoffMs??S,a=e.maxBackoffMs??x;return{fetch:async(d,g)=>{const u=typeof d=="string"?r:1;let l;for(let m=0;m<u;m+=1){const f=m===u-1;m>0&&await P(Math.min(s*2**(m-1),a));try{const c=await t(b(d,g,o,n));if(f||!await O(c))return c}catch(c){if(l=c,f||!I(c))throw c}}throw l instanceof Error?l:new Error("ctx.containers: cold-start retry exhausted")},port:d=>h(t,e,d,n)}},w=(t,e,o,n)=>h(async r=>t.get(t.idFromName(e)).fetch(r),o,void 0,n),i=async(t,e,o,n)=>{const r=t[e];if(typeof r!="function")throw new TypeError(`ctx.containers: the "${o}" container DO does not expose ${e}() — is @lunora/container/do up to date?`);return r(n)},q=(t,e)=>({allow:async o=>i(t(),"allowHost",e,o),deny:async o=>i(t(),"denyHost",e,o),removeAllowed:async o=>i(t(),"removeAllowedHost",e,o),removeDenied:async o=>i(t(),"removeDeniedHost",e,o),setAllowed:async o=>i(t(),"setAllowedHosts",e,[...o]),setDenied:async o=>i(t(),"setDeniedHosts",e,[...o])}),H=(t,e,o,n,r)=>{const s=()=>t.get(t.idFromName(o));return{...h(async a=>s().fetch(a),n,void 0,r),destroy:async()=>i(s(),"destroy",e.binding),egress:q(s,e.binding),getState:async()=>i(s(),"getState",e.binding),renewActivityTimeout:async()=>i(s(),"renewActivityTimeout",e.binding),start:async a=>i(s(),"start",e.binding,a),stop:async a=>i(s(),"stop",e.binding,a)}},N=t=>`pool-${String(Math.floor(Math.random()*t))}`,L=t=>t.status>=500,A=(t,e,o={},n,r)=>{const s=o.size??e.maxInstances??v,a=Math.max(1,o.attempts??3),d=o.backoffMs??100,g=o.maxBackoffMs??x,u=o.retryOn??L;return{fetch:async(l,m)=>{const f=typeof l=="string"?a:1;let c;for(let y=0;y<f;y+=1){y>0&&await P(Math.min(d*2**(y-1),g));const M=b(l,m,n,r);try{const p=await t.get(t.idFromName(N(s))).fetch(M);if(y===f-1||!u(p))return p}catch(p){c=p}}throw c instanceof Error?c:new Error(`ctx.containers.${e.exportName}.pool(): all ${String(f)} attempts failed`)},port:l=>A(t,e,o,l,r)}},z=(t,e,o)=>({any:(n,r)=>w(t,N(n??e.maxInstances??v),r,o),get:(n,r)=>H(t,e,n,r,o),pool:n=>A(t,e,n,void 0,o)}),W=t=>{const e=()=>{throw new D("INTERNAL",`ctx.containers.${t.exportName}: no "${t.binding}" Durable Object binding found. Run \`lunora dev\` (or \`lunora deploy\`) to reconcile wrangler.jsonc, and make sure the worker entry re-exports the generated container classes.`)};return{any:e,get:e,pool:e}},G=(t,e,o,n)=>{const r={};for(const s of e){const a=t[s.binding];r[s.exportName]=a&&typeof a.idFromName=="function"&&typeof a.get=="function"?z($(a,o),s,n):W(s)}return r},J=t=>{const e=o=>({allowHost:()=>Promise.resolve(),denyHost:()=>Promise.resolve(),destroy:()=>Promise.resolve(),fetch:n=>Promise.resolve(t(n,{name:o})),getState:()=>Promise.resolve({lastChange:0}),removeAllowedHost:()=>Promise.resolve(),removeDeniedHost:()=>Promise.resolve(),renewActivityTimeout:()=>Promise.resolve(),setAllowedHosts:()=>Promise.resolve(),setDeniedHosts:()=>Promise.resolve(),start:()=>Promise.resolve(),stop:()=>Promise.resolve()});return{get:o=>e(String(o)),idFromName:o=>o}},K=t=>{const e={};for(const[o,n]of Object.entries(t)){const r=J(n),s={binding:E(o)};e[o]={any:()=>w(r,"pool-0",{attempts:1}),get:a=>H(r,s,a,{attempts:1}),pool:()=>w(r,"pool-0",{attempts:1})}}return e};export{G as createContainerContext,K as createContainerTestContext};
@@ -61,7 +61,7 @@ interface BuildImageSource {
61
61
  /**
62
62
  * Where the container image comes from. A `string` is a **local path** —
63
63
  * either a directory containing a `Dockerfile` (normalized to
64
- * `&lt;dir>/Dockerfile` with the directory as the build context) or a path to
64
+ * `<dir>/Dockerfile` with the directory as the build context) or a path to
65
65
  * the Dockerfile itself — while `{ registry }` is a pre-built image reference.
66
66
  * @experimental
67
67
  */
@@ -192,7 +192,7 @@ interface ContainerConfig {
192
192
  pingEndpoint?: string;
193
193
  /**
194
194
  * Application-level readiness probes that gate request proxying: a
195
- * `ctx.containers.&lt;name>` fetch waits until every probe responds with its
195
+ * `ctx.containers.<name>` fetch waits until every probe responds with its
196
196
  * expected status before the request reaches the container — on top of the
197
197
  * platform's port/`pingEndpoint` health wait. All probes run in parallel.
198
198
  * Use these for readiness an open-port check can't see (migrations applied,
@@ -61,7 +61,7 @@ interface BuildImageSource {
61
61
  /**
62
62
  * Where the container image comes from. A `string` is a **local path** —
63
63
  * either a directory containing a `Dockerfile` (normalized to
64
- * `&lt;dir>/Dockerfile` with the directory as the build context) or a path to
64
+ * `<dir>/Dockerfile` with the directory as the build context) or a path to
65
65
  * the Dockerfile itself — while `{ registry }` is a pre-built image reference.
66
66
  * @experimental
67
67
  */
@@ -192,7 +192,7 @@ interface ContainerConfig {
192
192
  pingEndpoint?: string;
193
193
  /**
194
194
  * Application-level readiness probes that gate request proxying: a
195
- * `ctx.containers.&lt;name>` fetch waits until every probe responds with its
195
+ * `ctx.containers.<name>` fetch waits until every probe responds with its
196
196
  * expected status before the request reaches the container — on top of the
197
197
  * platform's port/`pingEndpoint` health wait. All probes run in parallel.
198
198
  * Use these for readiness an open-port check can't see (migrations applied,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/container",
3
- "version": "1.0.0-alpha.22",
3
+ "version": "1.0.0-alpha.24",
4
4
  "description": "Cloudflare Containers for Lunora: defineContainer, generated Container DO classes, and the ctx.containers action surface",
5
5
  "keywords": [
6
6
  "cloudflare",
@@ -56,7 +56,7 @@
56
56
  "access": "public"
57
57
  },
58
58
  "dependencies": {
59
- "@lunora/errors": "1.0.0-alpha.13"
59
+ "@lunora/errors": "1.0.0-alpha.15"
60
60
  },
61
61
  "engines": {
62
62
  "node": "^22.15.0 || >=24.11.0"
@@ -1 +0,0 @@
1
- import{LunoraError as p}from"@lunora/errors";const d=new Set(["basic","lite","standard-1","standard-2","standard-3","standard-4"]),o=/^[A-Z_]\w*$/i,s=/^\d+[smh]$/,u={h:3600,m:60,s:1},E=e=>{if(typeof e=="number")return Math.floor(e);if(s.exec(e)===null)throw new TypeError(`Invalid duration "${e}" — expected a number of seconds or "<n>[smh]"`);return Number(e.slice(0,-1))*(u[e.slice(-1)]??1)},c=e=>{const r=e.endsWith("/")?e.slice(0,-1):e,n=r.lastIndexOf("/");return n===-1?r:r.slice(n+1)},m=e=>{const r=e.lastIndexOf("/");return r===-1?".":e.slice(0,r)||"/"},$=e=>{if(typeof e!="string")return"build"in e?{buildDir:e.build.endsWith("/")?e.build.slice(0,-1):e.build,kind:"build"}:{kind:"registry",reference:e.registry};if(c(e).startsWith("Dockerfile"))return{buildContext:m(e),dockerfilePath:e,kind:"dockerfile"};const r=e.endsWith("/")?e.slice(0,-1):e;return{buildContext:r,dockerfilePath:`${r}/Dockerfile`,kind:"dockerfile"}},k=e=>`${e.charAt(0).toUpperCase()}${e.slice(1)}Container`,A=e=>`CONTAINER_${e.replaceAll(/(?<=[a-z0-9])(?=[A-Z])/g,"_").toUpperCase()}`,I=e=>`lunora-${e.replaceAll(/(?<=[a-z0-9])(?=[A-Z])/g,"-").toLowerCase()}:build`,g=e=>{if(typeof e=="string"){if(e.length===0)throw new TypeError("defineContainer: `image` must be a non-empty path or a { registry } reference");if(e.includes(":"))throw new TypeError(`defineContainer: \`image\` string "${e}" looks like a registry reference — pass it as { registry: "${e}" } instead. Plain strings are local Dockerfile paths.`);return}if("build"in e){if(typeof e.build!="string"||e.build.length===0)throw new TypeError("defineContainer: `image.build` must be a non-empty source directory for Railpack to build");return}if(typeof e.registry!="string"||e.registry.length===0)throw new TypeError("defineContainer: `image.registry` must be a non-empty fully-qualified image reference")},y=(e,r,n)=>{for(const[t,i]of Object.entries(e.secretsStore??{})){if(!o.test(t))throw new TypeError(`defineContainer: secretsStore env name "${t}" is not a valid environment variable name`);if(typeof i!="string"||i.trim().length===0)throw new TypeError(`defineContainer: \`secretsStore["${t}"]\` must be a non-empty Secrets Store binding name`);if(r.has(t)||n.has(t))throw new TypeError(`defineContainer: "${t}" is declared in both \`secretsStore\` and \`env\`/\`secrets\` — pick one source for the value`)}},h=e=>{for(const t of Object.keys(e.env??{}))if(!o.test(t))throw new TypeError(`defineContainer: env variable name "${t}" is not a valid environment variable name`);for(const t of Object.keys(e.buildArgs??{}))if(!o.test(t))throw new TypeError(`defineContainer: buildArg name "${t}" is not a valid environment variable name`);const r=new Set(Object.keys(e.env??{})),n=new Set(e.secrets);for(const t of e.secrets??[]){if(!o.test(t))throw new TypeError(`defineContainer: secret name "${t}" is not a valid environment variable name`);if(r.has(t))throw new TypeError(`defineContainer: "${t}" is declared in both \`env\` and \`secrets\` — a secret would silently overwrite the static env value; pick one`)}y(e,r,n)},a=(e,r)=>{if(!Number.isInteger(e)||e<1||e>65535)throw new TypeError(`defineContainer: \`${r}\` must be an integer in 1–65535 (got ${String(e)})`)},w=e=>{for(const r of e.readyOn??[]){if(typeof r.path!="string"||r.path.trim().length===0)throw new TypeError("defineContainer: `readyOn[].path` must be a non-empty HTTP path string");if(r.path!==r.path.trim())throw new TypeError("defineContainer: `readyOn[].path` must not have leading or trailing whitespace");if(r.port!==void 0&&a(r.port,"readyOn[].port"),r.status!==void 0&&(!Number.isInteger(r.status)||r.status<100||r.status>599))throw new TypeError(`defineContainer: \`readyOn[].status\` must be an HTTP status code in 100–599 (got ${String(r.status)})`)}},b=e=>{if(e!==void 0){if(typeof e=="string"){if(!s.test(e))throw new TypeError(`defineContainer: \`hardTimeout\` string "${e}" must be a number of seconds followed by a unit, e.g. "30s", "5m", or "1h"`)}else if(!Number.isInteger(e)||e<1)throw new TypeError(`defineContainer: \`hardTimeout\` must be a positive integer number of seconds or a duration string like "5m" (got ${String(e)})`)}},v=e=>{for(const r of["allowedHosts","deniedHosts"])if(e[r]?.some(n=>typeof n!="string"||n.trim().length===0))throw new TypeError(`defineContainer: \`${r}\` must be an array of non-empty hostname patterns`);if(e.interceptHttps!==void 0&&typeof e.interceptHttps!="boolean")throw new TypeError("defineContainer: `interceptHttps` must be a boolean, or omitted")},T=e=>{if(e.requiredPorts!==void 0){if(e.requiredPorts.length===0)throw new TypeError("defineContainer: `requiredPorts` must be a non-empty array of ports, or omitted");for(const r of e.requiredPorts)a(r,"requiredPorts[]")}if(e.entrypoint!==void 0&&(e.entrypoint.length===0||e.entrypoint.some(r=>typeof r!="string"||r.trim().length===0)))throw new TypeError("defineContainer: `entrypoint` must be a non-empty array of non-empty strings, or omitted");if(v(e),e.pingEndpoint!==void 0&&(typeof e.pingEndpoint!="string"||e.pingEndpoint.trim().length===0))throw new TypeError("defineContainer: `pingEndpoint` must be a non-empty path string");for(const[r,n]of Object.entries(e.labels??{}))if(r.trim().length===0||typeof n!="string")throw new TypeError("defineContainer: `labels` must be a record of non-empty keys to string values");w(e)},S=e=>{g(e.image),e.defaultPort!==void 0&&a(e.defaultPort,"defaultPort");const r=e.rollout?.stepPercentage;if(r!==void 0&&(!Number.isInteger(r)||r<1||r>100))throw new TypeError(`defineContainer: \`rollout.stepPercentage\` must be an integer in 1–100 (got ${String(r)})`);if(e.maxInstances!==void 0&&(!Number.isInteger(e.maxInstances)||e.maxInstances<1))throw new TypeError(`defineContainer: \`maxInstances\` must be a positive integer (got ${String(e.maxInstances)})`);if(typeof e.instanceType=="string"&&!d.has(e.instanceType))throw new TypeError(`defineContainer: unknown \`instanceType\` "${e.instanceType}" — use one of ${[...d].join(", ")}, or a custom { vcpu, memoryMib, diskMb } object`);if(typeof e.sleepAfter=="string"){if(!s.test(e.sleepAfter))throw new TypeError(`defineContainer: \`sleepAfter\` string "${e.sleepAfter}" must be a number of seconds followed by a unit, e.g. "30s", "5m", or "1h"`)}else if(e.sleepAfter!==void 0&&(!Number.isInteger(e.sleepAfter)||e.sleepAfter<1))throw new TypeError(`defineContainer: \`sleepAfter\` must be a positive integer number of seconds or a duration string like "5m" (got ${String(e.sleepAfter)})`);return b(e.hardTimeout),h(e),T(e),{...e,isLunoraContainer:!0}},P=e=>typeof e=="object"&&e!==null&&e.isLunoraContainer===!0,N=(e,r,n)=>{const t={...e.env};for(const i of e.secrets??[]){const f=r[i];if(typeof f!="string"){const l=n===void 0?"container":`container "${n}"`;throw new p("INTERNAL",`${l}: declared secret "${i}" is not set on the Worker environment. Add it to .dev.vars for local dev and run \`wrangler secret put ${i}\` for production.`)}t[i]=f}return t};export{A as containerBindingName,I as containerBuildTag,k as containerClassName,S as defineContainer,P as isContainerDefinition,$ as normalizeContainerImage,E as parseDurationSeconds,N as resolveContainerEnvVars};