@lunora/container 1.0.0-alpha.23 → 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/do/index.mjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/otel.mjs +1 -1
- package/dist/packem_shared/containerBindingName-CP4v-2Cw.mjs +1 -0
- package/dist/packem_shared/{createContainerContext-Df9Ev-Fp.mjs → createContainerContext-BXarJmWd.mjs} +1 -1
- package/package.json +2 -2
- package/dist/packem_shared/containerBindingName-DP2NqQV-.mjs +0 -1
package/dist/do/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{Container as S}from"../packem_shared/ContainerProxy-BsQAwSNX.mjs";import{ContainerProxy as
|
|
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.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createContainerContext as t,createContainerTestContext as a}from"./packem_shared/createContainerContext-
|
|
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.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=>
|
|
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
|
|
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};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lunora/container",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
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.
|
|
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};
|