@lunora/container 1.0.0-alpha.53 → 1.0.0-alpha.55

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/index.d.mts CHANGED
@@ -314,11 +314,18 @@ interface ContainerBindingSpec {
314
314
  * A missing binding doesn't throw here — only when the handle is actually used —
315
315
  * so one unprovisioned container never breaks unrelated functions.
316
316
  *
317
- * `traceparent` (the inbound RPC's W3C trace context, forwarded by the runtime
318
- * and read off the request by the DO) is stamped onto every outbound container
319
- * `fetch`, so the container's own spans stitch under the Worker's trace.
320
- */
321
- declare const createContainerContext: (env: Record<string, unknown>, specs: ReadonlyArray<ContainerBindingSpec>, jurisdiction?: DurableObjectJurisdiction, traceparent?: string) => Record<string, ContainerAccessor>;
317
+ * The last two arguments carry the dispatch's telemetry verdict onto every
318
+ * outbound container `fetch`, both read off the inbound request by the DO:
319
+ *
320
+ * - `traceparent` — the W3C trace context, so the container's own spans stitch
321
+ * under the Worker's trace rather than starting a disconnected one.
322
+ * - `sampleErrors` — the tail-bias toggle, so the container exports what THIS
323
+ * dispatch decided instead of falling back to its own environment and agreeing
324
+ * with the other tiers only by coincidence. `undefined` (an alarm, a
325
+ * subscription re-run, a non-Lunora caller propagated no verdict) omits the
326
+ * header and leaves the container on its own configuration.
327
+ */
328
+ declare const createContainerContext: (env: Record<string, unknown>, specs: ReadonlyArray<ContainerBindingSpec>, jurisdiction?: DurableObjectJurisdiction, traceparent?: string, sampleErrors?: boolean) => Record<string, ContainerAccessor>;
322
329
  /**
323
330
  * A test handler: receives the request plus the targeted instance name.
324
331
  */
package/dist/index.d.ts CHANGED
@@ -314,11 +314,18 @@ interface ContainerBindingSpec {
314
314
  * A missing binding doesn't throw here — only when the handle is actually used —
315
315
  * so one unprovisioned container never breaks unrelated functions.
316
316
  *
317
- * `traceparent` (the inbound RPC's W3C trace context, forwarded by the runtime
318
- * and read off the request by the DO) is stamped onto every outbound container
319
- * `fetch`, so the container's own spans stitch under the Worker's trace.
320
- */
321
- declare const createContainerContext: (env: Record<string, unknown>, specs: ReadonlyArray<ContainerBindingSpec>, jurisdiction?: DurableObjectJurisdiction, traceparent?: string) => Record<string, ContainerAccessor>;
317
+ * The last two arguments carry the dispatch's telemetry verdict onto every
318
+ * outbound container `fetch`, both read off the inbound request by the DO:
319
+ *
320
+ * - `traceparent` — the W3C trace context, so the container's own spans stitch
321
+ * under the Worker's trace rather than starting a disconnected one.
322
+ * - `sampleErrors` — the tail-bias toggle, so the container exports what THIS
323
+ * dispatch decided instead of falling back to its own environment and agreeing
324
+ * with the other tiers only by coincidence. `undefined` (an alarm, a
325
+ * subscription re-run, a non-Lunora caller propagated no verdict) omits the
326
+ * header and leaves the container on its own configuration.
327
+ */
328
+ declare const createContainerContext: (env: Record<string, unknown>, specs: ReadonlyArray<ContainerBindingSpec>, jurisdiction?: DurableObjectJurisdiction, traceparent?: string, sampleErrors?: boolean) => Record<string, ContainerAccessor>;
322
329
  /**
323
330
  * A test handler: receives the request plus the targeted instance name.
324
331
  */
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{createContainerContext as o,createContainerTestContext as r}from"./packem_shared/createContainerContext-BOuFFfdy.mjs";import{containerBindingName as a,containerBuildTag as i,containerClassName as C,defineContainer as m,isContainerDefinition as s,normalizeContainerImage as c,resolveContainerEnvVars as f}from"./packem_shared/containerBindingName-C7SonAIK.mjs";import{C as l}from"./packem_shared/exec-KlR4eMip.mjs";export{l as CONTAINER_EXEC_PATH,a as containerBindingName,i as containerBuildTag,C as containerClassName,o as createContainerContext,r as createContainerTestContext,m as defineContainer,s as isContainerDefinition,c as normalizeContainerImage,f as resolveContainerEnvVars};
1
+ import{createContainerContext as o,createContainerTestContext as r}from"./packem_shared/createContainerContext-DRyINB-P.mjs";import{containerBindingName as a,containerBuildTag as i,containerClassName as C,defineContainer as m,isContainerDefinition as s,normalizeContainerImage as c,resolveContainerEnvVars as f}from"./packem_shared/containerBindingName-C7SonAIK.mjs";import{C as l}from"./packem_shared/exec-KlR4eMip.mjs";export{l as CONTAINER_EXEC_PATH,a as containerBindingName,i as containerBuildTag,C as containerClassName,o as createContainerContext,r as createContainerTestContext,m as defineContainer,s as isContainerDefinition,c as normalizeContainerImage,f as resolveContainerEnvVars};
package/dist/otel.d.mts CHANGED
@@ -56,6 +56,24 @@ interface ContainerLogInput {
56
56
  * Options for {@link createContainerTelemetry}.
57
57
  */
58
58
  interface ContainerTelemetryOptions {
59
+ /**
60
+ * Keep a span that ERRORED even when the inbound `traceparent` says the trace
61
+ * was sampled out — the **tail bias**, the same rule `@lunora/runtime`'s
62
+ * `sampling.alwaysSampleErrors` and the shard apply.
63
+ *
64
+ * Normally you do not set this: the dispatch's verdict arrives on the
65
+ * inbound request (see {@link ContainerTelemetryOptions.request}), so all
66
+ * three tiers agree because they were told the same thing. Set it to
67
+ * override that. With neither, the `LUNORA_SAMPLE_ERRORS` env var applies
68
+ * (`"0"` turns it off) — which is what a one-shot container with no request
69
+ * to read has — and failing that, the tier default `true`.
70
+ *
71
+ * Per SPAN, not per trace: the container is a long-running process with no
72
+ * dispatch boundary to re-decide at, so a span that already settled `ok`
73
+ * before a sibling failed is not retro-exported. That is the same shape the
74
+ * worker applies to its own dispatch events.
75
+ */
76
+ alwaysSampleErrors?: boolean;
59
77
  /**
60
78
  * Value of the `deployment.environment` resource attribute. Falls back to
61
79
  * the `DEPLOYMENT_ENVIRONMENT` / `ENVIRONMENT` / `NODE_ENV` env vars **only
@@ -78,6 +96,26 @@ interface ContainerTelemetryOptions {
78
96
  headers?: Record<string, string>;
79
97
  /** Called with any send failure so the caller can surface it; the export itself always swallows. */
80
98
  onError?: (error: unknown) => void;
99
+ /**
100
+ * The inbound container request, read for the telemetry context the caller's
101
+ * Worker dispatch propagated onto it: the W3C `traceparent` and the
102
+ * tail-bias toggle.
103
+ *
104
+ * The one-call form of the per-request pattern. A container that serves many
105
+ * requests must build a telemetry instance per request anyway (the trace
106
+ * context differs each call), and passing the request means the handler does
107
+ * not have to know which headers carry what — which is the only way the
108
+ * toggle's header name would otherwise reach user code.
109
+ *
110
+ * ```ts
111
+ * const telemetry = createContainerTelemetry({ request });
112
+ * ```
113
+ */
114
+ request?: {
115
+ headers: {
116
+ get: (name: string) => null | string;
117
+ };
118
+ };
81
119
  /** Additional resource attributes merged onto every signal. */
82
120
  resourceAttributes?: Record<string, ContainerAttributeValue>;
83
121
  /** `service.name` resource attribute; defaults to the `LUNORA_SERVICE_NAME` env var then `"lunora-container"`. */
@@ -105,23 +143,28 @@ interface ContainerTelemetryOptions {
105
143
  * OBEYS rather than re-derives: a `traceparent` whose flags say the trace was
106
144
  * sampled out (`…-00`) suppresses span export, because the worker and shard
107
145
  * spans of that trace were dropped and shipping ours would leave the collector
108
- * holding the middle of a trace. Logs are never sampled and are unaffected.
146
+ * holding the middle of a trace. An ERRORED span is the exception — see
147
+ * {@link ContainerTelemetryOptions.alwaysSampleErrors}. Logs are never sampled
148
+ * and are unaffected.
109
149
  *
110
- * `@lunora/container` stamps this trace context as the **`traceparent` request
111
- * header** on every proxied fetch (`ctx.containers.<name>.…`), so a container
112
- * that serves many requests should read it per request and create a telemetry
113
- * instance scoped to that request — the trace context differs each call, so a
114
- * single process-lifetime instance can't carry it:
150
+ * `@lunora/container` stamps this trace context on every proxied fetch
151
+ * (`ctx.containers.<name>.…`), so a container that serves many requests
152
+ * should create a telemetry instance per request — the trace context differs
153
+ * each call, so a single process-lifetime instance can't carry it. Prefer
154
+ * {@link ContainerTelemetryOptions.request}, which takes this AND the
155
+ * tail-bias toggle off the same request:
115
156
  *
116
157
  * ```ts
117
158
  * // inside the container's request handler
118
- * const telemetry = createContainerTelemetry({ traceparent: request.headers.get("traceparent") ?? undefined });
159
+ * const telemetry = createContainerTelemetry({ request });
119
160
  * await telemetry.trace("transcode", () => transcode(job));
120
161
  * await telemetry.flush();
121
162
  * ```
122
163
  *
123
- * The `LUNORA_TRACEPARENT` env fallback fits a one-shot container that
124
- * processes a single job per start (the value is fixed for the process).
164
+ * Pass this option explicitly only to override what the request carries. The
165
+ * `LUNORA_TRACEPARENT` env fallback fits a one-shot container that processes
166
+ * a single job per start, where there is no request to read (the value is
167
+ * fixed for the process).
125
168
  */
126
169
  traceparent?: string;
127
170
  }
package/dist/otel.d.ts CHANGED
@@ -56,6 +56,24 @@ interface ContainerLogInput {
56
56
  * Options for {@link createContainerTelemetry}.
57
57
  */
58
58
  interface ContainerTelemetryOptions {
59
+ /**
60
+ * Keep a span that ERRORED even when the inbound `traceparent` says the trace
61
+ * was sampled out — the **tail bias**, the same rule `@lunora/runtime`'s
62
+ * `sampling.alwaysSampleErrors` and the shard apply.
63
+ *
64
+ * Normally you do not set this: the dispatch's verdict arrives on the
65
+ * inbound request (see {@link ContainerTelemetryOptions.request}), so all
66
+ * three tiers agree because they were told the same thing. Set it to
67
+ * override that. With neither, the `LUNORA_SAMPLE_ERRORS` env var applies
68
+ * (`"0"` turns it off) — which is what a one-shot container with no request
69
+ * to read has — and failing that, the tier default `true`.
70
+ *
71
+ * Per SPAN, not per trace: the container is a long-running process with no
72
+ * dispatch boundary to re-decide at, so a span that already settled `ok`
73
+ * before a sibling failed is not retro-exported. That is the same shape the
74
+ * worker applies to its own dispatch events.
75
+ */
76
+ alwaysSampleErrors?: boolean;
59
77
  /**
60
78
  * Value of the `deployment.environment` resource attribute. Falls back to
61
79
  * the `DEPLOYMENT_ENVIRONMENT` / `ENVIRONMENT` / `NODE_ENV` env vars **only
@@ -78,6 +96,26 @@ interface ContainerTelemetryOptions {
78
96
  headers?: Record<string, string>;
79
97
  /** Called with any send failure so the caller can surface it; the export itself always swallows. */
80
98
  onError?: (error: unknown) => void;
99
+ /**
100
+ * The inbound container request, read for the telemetry context the caller's
101
+ * Worker dispatch propagated onto it: the W3C `traceparent` and the
102
+ * tail-bias toggle.
103
+ *
104
+ * The one-call form of the per-request pattern. A container that serves many
105
+ * requests must build a telemetry instance per request anyway (the trace
106
+ * context differs each call), and passing the request means the handler does
107
+ * not have to know which headers carry what — which is the only way the
108
+ * toggle's header name would otherwise reach user code.
109
+ *
110
+ * ```ts
111
+ * const telemetry = createContainerTelemetry({ request });
112
+ * ```
113
+ */
114
+ request?: {
115
+ headers: {
116
+ get: (name: string) => null | string;
117
+ };
118
+ };
81
119
  /** Additional resource attributes merged onto every signal. */
82
120
  resourceAttributes?: Record<string, ContainerAttributeValue>;
83
121
  /** `service.name` resource attribute; defaults to the `LUNORA_SERVICE_NAME` env var then `"lunora-container"`. */
@@ -105,23 +143,28 @@ interface ContainerTelemetryOptions {
105
143
  * OBEYS rather than re-derives: a `traceparent` whose flags say the trace was
106
144
  * sampled out (`…-00`) suppresses span export, because the worker and shard
107
145
  * spans of that trace were dropped and shipping ours would leave the collector
108
- * holding the middle of a trace. Logs are never sampled and are unaffected.
146
+ * holding the middle of a trace. An ERRORED span is the exception — see
147
+ * {@link ContainerTelemetryOptions.alwaysSampleErrors}. Logs are never sampled
148
+ * and are unaffected.
109
149
  *
110
- * `@lunora/container` stamps this trace context as the **`traceparent` request
111
- * header** on every proxied fetch (`ctx.containers.<name>.…`), so a container
112
- * that serves many requests should read it per request and create a telemetry
113
- * instance scoped to that request — the trace context differs each call, so a
114
- * single process-lifetime instance can't carry it:
150
+ * `@lunora/container` stamps this trace context on every proxied fetch
151
+ * (`ctx.containers.<name>.…`), so a container that serves many requests
152
+ * should create a telemetry instance per request — the trace context differs
153
+ * each call, so a single process-lifetime instance can't carry it. Prefer
154
+ * {@link ContainerTelemetryOptions.request}, which takes this AND the
155
+ * tail-bias toggle off the same request:
115
156
  *
116
157
  * ```ts
117
158
  * // inside the container's request handler
118
- * const telemetry = createContainerTelemetry({ traceparent: request.headers.get("traceparent") ?? undefined });
159
+ * const telemetry = createContainerTelemetry({ request });
119
160
  * await telemetry.trace("transcode", () => transcode(job));
120
161
  * await telemetry.flush();
121
162
  * ```
122
163
  *
123
- * The `LUNORA_TRACEPARENT` env fallback fits a one-shot container that
124
- * processes a single job per start (the value is fixed for the process).
164
+ * Pass this option explicitly only to override what the request carries. The
165
+ * `LUNORA_TRACEPARENT` env fallback fits a one-shot container that processes
166
+ * a single job per start, where there is no request to read (the value is
167
+ * fixed for the process).
125
168
  */
126
169
  traceparent?: string;
127
170
  }
package/dist/otel.mjs CHANGED
@@ -1 +1 @@
1
- import{a as F}from"./packem_shared/abort-deadline-jwbW9Ala.mjs";const Y={debug:5,error:17,fatal:21,info:9,log:9,trace:1,warn:13},N=e=>`${String(Math.round(e))}000000`,X=Array.from({length:256},(e,t)=>t.toString(16).padStart(2,"0")),y=512,x=new Uint8Array(y);let T=y;const L=(e,t,r)=>{let n="";for(let o=0;o<r;o+=1)n+=X[e[t+o]];return n},U=e=>{if(e>y){const r=new Uint8Array(e);return crypto.getRandomValues(r),L(r,0,e)}T+e>y&&(crypto.getRandomValues(x),T=0);const t=L(x,T,e);return T+=e,t},S=/^[0-9a-f]+$/,G=e=>{if(e==null)return;const t=e.trim().toLowerCase().split("-"),[r,n,o,s]=t;if(!(t.length<4||r===void 0||r.length!==2||!S.test(r)||r==="ff"||r==="00"&&t.length!==4||n===void 0||o===void 0||s===void 0||s.length!==2||!S.test(s)||n.length!==32||o.length!==16||!S.test(n)||!S.test(o)||n==="00000000000000000000000000000000"||o==="0000000000000000"))return{parentSpanId:o,sampled:(Number.parseInt(s,16)&1)===1,traceId:n}},h=(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}},P=e=>e===void 0?[]:Object.entries(e).map(([t,r])=>h(t,r)),K=(e,t,r)=>{const n={},o=new Map,s=(c,d)=>{const m=c.toLowerCase(),f=o.get(m);f===void 0?(o.set(m,c),n[c]=d):n[f]=d};for(const[c,d]of Object.entries(e))s(c,d);for(const[c,d]of Object.entries(t??{}))s(c,d);return r!==void 0&&r.length>0&&s("authorization",`Bearer ${r}`),n},D=e=>Array.isArray(e)?e:[e],C=(e,t)=>{const r={"service.name":e};for(const[n,o]of Object.entries(t??{}))r[n]=o;return Object.entries(r).map(([n,o])=>h(n,o))},z=(e,t,r,n)=>({resourceSpans:[{resource:{attributes:C(r,n)},scopeSpans:[{scope:{name:t},spans:D(e)}]}]}),W=(e,t,r,n)=>({resourceLogs:[{resource:{attributes:C(r,n)},scopeLogs:[{logRecords:D(e),scope:{name:t}}]}]}),k=512,J=200,V=e=>{const t=e.maxItems??k,r=e.maxDelayMs??J;let n=[],o,s,c;const d=()=>{o!==void 0&&(clearTimeout(o),o=void 0)},m=async()=>{d();const u=n;n=[];const a=c;s=void 0,c=void 0;try{u.length>0&&await e.export(u)}catch{}finally{a?.()}},f=u=>{s===void 0&&(s=new Promise(a=>{c=a}),o=setTimeout(()=>{m()},r)),u?.(s)};return{add:(u,a)=>{for(n.push(u);n.length>t;)n.shift();f(a),n.length>=t&&m()},flush:async u=>{if(n.length===0){d();return}const a=m();return u?.(a),a},get size(){return n.length}}},q=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 n=e("DEPLOYMENT_ENVIRONMENT")??e("ENVIRONMENT")??e("NODE_ENV");return n!==void 0&&(t["deployment.environment"]=n),t},Q=(e,t)=>{const r={},n=e("HOSTNAME")??e("COMPUTERNAME");n!==void 0&&(r["host.name"]=n);const o=e("KUBERNETES_POD_NAME")??(e("KUBERNETES_SERVICE_HOST")===void 0?void 0:e("HOSTNAME"));return o!==void 0&&(r["k8s.pod.name"]=o),t!==void 0&&Number.isFinite(t)&&(r["process.pid"]=t),r},H=(...e)=>{const t={};for(const r of e)if(r!==void 0)for(const[n,o]of Object.entries(r))t[n]=o;return t},Z=1e4,B=1,p=e=>process.env[e],ee=()=>{const e=typeof process.pid!="number"?void 0:process.pid;return H(q(p),Q(p,e))},te=e=>e??(typeof globalThis.fetch=="function"?globalThis.fetch:void 0),re=(e,t)=>{const r=P(e.attributes);e.error?.type!==void 0&&r.push(h("error.type",e.error.type));const n={attributes:r,endTimeUnixNano:N(e.endMs),flags:t?.sampled??!0?B:0,kind:1,name:e.name,...t===void 0?{}:{parentSpanId:t.parentSpanId},spanId:U(8),startTimeUnixNano:N(e.startMs),status:e.error===void 0?{code:1}:{code:2,message:e.error.message},traceId:t?.traceId??U(16)};return e.error&&(n.events=[{attributes:[h("exception.type",e.error.type??"Error"),h("exception.message",e.error.message)],name:"exception",timeUnixNano:N(e.endMs)}]),n},ne=(e,t,r)=>{const n=e.level??"info";return{attributes:P(e.attributes),body:{stringValue:e.message},severityNumber:Y[n],severityText:n.toUpperCase(),timeUnixNano:N(e.ts??t),...r===void 0?{}:{flags:r.sampled?B:0,spanId:r.parentSpanId,traceId:r.traceId}}},ae=(e={})=>{const t=e.endpoint??p("LUNORA_OTLP_ENDPOINT"),r=t!==void 0&&t.length>0,n=e.token??p("LUNORA_OTLP_TOKEN"),o=e.serviceName??p("LUNORA_SERVICE_NAME")??"lunora-container",s=G(e.traceparent??p("LUNORA_TRACEPARENT")),c=s?.sampled!==!1,d=e.timeoutMs??Z,m=te(e.fetch),f=K({"content-type":"application/json"},e.headers,n),u=e.detectResources===!0?ee():void 0,a={};e.serviceVersion!==void 0&&(a["service.version"]=e.serviceVersion),e.deploymentEnvironment!==void 0&&(a["deployment.environment"]=e.deploymentEnvironment),e.resourceAttributes!==void 0&&Object.assign(a,e.resourceAttributes);const M=H(u,a);let g=t??"";for(;g.endsWith("/");)g=g.slice(0,-1);const $=`${g}/v1/traces`,j=`${g}/v1/logs`,A=new Set,R=(i,O)=>{if(m===void 0)return e.onError?.(new TypeError("createContainerTelemetry: no `fetch` available — pass `fetch` in options for this runtime.")),Promise.resolve();const v=(async()=>{const l=F(void 0,d,()=>new DOMException(`OTLP export to ${i} timed out after ${String(d)}ms`,"TimeoutError"));try{const E=await m(i,{body:JSON.stringify(O),headers:f,method:"POST",signal:l.signal});E.ok||e.onError?.(new Error(`createContainerTelemetry: OTLP export to ${i} failed with status ${String(E.status)}.`));try{await E.body?.cancel()}catch{}}catch(E){e.onError?.(E)}finally{l.dispose()}})().finally(()=>{A.delete(v)});return A.add(v),v},I=V({export:i=>R($,z(i,"@lunora/container",o,M))}),w=V({export:i=>R(j,W(i,"@lunora/container",o,M))}),b=i=>{!r||!c||I.add(re(i,s))};return{emitLog:i=>{r&&w.add(ne(i,Date.now(),s))},emitSpan:b,enabled:r,flush:async()=>{await Promise.allSettled([I.flush(),w.flush()]),await Promise.allSettled(A)},trace:async(i,O,_)=>{const v=Date.now();try{const l=await O();return b({attributes:_,endMs:Date.now(),name:i,startMs:v}),l}catch(l){throw b({attributes:_,endMs:Date.now(),error:{message:l instanceof Error?l.message:String(l),type:l instanceof Error?l.name:void 0},name:i,startMs:v}),l}}}};export{ae as createContainerTelemetry};
1
+ import{a as X}from"./packem_shared/abort-deadline-jwbW9Ala.mjs";import{S as G,s as K}from"./packem_shared/sampling-D21rpu_s.mjs";const k={debug:5,error:17,fatal:21,info:9,log:9,trace:1,warn:13},N=e=>`${String(Math.round(e))}000000`,z=Array.from({length:256},(e,t)=>t.toString(16).padStart(2,"0")),y=512,x=new Uint8Array(y);let T=y;const U=(e,t,r)=>{let n="";for(let o=0;o<r;o+=1)n+=z[e[t+o]];return n},P=e=>{if(e>y){const r=new Uint8Array(e);return crypto.getRandomValues(r),U(r,0,e)}T+e>y&&(crypto.getRandomValues(x),T=0);const t=U(x,T,e);return T+=e,t},A=/^[0-9a-f]+$/,W=e=>{if(e==null)return;const t=e.trim().toLowerCase().split("-"),[r,n,o,s]=t;if(!(t.length<4||r===void 0||r.length!==2||!A.test(r)||r==="ff"||r==="00"&&t.length!==4||n===void 0||o===void 0||s===void 0||s.length!==2||!A.test(s)||n.length!==32||o.length!==16||!A.test(n)||!A.test(o)||n==="00000000000000000000000000000000"||o==="0000000000000000"))return{parentSpanId:o,sampled:(Number.parseInt(s,16)&1)===1,traceId:n}},h=(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}},D=e=>e===void 0?[]:Object.entries(e).map(([t,r])=>h(t,r)),q=(e,t,r)=>{const n={},o=new Map,s=(i,a)=>{const m=i.toLowerCase(),f=o.get(m);f===void 0?(o.set(m,i),n[i]=a):n[f]=a};for(const[i,a]of Object.entries(e))s(i,a);for(const[i,a]of Object.entries(t??{}))s(i,a);return r!==void 0&&r.length>0&&s("authorization",`Bearer ${r}`),n},C=e=>Array.isArray(e)?e:[e],H=(e,t)=>{const r={"service.name":e};for(const[n,o]of Object.entries(t??{}))r[n]=o;return Object.entries(r).map(([n,o])=>h(n,o))},J=(e,t,r,n)=>({resourceSpans:[{resource:{attributes:H(r,n)},scopeSpans:[{scope:{name:t},spans:C(e)}]}]}),Q=(e,t,r,n)=>({resourceLogs:[{resource:{attributes:H(r,n)},scopeLogs:[{logRecords:C(e),scope:{name:t}}]}]}),Z=512,ee=200,V=e=>{const t=e.maxItems??Z,r=e.maxDelayMs??ee;let n=[],o,s,i;const a=()=>{o!==void 0&&(clearTimeout(o),o=void 0)},m=async()=>{a();const d=n;n=[];const u=i;s=void 0,i=void 0;try{d.length>0&&await e.export(d)}catch{}finally{u?.()}},f=d=>{s===void 0&&(s=new Promise(u=>{i=u}),o=setTimeout(()=>{m()},r)),d?.(s)};return{add:(d,u)=>{for(n.push(d);n.length>t;)n.shift();f(u),n.length>=t&&m()},flush:async d=>{if(n.length===0){a();return}const u=m();return d?.(u),u},get size(){return n.length}}},te=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 n=e("DEPLOYMENT_ENVIRONMENT")??e("ENVIRONMENT")??e("NODE_ENV");return n!==void 0&&(t["deployment.environment"]=n),t},re=(e,t)=>{const r={},n=e("HOSTNAME")??e("COMPUTERNAME");n!==void 0&&(r["host.name"]=n);const o=e("KUBERNETES_POD_NAME")??(e("KUBERNETES_SERVICE_HOST")===void 0?void 0:e("HOSTNAME"));return o!==void 0&&(r["k8s.pod.name"]=o),t!==void 0&&Number.isFinite(t)&&(r["process.pid"]=t),r},B=(...e)=>{const t={};for(const r of e)if(r!==void 0)for(const[n,o]of Object.entries(r))t[n]=o;return t},ne=e=>e===void 0||e!=="0",oe=1e4,$=1,E=e=>process.env[e],se=()=>{const e=typeof process.pid!="number"?void 0:process.pid;return B(te(E),re(E,e))},ie=e=>e??(typeof globalThis.fetch=="function"?globalThis.fetch:void 0),ce=(e,t)=>{const r=D(e.attributes);e.error?.type!==void 0&&r.push(h("error.type",e.error.type));const n={attributes:r,endTimeUnixNano:N(e.endMs),flags:t?.sampled??!0?$:0,kind:1,name:e.name,...t===void 0?{}:{parentSpanId:t.parentSpanId},spanId:P(8),startTimeUnixNano:N(e.startMs),status:e.error===void 0?{code:1}:{code:2,message:e.error.message},traceId:t?.traceId??P(16)};return e.error&&(n.events=[{attributes:[h("exception.type",e.error.type??"Error"),h("exception.message",e.error.message)],name:"exception",timeUnixNano:N(e.endMs)}]),n},ae=(e,t,r)=>{const n=e.level??"info";return{attributes:D(e.attributes),body:{stringValue:e.message},severityNumber:k[n],severityText:n.toUpperCase(),timeUnixNano:N(e.ts??t),...r===void 0?{}:{flags:r.sampled?$:0,spanId:r.parentSpanId,traceId:r.traceId}}},Ee=(e={})=>{const t=e.endpoint??E("LUNORA_OTLP_ENDPOINT"),r=t!==void 0&&t.length>0,n=e.token??E("LUNORA_OTLP_TOKEN"),o=e.serviceName??E("LUNORA_SERVICE_NAME")??"lunora-container",s=e.request?.headers,i=W(e.traceparent??s?.get("traceparent")??E("LUNORA_TRACEPARENT")),a=i?.sampled!==!1,m=e.alwaysSampleErrors??ne(s?.get(G)??E("LUNORA_SAMPLE_ERRORS")),f=e.timeoutMs??oe,d=ie(e.fetch),u=q({"content-type":"application/json"},e.headers,n),j=e.detectResources===!0?se():void 0,S={};e.serviceVersion!==void 0&&(S["service.version"]=e.serviceVersion),e.deploymentEnvironment!==void 0&&(S["deployment.environment"]=e.deploymentEnvironment),e.resourceAttributes!==void 0&&Object.assign(S,e.resourceAttributes);const M=B(j,S);let p=t??"";for(;p.endsWith("/");)p=p.slice(0,-1);const F=`${p}/v1/traces`,Y=`${p}/v1/logs`,O=new Set,I=(c,R)=>{if(d===void 0)return e.onError?.(new TypeError("createContainerTelemetry: no `fetch` available — pass `fetch` in options for this runtime.")),Promise.resolve();const v=(async()=>{const l=X(void 0,f,()=>new DOMException(`OTLP export to ${c} timed out after ${String(f)}ms`,"TimeoutError"));try{const g=await d(c,{body:JSON.stringify(R),headers:u,method:"POST",signal:l.signal});g.ok||e.onError?.(new Error(`createContainerTelemetry: OTLP export to ${c} failed with status ${String(g.status)}.`));try{await g.body?.cancel()}catch{}}catch(g){e.onError?.(g)}finally{l.dispose()}})().finally(()=>{O.delete(v)});return O.add(v),v},L=V({export:c=>I(F,J(c,"@lunora/container",o,M))}),w=V({export:c=>I(Y,Q(c,"@lunora/container",o,M))}),b=c=>{!r||!K({isTraced:a,keepErrors:m},c.error!==void 0)||L.add(ce(c,i))};return{emitLog:c=>{r&&w.add(ae(c,Date.now(),i))},emitSpan:b,enabled:r,flush:async()=>{await Promise.allSettled([L.flush(),w.flush()]),await Promise.allSettled(O)},trace:async(c,R,_)=>{const v=Date.now();try{const l=await R();return b({attributes:_,endMs:Date.now(),name:c,startMs:v}),l}catch(l){throw b({attributes:_,endMs:Date.now(),error:{message:l instanceof Error?l.message:String(l),type:l instanceof Error?l.name:void 0},name:c,startMs:v}),l}}}};export{Ee as createContainerTelemetry};
@@ -0,0 +1 @@
1
+ import{LunoraError as D}from"@lunora/errors";import{e as N,r as k}from"./exec-KlR4eMip.mjs";import{S as I}from"./sampling-D21rpu_s.mjs";import{containerBindingName as U}from"./containerBindingName-C7SonAIK.mjs";import{a as q}from"./jurisdiction-DtE9s70w.mjs";const p=3,C=3e4,W=3,j=500,Y="cf-container-target-port",v="__lunora",F=t=>t.split("/").find(e=>e!=="")??"",P=(t,e)=>{const o=typeof t=="string"?t:t.url,n=URL.parse(o.startsWith("/")?`http://container${o}`:o);if(n===null)return;const{pathname:r}=n;let s=r;try{s=decodeURIComponent(r)}catch{}if(F(r)===v||F(s)===v)throw new D("BAD_REQUEST",`${e}: \`/${v}/*\` is reserved for Lunora's own container routes and cannot be reached with \`fetch\`. Use \`exec\` to run a command.`)},L=(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(Y,String(o)),n?.traceparent!==void 0&&r.headers.set("traceparent",n.traceparent),n?.sampleErrors!==void 0&&r.headers.set(I,n.sampleErrors?"1":"0"),r},O=async t=>{t<=0||await new Promise(e=>{setTimeout(e,t)})},z=/no container instance|not listening|try again later|rate.?limit|provision/i,G="no Container instance available",K="Failed to start container:",V=1024,J=async t=>(await k(t.clone().body,V)).text,H=t=>t instanceof Error&&z.test(t.message),b=async t=>{if(t.status===429)return!0;if(t.status!==500&&t.status!==503)return!1;try{const e=await J(t);return e.includes(G)||e.startsWith(K)}catch{return!1}},T=t=>`ctx.containers.${t.exportName}`,S=(t,e,o={},n,r)=>{const s=Math.max(1,o.attempts??W),a=o.backoffMs??j,c=o.maxBackoffMs??C,g=async(l,y)=>{const E=typeof l=="string"?s:1;let d;for(let m=0;m<E;m+=1){const u=m===E-1;m>0&&await O(Math.min(a*2**(m-1),c));try{const f=await t(L(l,y,n,r));if(u||!await b(f))return f}catch(f){if(d=f,u||!H(f))throw f}}throw d instanceof Error?d:new Error("ctx.containers: cold-start retry exhausted")};return{exec:N(g,e),fetch:async(l,y)=>(P(l,e),g(l,y)),port:l=>S(t,e,o,l,r)}},M=(t,e,o,n,r)=>S(async s=>t.get(t.idFromName(e)).fetch(s),o,n,void 0,r),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])}),B=(t,e,o,n,r)=>{const s=()=>t.get(t.idFromName(o));return{...S(async a=>s().fetch(a),T(e),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)}},R=t=>`pool-${String(Math.floor(Math.random()*t))}`,X=t=>t.status>=500,x=(t,e,o={},n,r)=>{const s=o.size??e.maxInstances??p,a=Math.max(1,o.attempts??3),c=o.backoffMs??100,g=o.maxBackoffMs??C,l=`${T(e)}.pool()`,y=(d,m)=>async(u,f)=>{const A=typeof u=="string"?a:1;let _;for(let w=0;w<A;w+=1){w>0&&await O(Math.min(c*2**(w-1),g));const $=L(u,f,n,r);try{const h=await t.get(t.idFromName(R(s))).fetch($);if(w===A-1||!await d(h))return h}catch(h){if(_=h,!m(h))throw h}}throw _ instanceof Error?_:new Error(`ctx.containers.${e.exportName}.pool(): all ${String(A)} attempts failed`)},E=y(o.retryOn??X,()=>!0);return{exec:N(y(b,H),l),fetch:async(d,m)=>(P(d,l),E(d,m)),port:d=>x(t,e,o,d,r)}},Z=(t,e,o)=>({any:(n,r)=>M(t,R(n??e.maxInstances??p),T(e),r,o),get:(n,r)=>B(t,e,n,r,o),pool:n=>x(t,e,n,void 0,o)}),tt=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}},ct=(t,e,o,n,r)=>{const s={};for(const a of e){const c=t[a.binding];s[a.exportName]=c&&typeof c.idFromName=="function"&&typeof c.get=="function"?Z(q(c,o),a,{...r===void 0?{}:{sampleErrors:r},...n===void 0?{}:{traceparent:n}}):tt(a)}return s},et=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}},it=t=>{const e={};for(const[o,n]of Object.entries(t)){const r=et(n),s={binding:U(o),exportName:o};e[o]={any:(a,c)=>M(r,R(a??s.maxInstances??p),T(s),{attempts:1,...c}),get:(a,c)=>B(r,s,a,{attempts:1,...c}),pool:a=>x(r,s,{...a,attempts:1})}}return e};export{ct as createContainerContext,it as createContainerTestContext};
@@ -0,0 +1 @@
1
+ const e="x-lunora-sample-errors",o=(r,s)=>r.isTraced||r.keepErrors&&s;export{e as S,o as s};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/container",
3
- "version": "1.0.0-alpha.53",
3
+ "version": "1.0.0-alpha.55",
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.39"
59
+ "@lunora/errors": "1.0.0-alpha.40"
60
60
  },
61
61
  "engines": {
62
62
  "node": "^22.15.0 || >=24.11.0"
@@ -1 +0,0 @@
1
- import{LunoraError as N}from"@lunora/errors";import{e as D,r as k}from"./exec-KlR4eMip.mjs";import{containerBindingName as I}from"./containerBindingName-C7SonAIK.mjs";import{a as U}from"./jurisdiction-DtE9s70w.mjs";const x=3,C=3e4,q=3,W=500,j="cf-container-target-port",v="__lunora",F=t=>t.split("/").find(e=>e!=="")??"",P=(t,e)=>{const o=typeof t=="string"?t:t.url,n=URL.parse(o.startsWith("/")?`http://container${o}`:o);if(n===null)return;const{pathname:r}=n;let s=r;try{s=decodeURIComponent(r)}catch{}if(F(r)===v||F(s)===v)throw new N("BAD_REQUEST",`${e}: \`/${v}/*\` is reserved for Lunora's own container routes and cannot be reached with \`fetch\`. Use \`exec\` to run a command.`)},L=(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(j,String(o)),n!==void 0&&r.headers.set("traceparent",n),r},O=async t=>{t<=0||await new Promise(e=>{setTimeout(e,t)})},Y=/no container instance|not listening|try again later|rate.?limit|provision/i,z="no Container instance available",G="Failed to start container:",K=1024,V=async t=>(await k(t.clone().body,K)).text,H=t=>t instanceof Error&&Y.test(t.message),b=async t=>{if(t.status===429)return!0;if(t.status!==500&&t.status!==503)return!1;try{const e=await V(t);return e.includes(z)||e.startsWith(G)}catch{return!1}},A=t=>`ctx.containers.${t.exportName}`,S=(t,e,o={},n,r)=>{const s=Math.max(1,o.attempts??q),a=o.backoffMs??W,f=o.maxBackoffMs??C,g=async(i,y)=>{const T=typeof i=="string"?s:1;let l;for(let d=0;d<T;d+=1){const h=d===T-1;d>0&&await O(Math.min(a*2**(d-1),f));try{const m=await t(L(i,y,n,r));if(h||!await b(m))return m}catch(m){if(l=m,h||!H(m))throw m}}throw l instanceof Error?l:new Error("ctx.containers: cold-start retry exhausted")};return{exec:D(g,e),fetch:async(i,y)=>(P(i,e),g(i,y)),port:i=>S(t,e,o,i,r)}},B=(t,e,o,n,r)=>S(async s=>t.get(t.idFromName(e)).fetch(s),o,n,void 0,r),c=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)},J=(t,e)=>({allow:async o=>c(t(),"allowHost",e,o),deny:async o=>c(t(),"denyHost",e,o),removeAllowed:async o=>c(t(),"removeAllowedHost",e,o),removeDenied:async o=>c(t(),"removeDeniedHost",e,o),setAllowed:async o=>c(t(),"setAllowedHosts",e,[...o]),setDenied:async o=>c(t(),"setDeniedHosts",e,[...o])}),M=(t,e,o,n,r)=>{const s=()=>t.get(t.idFromName(o));return{...S(async a=>s().fetch(a),A(e),n,void 0,r),destroy:async()=>c(s(),"destroy",e.binding),egress:J(s,e.binding),getState:async()=>c(s(),"getState",e.binding),renewActivityTimeout:async()=>c(s(),"renewActivityTimeout",e.binding),start:async a=>c(s(),"start",e.binding,a),stop:async a=>c(s(),"stop",e.binding,a)}},R=t=>`pool-${String(Math.floor(Math.random()*t))}`,Q=t=>t.status>=500,p=(t,e,o={},n,r)=>{const s=o.size??e.maxInstances??x,a=Math.max(1,o.attempts??3),f=o.backoffMs??100,g=o.maxBackoffMs??C,i=`${A(e)}.pool()`,y=(l,d)=>async(h,m)=>{const E=typeof h=="string"?a:1;let _;for(let w=0;w<E;w+=1){w>0&&await O(Math.min(f*2**(w-1),g));const $=L(h,m,n,r);try{const u=await t.get(t.idFromName(R(s))).fetch($);if(w===E-1||!await l(u))return u}catch(u){if(_=u,!d(u))throw u}}throw _ instanceof Error?_:new Error(`ctx.containers.${e.exportName}.pool(): all ${String(E)} attempts failed`)},T=y(o.retryOn??Q,()=>!0);return{exec:D(y(b,H),i),fetch:async(l,d)=>(P(l,i),T(l,d)),port:l=>p(t,e,o,l,r)}},X=(t,e,o)=>({any:(n,r)=>B(t,R(n??e.maxInstances??x),A(e),r,o),get:(n,r)=>M(t,e,n,r,o),pool:n=>p(t,e,n,void 0,o)}),Z=t=>{const e=()=>{throw new N("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}},st=(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"?X(U(a,o),s,n):Z(s)}return r},tt=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}},at=t=>{const e={};for(const[o,n]of Object.entries(t)){const r=tt(n),s={binding:I(o),exportName:o};e[o]={any:(a,f)=>B(r,R(a??s.maxInstances??x),A(s),{attempts:1,...f}),get:(a,f)=>M(r,s,a,{attempts:1,...f}),pool:a=>p(r,s,{...a,attempts:1})}}return e};export{st as createContainerContext,at as createContainerTestContext};