@lunora/container 1.0.0-alpha.68 → 1.0.0-alpha.69
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.d.mts +1 -1
- package/dist/do/index.d.ts +1 -1
- package/dist/do/index.mjs +1 -1
- package/dist/index.d.mts +29 -4
- package/dist/index.d.ts +29 -4
- package/dist/index.mjs +1 -1
- package/dist/packem_shared/CUSTOM_INSTANCE_MIN_MEMORY_MIB_PER_VCPU-D8Hy9Rxv.mjs +1 -0
- package/dist/packem_shared/DURABLE_OBJECT_POLICY_FORBIDDEN_KEYS-gYi8RBUO.mjs +1 -0
- package/dist/packem_shared/createContainerContext-p6QxWjsm.mjs +1 -0
- package/dist/packem_shared/{jurisdiction.d-B8_fht7G.d.mts → jurisdiction.d-BwpauCoI.d.mts} +4 -1
- package/dist/packem_shared/{jurisdiction.d-B8_fht7G.d.ts → jurisdiction.d-BwpauCoI.d.ts} +4 -1
- package/dist/sandbox/index.d.mts +1 -1
- package/dist/sandbox/index.d.ts +1 -1
- package/package.json +1 -1
- package/dist/packem_shared/DURABLE_OBJECT_POLICY_FORBIDDEN_KEYS-B9DkqZkf.mjs +0 -1
- package/dist/packem_shared/createContainerContext-CcW1olml.mjs +0 -1
package/dist/do/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { d as ContainerRuntimeInstanceType, C as ContainerDefinition, D as DurableObjectJurisdiction, b as ContainerSnapshot, e as ContainerSpawnRequest } from "../packem_shared/jurisdiction.d-
|
|
1
|
+
import { d as ContainerRuntimeInstanceType, C as ContainerDefinition, D as DurableObjectJurisdiction, b as ContainerSnapshot, e as ContainerSpawnRequest } from "../packem_shared/jurisdiction.d-BwpauCoI.mjs";
|
|
2
2
|
import { DurableObject, WorkerEntrypoint, RpcTarget } from 'cloudflare:workers';
|
|
3
3
|
/**
|
|
4
4
|
* ContainerStartOptions as they come from worker types
|
package/dist/do/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { d as ContainerRuntimeInstanceType, C as ContainerDefinition, D as DurableObjectJurisdiction, b as ContainerSnapshot, e as ContainerSpawnRequest } from "../packem_shared/jurisdiction.d-
|
|
1
|
+
import { d as ContainerRuntimeInstanceType, C as ContainerDefinition, D as DurableObjectJurisdiction, b as ContainerSnapshot, e as ContainerSpawnRequest } from "../packem_shared/jurisdiction.d-BwpauCoI.js";
|
|
2
2
|
import { DurableObject, WorkerEntrypoint, RpcTarget } from 'cloudflare:workers';
|
|
3
3
|
/**
|
|
4
4
|
* ContainerStartOptions as they come from worker types
|
package/dist/do/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{LunoraError as u}from"@lunora/errors";import{a as A}from"../packem_shared/abort-deadline-jwbW9Ala.mjs";import{isManagedImage as C,resolveContainerEnvVars as I,parseDurationSeconds as L}from"../packem_shared/DURABLE_OBJECT_POLICY_FORBIDDEN_KEYS-B9DkqZkf.mjs";import{D as H,C as O,p as M}from"../packem_shared/exec-BYLW70sR.mjs";import{a as j}from"../packem_shared/jurisdiction-DtE9s70w.mjs";import{RpcTarget as V}from"cloudflare:workers";import{Container as U}from"../packem_shared/ContainerProxy-CfXjS6ZI.mjs";import{ContainerProxy as wt,outboundParams as ft}from"../packem_shared/ContainerProxy-CfXjS6ZI.mjs";const k="lunora",B=(s,t,e,n)=>({container:s,event:e,instance:t,level:e==="error"?"error":"info",message:n,source:k,ts:Date.now(),type:"container"}),p=(s,t,e,n)=>{const r=B(s,t,e,n),a=JSON.stringify(r);return e==="error"?console.error(a):console.log(a),r},F=async(s,t,e,n)=>{const r=t.maxOutputBytes??H,a=await s([t.command,...t.args??[]],{...t.cwd===void 0?{}:{cwd:t.cwd},env:{...e,...t.env},stderr:"pipe",stdout:"pipe"}),o=new AbortController;let i=!1;const c={overflowed:!1,timedOut:!1},v=()=>c.timedOut,m=a.exitCode.finally(()=>{i=!0}),y=()=>{o.abort(),i||(i=!0,a.kill())},$=t.timeoutMs===void 0?void 0:setTimeout(()=>{c.timedOut=!0,y()},t.timeoutMs);let S=0;const R=async l=>{if(l===null)return"";const d=l.getReader(),w=new TextDecoder,h=()=>{d.cancel().catch(()=>{})};let E="";o.signal.addEventListener("abort",h);try{for(;!o.signal.aborted;){const{done:P,value:T}=await d.read();if(P)break;if(S+=T.byteLength,S>r){c.overflowed=!0,y();break}E+=w.decode(T,{stream:!0})}return E+w.decode()}finally{o.signal.removeEventListener("abort",h),d.cancel().catch(()=>{})}};try{const[l,d]=await Promise.allSettled([R(a.stdout),R(a.stderr)]);if(c.overflowed)return new Response(`container "${n}": exec output exceeded ${String(r)} bytes; the process was killed`,{status:413});if(c.timedOut)return new Response(`container "${n}": exec timed out after ${String(t.timeoutMs)}ms`,{status:504});if(l.status==="rejected")throw l.reason;if(d.status==="rejected")throw d.reason;const w=await m;if(v())return new Response(`container "${n}": exec timed out after ${String(t.timeoutMs)}ms`,{status:504});const h=JSON.stringify({code:w,stderr:d.value,stdout:l.value});return new TextEncoder().encode(h).byteLength>r?new Response(`container "${n}": exec output exceeded ${String(r)} bytes once encoded`,{status:413}):new Response(h,{headers:{"content-type":"application/json"}})}finally{clearTimeout($)}},J=async(s,t,e,n)=>{let r;try{r=await t.json()}catch{return new Response(`container "${n}": exec body must be JSON`,{status:400})}return typeof r.command!="string"||r.command.length===0?new Response(`container "${n}": exec requires a non-empty \`command\``,{status:400}):F(s,r,e,n)},z="__lunora_admin__:recordContainerEvent",N="__root__",G=s=>{if(s===null||typeof s!="object")return!1;const t=s;return typeof t.get=="function"&&typeof t.idFromName=="function"},Q=(s,t)=>{const e=j(s,t);return typeof e.getByName=="function"?e.getByName(N):e.get(e.idFromName(N))},W=async(s,t,e)=>{try{const n=s??{},r=n.SHARD;if(!G(r))return;const a=typeof n.LUNORA_ADMIN_TOKEN=="string"?n.LUNORA_ADMIN_TOKEN:void 0;if(!a||a.length===0)return;const o=new Request("https://shard.internal/rpc",{body:JSON.stringify({args:{event:t},functionPath:z}),headers:{authorization:`Bearer ${a}`,"content-type":"application/json"},method:"POST"});await Q(r,e).fetch(o)}catch{}},K=1048576,_=s=>{if(s==null)return null;const{readable:t,writable:e}=new TransformStream(void 0,new ByteLengthQueuingStrategy({highWaterMark:K}));return s.pipeTo(e).catch(()=>{}),t};class Y extends V{#t;#e;constructor(t,e){super(),this.#t=t,this.#e=e}async exitCode(){return this.#e}kill(t){this.#t.kill(t)}resize(t,e){if(this.#t.isPty!==!0||typeof this.#t.resize!="function")throw new TypeError("resize() needs a process spawned with `pty`");this.#t.resize(t,e)}}const X=async(s,t,e,n)=>{const r=t.pty===void 0?void 0:{cols:t.pty.cols??80,rows:t.pty.rows??24};let a;try{a=await s([t.command,...t.args??[]],{...t.cwd===void 0?{}:{cwd:t.cwd},env:{...e,...t.env},...r===void 0?{stderr:"pipe"}:{pty:r},...t.stdin===void 0||t.stdin===!1?{}:{stdin:t.stdin===!0?"pipe":t.stdin},stdout:"pipe"})}catch(c){throw n(),c}const o=A(void 0,t.timeoutMs,()=>new DOMException(`spawn timed out after ${String(t.timeoutMs)}ms`,"TimeoutError"));o.signal?.addEventListener("abort",()=>{a.kill()});const i=a.exitCode.finally(()=>{o.dispose(),n()});return i.catch(()=>{}),{control:new Y(a,i),isPty:a.isPty===!0,pid:a.pid??-1,stderr:_(a.stderr),stdin:t.stdin===!0?a.stdin??null:null,stdout:_(a.stdout)}},f="__lunoraStartOverride",Z=(s,t)=>{if(s===void 0||t===void 0)return s===t;const e=Object.keys(s);return e.length===Object.keys(t).length&&e.every(n=>Object.hasOwn(t,n)&&s[n]===t[n])},q=(s,t)=>Z(s.envVars,t.envVars)&&s.image===t.image&&JSON.stringify(s.instanceType)===JSON.stringify(t.instanceType),tt=(s,t,e)=>{if(C(t))return t;const n=s[t];if(n===void 0){const r=Object.keys(s);throw new u("BAD_REQUEST",`container "${e}": no image named "${t}" — ${r.length===0?"the container declares no images":`declared images: ${r.join(", ")}`}`)}return n},x=500,b=3e4,g="__lunoraHardTimeoutGeneration",D="__lunora",et=/[/\\]/u,nt=s=>{const t=s.indexOf(";");return t===-1?s:s.slice(0,t)},rt=s=>M(s,e=>e.split(et).some(n=>nt(n).toLowerCase()===D)),at=s=>{const t=s instanceof Request?s.url:String(s);return URL.parse(t,"https://container")?.pathname??t},st="cf-container-target-port";class pt extends U{lunoraName;lunoraJurisdiction;lunoraDefaultPort;lunoraHardTimeoutSeconds;lunoraReadiness;lunoraReadyOn;lunoraSecretsStore;lunoraDeclaredEnv;lunoraOverride;lunoraOverrideLoaded=!1;lunoraSecretsStoreResolved;lunoraDurableObjectScheduled;lunoraDefaultSelection;lunoraStops=0;constructor(t,e,n,r,a){const o=I(n,e,r);super(t,e,{defaultPort:n.defaultPort,entrypoint:n.entrypoint?[...n.entrypoint]:void 0,envVars:o,sleepAfter:n.sleepAfter}),this.lunoraDeclaredEnv={...o},n.enableInternet!==void 0&&(this.enableInternet=n.enableInternet),n.requiredPorts!==void 0&&(this.requiredPorts=[...n.requiredPorts]),n.interceptHttps!==void 0&&(this.interceptHttps=n.interceptHttps),n.allowedHosts!==void 0&&(this.allowedHosts=[...n.allowedHosts]),n.deniedHosts!==void 0&&(this.deniedHosts=[...n.deniedHosts]),n.pingEndpoint!==void 0&&(this.pingEndpoint=n.pingEndpoint),n.labels!==void 0&&(this.labels={...n.labels}),this.lunoraName=r??"container",this.lunoraJurisdiction=a,this.lunoraDefaultPort=n.defaultPort,this.lunoraReadyOn=n.readyOn?[...n.readyOn]:[],this.lunoraHardTimeoutSeconds=n.hardTimeout===void 0?void 0:L(n.hardTimeout),this.lunoraSecretsStore=n.secretsStore,this.lunoraDurableObjectScheduled=n.schedulingPolicy==="durable_object",this.lunoraDefaultSelection=n.schedulingPolicy==="durable_object"?{image:n.image,instanceType:n.instanceType}:{}}async lunoraSnapshot(t={}){if(!this.lunoraDurableObjectScheduled)throw new u("BAD_REQUEST",`container "${this.lunoraName}": snapshots need schedulingPolicy "durable_object"`);const{container:e}=this.ctx;if(e?.running!==!0||typeof e.snapshotContainer!="function")throw new u("BAD_REQUEST",`container "${this.lunoraName}": snapshot() needs a running container — start it first`);const{id:n,name:r,size:a}=await e.snapshotContainer({...t.name===void 0?{}:{name:t.name}});return{id:n,size:a,...r===void 0?{}:{name:r}}}async fetch(t){return this.refuseReserved(new URL(t.url).pathname)??super.fetch(t)}async containerFetch(...t){const e=this.refuseReserved(at(t[0]));return e!==void 0?e:(await this.awaitReadinessGate(),super.containerFetch(...t))}async lunoraExec(t){const{pathname:e}=new URL(t.url);if(t.method!=="POST"||e!==O)return new Response(`container "${this.lunoraName}": lunoraExec only serves POST ${O}.`,{status:400});const n=t.headers.get(st),r=n===null?this.defaultPort:Number.parseInt(n,10);if(r===void 0||Number.isNaN(r))return new Response(`container "${this.lunoraName}": exec needs a port — set \`defaultPort\` or exec through \`.port(n)\`.`,{status:400});await this.awaitReadinessGate();const{container:a}=this.ctx;if(typeof a?.exec!="function")return super.containerFetch(t,r);await this.startAndWaitForPorts(r),this.inflightRequests+=1,this.renewActivityTimeout();try{return await J(a.exec.bind(a),t,this.envVars??{},this.lunoraName)}finally{this.decrementInflight()}}async lunoraSpawn(t){const{container:e,release:n}=await this.lunoraAcquire("spawn");return X(e.exec.bind(e),t,this.envVars??{},n)}async startAndWaitForPorts(...t){await this.applyStartEnv(),await this.applyStartSelection(),await this.beforeContainerStart();const e=this.lunoraStops,n=this.beginStart();await super.startAndWaitForPorts(...t),await this.afterContainerStart(n&&this.lunoraStops===e)}async start(t,e){const{image:n,instanceType:r,snapshot:a,...o}=t??{};await this.recordStartOverride({envVars:t?.envVars,image:n,instanceType:r},a),await this.applyStartEnv(),await this.applyStartSelection(a!==void 0),await this.beforeContainerStart();const i=this.lunoraStops,c=this.beginStart();await super.start({...o,...a===void 0?{}:{containerSnapshot:a}},e),await this.afterContainerStart(c&&this.lunoraStops===i)}async destroy(){await super.destroy(),await this.ctx.storage.delete(f),this.lunoraOverride=void 0}async onActivityExpired(){const t=p(this.lunoraName,this.instanceId(),"sleep");this.surfaceInStudioLogs(t),await super.onActivityExpired()}onError(t){const e=p(this.lunoraName,this.instanceId(),"error",t instanceof Error?t.message:String(t));return this.surfaceInStudioLogs(e),super.onError(t)}async onStart(){const t=p(this.lunoraName,this.instanceId(),"start");this.surfaceInStudioLogs(t),await super.onStart()}async onHardTimeoutExpired(t){const e=await this.ctx.storage.get(g);if(t?.generation!==void 0&&t.generation!==e||this.ctx.container?.running!==!0)return;const n=p(this.lunoraName,this.instanceId(),"stop","hard timeout reached");this.surfaceInStudioLogs(n),await this.stop()}async onStop(t){const e=p(this.lunoraName,this.instanceId(),"stop",`${t.reason} (exit ${String(t.exitCode)})`);this.surfaceInStudioLogs(e),this.lunoraReadiness=void 0,this.lunoraStops+=1,await super.onStop(t)}async lunoraAcquire(t){const{container:e}=this.ctx;if(typeof e?.exec!="function")throw new u("NOT_IMPLEMENTED",`container "${this.lunoraName}": ${t}() needs the runtime's native ctx.container.exec(), which this host does not provide`);await this.awaitReadinessGate(),e.running||await this.start(),this.inflightRequests+=1,this.renewActivityTimeout();let n=!1;return{container:e,release:()=>{n||(n=!0,this.decrementInflight())}}}async beforeContainerStart(){}refuseReserved(t){if(rt(t))return new Response(`container "${this.lunoraName}": /${D}/* is reserved for Lunora's own container routes; use exec.`,{status:403})}async afterContainerStart(t){const e=this.lunoraReadiness;if(e!==void 0){await e;return}const n=(async()=>{t||await this.armHardTimeout(),await this.awaitContainerReadiness()})();this.lunoraReadiness=n;try{await n}catch(r){throw this.lunoraReadiness===n&&(this.lunoraReadiness=void 0),r}}beginStart(){const t=this.ctx.container?.running===!0;return t||(this.lunoraReadiness=void 0),t}async awaitReadinessGate(){if(this.lunoraReadyOn.length===0)return;if(this.lunoraReadiness===void 0){const{status:e}=await this.getState();if(e!=="healthy")return;this.lunoraReadiness=this.awaitContainerReadiness()}const t=this.lunoraReadiness;try{await t}catch(e){throw this.lunoraReadiness===t&&(this.lunoraReadiness=void 0),e}}async armHardTimeout(){if(this.lunoraHardTimeoutSeconds===void 0)return;const t=(await this.ctx.storage.get(g)??0)+1;await this.ctx.storage.put(g,t),await this.schedule(this.lunoraHardTimeoutSeconds,"onHardTimeoutExpired",{generation:t})}async resolveSecretsStoreEnv(){const t=this.lunoraSecretsStore;return t===void 0?{}:(this.lunoraSecretsStoreResolved??=(async()=>{const e=this.env,n={};for(const[r,a]of Object.entries(t)){const o=e[a];if(o===void 0||typeof o.get!="function")throw new u("INTERNAL",`container "${this.lunoraName}": secretsStore env "${r}" points at binding "${a}", which is not a Secrets Store binding on the Worker env. Add a \`secrets_store_secrets\` entry binding "${a}".`);const i=await o.get();if(typeof i!="string")throw new TypeError(`container "${this.lunoraName}": Secrets Store binding "${a}" (env "${r}") did not resolve to a string value.`);n[r]=i}return n})().catch(e=>{throw this.lunoraSecretsStoreResolved=void 0,e}),this.lunoraSecretsStoreResolved)}async readOverride(){return this.lunoraOverrideLoaded||(this.lunoraOverride=await this.ctx.storage.get(f),this.lunoraOverrideLoaded=!0),this.lunoraOverride??{}}async recordStartOverride(t,e){const n=t.image!==void 0||t.instanceType!==void 0;if((n||e!==void 0)&&!this.lunoraDurableObjectScheduled)throw new u("BAD_REQUEST",`container "${this.lunoraName}": start({ image | instanceType | snapshot }) needs schedulingPolicy "durable_object" — a default-policy container's image and size are set in lunora/containers.ts`);if(t.image!==void 0&&e!==void 0)throw new u("BAD_REQUEST",`container "${this.lunoraName}": start() takes an image or a snapshot, not both — the snapshot is the filesystem`);t.image!==void 0&&this.resolveImage(t.image);const r=await this.readOverride(),a={...r,...t.envVars===void 0?{}:{envVars:{...t.envVars}},...t.image===void 0?{}:{image:t.image},...t.instanceType===void 0?{}:{instanceType:t.instanceType}};if((this.ctx.container?.running===!0||this.startInFlight!==void 0)&&(e!==void 0||!q({...this.lunoraDefaultSelection,...r},{...this.lunoraDefaultSelection,...a})))throw new u("CONFLICT",`container "${this.lunoraName}": start() on an instance that is already running with a different env, image or size (or a snapshot to restore) — the running container keeps its own. stop() it first (or destroy() it to also drop the override), then start again.`);(n||t.envVars!==void 0)&&(await this.ctx.storage.put(f,a),this.lunoraOverride=a)}async applyStartEnv(){const{envVars:t}=await this.readOverride();this.envVars=t===void 0?{...this.lunoraDeclaredEnv,...await this.resolveSecretsStoreEnv()}:{...t}}async applyStartSelection(t=!1){if(!this.lunoraDurableObjectScheduled)return;const e={...this.lunoraDefaultSelection,...await this.readOverride()};if(e.image===void 0&&!t&&this.ctx.container?.running!==!0)throw new u("BAD_REQUEST",`container "${this.lunoraName}": no image to start — pass start({ image }) or start({ snapshot }), or set a default \`image\` in lunora/containers.ts`);this.instance=e.instanceType,this.image=e.image===void 0?void 0:this.resolveImage(e.image)}resolveImage(t){return tt(this.ctx.container?.images??{},t,this.lunoraName)}async awaitContainerReadiness(){if(this.lunoraReadyOn.length===0)return;const{container:t}=this.ctx;if(t===void 0)return;const e=Date.now()+b;await Promise.all(this.lunoraReadyOn.map(async n=>this.awaitReadinessCheck(t,n,e)))}async awaitReadinessCheck(t,e,n){const r=e.port??this.lunoraDefaultPort;if(r===void 0)throw new u("INTERNAL",`container "${this.lunoraName}": readyOn check "${e.path}" has no port — set the check's \`port\` or the container \`defaultPort\`.`);const a=e.status??200,o=e.path.startsWith("/")?e.path:`/${e.path}`,i=t.getTcpPort(r);for(;;){const c=Math.max(x,n-Date.now()),v=A(void 0,c,()=>new DOMException(`readiness probe timed out after ${String(c)}ms`,"TimeoutError"));try{if((await i.fetch(`http://container${o}`,{signal:v.signal})).status===a)return}catch{}finally{v.dispose()}if(Date.now()>=n)throw new u("INTERNAL",`container "${this.lunoraName}": readiness check "${e.path}" (port ${String(r)}) did not return ${String(a)} within ${String(b)}ms`);await new Promise(m=>{setTimeout(m,x)})}}surfaceInStudioLogs(t){W(this.env,t,this.lunoraJurisdiction).catch(()=>{})}instanceId(){try{const{id:t}=this.ctx;return typeof t?.toString=="function"?t.toString():"unknown"}catch{return"unknown"}}}export{wt as ContainerProxy,pt as LunoraContainer,ft as outboundParams};
|
|
1
|
+
import{LunoraError as u}from"@lunora/errors";import{a as A}from"../packem_shared/abort-deadline-jwbW9Ala.mjs";import{isManagedImage as C,resolveContainerEnvVars as I,parseDurationSeconds as L}from"../packem_shared/DURABLE_OBJECT_POLICY_FORBIDDEN_KEYS-gYi8RBUO.mjs";import{D as H,C as O,p as M}from"../packem_shared/exec-BYLW70sR.mjs";import{a as j}from"../packem_shared/jurisdiction-DtE9s70w.mjs";import{RpcTarget as V}from"cloudflare:workers";import{Container as U}from"../packem_shared/ContainerProxy-CfXjS6ZI.mjs";import{ContainerProxy as wt,outboundParams as ft}from"../packem_shared/ContainerProxy-CfXjS6ZI.mjs";const k="lunora",B=(s,t,e,n)=>({container:s,event:e,instance:t,level:e==="error"?"error":"info",message:n,source:k,ts:Date.now(),type:"container"}),p=(s,t,e,n)=>{const r=B(s,t,e,n),a=JSON.stringify(r);return e==="error"?console.error(a):console.log(a),r},F=async(s,t,e,n)=>{const r=t.maxOutputBytes??H,a=await s([t.command,...t.args??[]],{...t.cwd===void 0?{}:{cwd:t.cwd},env:{...e,...t.env},stderr:"pipe",stdout:"pipe"}),o=new AbortController;let i=!1;const c={overflowed:!1,timedOut:!1},v=()=>c.timedOut,m=a.exitCode.finally(()=>{i=!0}),y=()=>{o.abort(),i||(i=!0,a.kill())},$=t.timeoutMs===void 0?void 0:setTimeout(()=>{c.timedOut=!0,y()},t.timeoutMs);let S=0;const R=async l=>{if(l===null)return"";const d=l.getReader(),w=new TextDecoder,h=()=>{d.cancel().catch(()=>{})};let E="";o.signal.addEventListener("abort",h);try{for(;!o.signal.aborted;){const{done:P,value:T}=await d.read();if(P)break;if(S+=T.byteLength,S>r){c.overflowed=!0,y();break}E+=w.decode(T,{stream:!0})}return E+w.decode()}finally{o.signal.removeEventListener("abort",h),d.cancel().catch(()=>{})}};try{const[l,d]=await Promise.allSettled([R(a.stdout),R(a.stderr)]);if(c.overflowed)return new Response(`container "${n}": exec output exceeded ${String(r)} bytes; the process was killed`,{status:413});if(c.timedOut)return new Response(`container "${n}": exec timed out after ${String(t.timeoutMs)}ms`,{status:504});if(l.status==="rejected")throw l.reason;if(d.status==="rejected")throw d.reason;const w=await m;if(v())return new Response(`container "${n}": exec timed out after ${String(t.timeoutMs)}ms`,{status:504});const h=JSON.stringify({code:w,stderr:d.value,stdout:l.value});return new TextEncoder().encode(h).byteLength>r?new Response(`container "${n}": exec output exceeded ${String(r)} bytes once encoded`,{status:413}):new Response(h,{headers:{"content-type":"application/json"}})}finally{clearTimeout($)}},J=async(s,t,e,n)=>{let r;try{r=await t.json()}catch{return new Response(`container "${n}": exec body must be JSON`,{status:400})}return typeof r.command!="string"||r.command.length===0?new Response(`container "${n}": exec requires a non-empty \`command\``,{status:400}):F(s,r,e,n)},z="__lunora_admin__:recordContainerEvent",N="__root__",G=s=>{if(s===null||typeof s!="object")return!1;const t=s;return typeof t.get=="function"&&typeof t.idFromName=="function"},Q=(s,t)=>{const e=j(s,t);return typeof e.getByName=="function"?e.getByName(N):e.get(e.idFromName(N))},W=async(s,t,e)=>{try{const n=s??{},r=n.SHARD;if(!G(r))return;const a=typeof n.LUNORA_ADMIN_TOKEN=="string"?n.LUNORA_ADMIN_TOKEN:void 0;if(!a||a.length===0)return;const o=new Request("https://shard.internal/rpc",{body:JSON.stringify({args:{event:t},functionPath:z}),headers:{authorization:`Bearer ${a}`,"content-type":"application/json"},method:"POST"});await Q(r,e).fetch(o)}catch{}},K=1048576,_=s=>{if(s==null)return null;const{readable:t,writable:e}=new TransformStream(void 0,new ByteLengthQueuingStrategy({highWaterMark:K}));return s.pipeTo(e).catch(()=>{}),t};class Y extends V{#t;#e;constructor(t,e){super(),this.#t=t,this.#e=e}async exitCode(){return this.#e}kill(t){this.#t.kill(t)}resize(t,e){if(this.#t.isPty!==!0||typeof this.#t.resize!="function")throw new TypeError("resize() needs a process spawned with `pty`");this.#t.resize(t,e)}}const X=async(s,t,e,n)=>{const r=t.pty===void 0?void 0:{cols:t.pty.cols??80,rows:t.pty.rows??24};let a;try{a=await s([t.command,...t.args??[]],{...t.cwd===void 0?{}:{cwd:t.cwd},env:{...e,...t.env},...r===void 0?{stderr:"pipe"}:{pty:r},...t.stdin===void 0||t.stdin===!1?{}:{stdin:t.stdin===!0?"pipe":t.stdin},stdout:"pipe"})}catch(c){throw n(),c}const o=A(void 0,t.timeoutMs,()=>new DOMException(`spawn timed out after ${String(t.timeoutMs)}ms`,"TimeoutError"));o.signal?.addEventListener("abort",()=>{a.kill()});const i=a.exitCode.finally(()=>{o.dispose(),n()});return i.catch(()=>{}),{control:new Y(a,i),isPty:a.isPty===!0,pid:a.pid??-1,stderr:_(a.stderr),stdin:t.stdin===!0?a.stdin??null:null,stdout:_(a.stdout)}},f="__lunoraStartOverride",Z=(s,t)=>{if(s===void 0||t===void 0)return s===t;const e=Object.keys(s);return e.length===Object.keys(t).length&&e.every(n=>Object.hasOwn(t,n)&&s[n]===t[n])},q=(s,t)=>Z(s.envVars,t.envVars)&&s.image===t.image&&JSON.stringify(s.instanceType)===JSON.stringify(t.instanceType),tt=(s,t,e)=>{if(C(t))return t;const n=s[t];if(n===void 0){const r=Object.keys(s);throw new u("BAD_REQUEST",`container "${e}": no image named "${t}" — ${r.length===0?"the container declares no images":`declared images: ${r.join(", ")}`}`)}return n},x=500,b=3e4,g="__lunoraHardTimeoutGeneration",D="__lunora",et=/[/\\]/u,nt=s=>{const t=s.indexOf(";");return t===-1?s:s.slice(0,t)},rt=s=>M(s,e=>e.split(et).some(n=>nt(n).toLowerCase()===D)),at=s=>{const t=s instanceof Request?s.url:String(s);return URL.parse(t,"https://container")?.pathname??t},st="cf-container-target-port";class pt extends U{lunoraName;lunoraJurisdiction;lunoraDefaultPort;lunoraHardTimeoutSeconds;lunoraReadiness;lunoraReadyOn;lunoraSecretsStore;lunoraDeclaredEnv;lunoraOverride;lunoraOverrideLoaded=!1;lunoraSecretsStoreResolved;lunoraDurableObjectScheduled;lunoraDefaultSelection;lunoraStops=0;constructor(t,e,n,r,a){const o=I(n,e,r);super(t,e,{defaultPort:n.defaultPort,entrypoint:n.entrypoint?[...n.entrypoint]:void 0,envVars:o,sleepAfter:n.sleepAfter}),this.lunoraDeclaredEnv={...o},n.enableInternet!==void 0&&(this.enableInternet=n.enableInternet),n.requiredPorts!==void 0&&(this.requiredPorts=[...n.requiredPorts]),n.interceptHttps!==void 0&&(this.interceptHttps=n.interceptHttps),n.allowedHosts!==void 0&&(this.allowedHosts=[...n.allowedHosts]),n.deniedHosts!==void 0&&(this.deniedHosts=[...n.deniedHosts]),n.pingEndpoint!==void 0&&(this.pingEndpoint=n.pingEndpoint),n.labels!==void 0&&(this.labels={...n.labels}),this.lunoraName=r??"container",this.lunoraJurisdiction=a,this.lunoraDefaultPort=n.defaultPort,this.lunoraReadyOn=n.readyOn?[...n.readyOn]:[],this.lunoraHardTimeoutSeconds=n.hardTimeout===void 0?void 0:L(n.hardTimeout),this.lunoraSecretsStore=n.secretsStore,this.lunoraDurableObjectScheduled=n.schedulingPolicy==="durable_object",this.lunoraDefaultSelection=n.schedulingPolicy==="durable_object"?{image:n.image,instanceType:n.instanceType}:{}}async lunoraSnapshot(t={}){if(!this.lunoraDurableObjectScheduled)throw new u("BAD_REQUEST",`container "${this.lunoraName}": snapshots need schedulingPolicy "durable_object"`);const{container:e}=this.ctx;if(e?.running!==!0||typeof e.snapshotContainer!="function")throw new u("BAD_REQUEST",`container "${this.lunoraName}": snapshot() needs a running container — start it first`);const{id:n,name:r,size:a}=await e.snapshotContainer({...t.name===void 0?{}:{name:t.name}});return{id:n,size:a,...r===void 0?{}:{name:r}}}async fetch(t){return this.refuseReserved(new URL(t.url).pathname)??super.fetch(t)}async containerFetch(...t){const e=this.refuseReserved(at(t[0]));return e!==void 0?e:(await this.awaitReadinessGate(),super.containerFetch(...t))}async lunoraExec(t){const{pathname:e}=new URL(t.url);if(t.method!=="POST"||e!==O)return new Response(`container "${this.lunoraName}": lunoraExec only serves POST ${O}.`,{status:400});const n=t.headers.get(st),r=n===null?this.defaultPort:Number.parseInt(n,10);if(r===void 0||Number.isNaN(r))return new Response(`container "${this.lunoraName}": exec needs a port — set \`defaultPort\` or exec through \`.port(n)\`.`,{status:400});await this.awaitReadinessGate();const{container:a}=this.ctx;if(typeof a?.exec!="function")return super.containerFetch(t,r);await this.startAndWaitForPorts(r),this.inflightRequests+=1,this.renewActivityTimeout();try{return await J(a.exec.bind(a),t,this.envVars??{},this.lunoraName)}finally{this.decrementInflight()}}async lunoraSpawn(t){const{container:e,release:n}=await this.lunoraAcquire("spawn");return X(e.exec.bind(e),t,this.envVars??{},n)}async startAndWaitForPorts(...t){await this.applyStartEnv(),await this.applyStartSelection(),await this.beforeContainerStart();const e=this.lunoraStops,n=this.beginStart();await super.startAndWaitForPorts(...t),await this.afterContainerStart(n&&this.lunoraStops===e)}async start(t,e){const{image:n,instanceType:r,snapshot:a,...o}=t??{};await this.recordStartOverride({envVars:t?.envVars,image:n,instanceType:r},a),await this.applyStartEnv(),await this.applyStartSelection(a!==void 0),await this.beforeContainerStart();const i=this.lunoraStops,c=this.beginStart();await super.start({...o,...a===void 0?{}:{containerSnapshot:a}},e),await this.afterContainerStart(c&&this.lunoraStops===i)}async destroy(){await super.destroy(),await this.ctx.storage.delete(f),this.lunoraOverride=void 0}async onActivityExpired(){const t=p(this.lunoraName,this.instanceId(),"sleep");this.surfaceInStudioLogs(t),await super.onActivityExpired()}onError(t){const e=p(this.lunoraName,this.instanceId(),"error",t instanceof Error?t.message:String(t));return this.surfaceInStudioLogs(e),super.onError(t)}async onStart(){const t=p(this.lunoraName,this.instanceId(),"start");this.surfaceInStudioLogs(t),await super.onStart()}async onHardTimeoutExpired(t){const e=await this.ctx.storage.get(g);if(t?.generation!==void 0&&t.generation!==e||this.ctx.container?.running!==!0)return;const n=p(this.lunoraName,this.instanceId(),"stop","hard timeout reached");this.surfaceInStudioLogs(n),await this.stop()}async onStop(t){const e=p(this.lunoraName,this.instanceId(),"stop",`${t.reason} (exit ${String(t.exitCode)})`);this.surfaceInStudioLogs(e),this.lunoraReadiness=void 0,this.lunoraStops+=1,await super.onStop(t)}async lunoraAcquire(t){const{container:e}=this.ctx;if(typeof e?.exec!="function")throw new u("NOT_IMPLEMENTED",`container "${this.lunoraName}": ${t}() needs the runtime's native ctx.container.exec(), which this host does not provide`);await this.awaitReadinessGate(),e.running||await this.start(),this.inflightRequests+=1,this.renewActivityTimeout();let n=!1;return{container:e,release:()=>{n||(n=!0,this.decrementInflight())}}}async beforeContainerStart(){}refuseReserved(t){if(rt(t))return new Response(`container "${this.lunoraName}": /${D}/* is reserved for Lunora's own container routes; use exec.`,{status:403})}async afterContainerStart(t){const e=this.lunoraReadiness;if(e!==void 0){await e;return}const n=(async()=>{t||await this.armHardTimeout(),await this.awaitContainerReadiness()})();this.lunoraReadiness=n;try{await n}catch(r){throw this.lunoraReadiness===n&&(this.lunoraReadiness=void 0),r}}beginStart(){const t=this.ctx.container?.running===!0;return t||(this.lunoraReadiness=void 0),t}async awaitReadinessGate(){if(this.lunoraReadyOn.length===0)return;if(this.lunoraReadiness===void 0){const{status:e}=await this.getState();if(e!=="healthy")return;this.lunoraReadiness=this.awaitContainerReadiness()}const t=this.lunoraReadiness;try{await t}catch(e){throw this.lunoraReadiness===t&&(this.lunoraReadiness=void 0),e}}async armHardTimeout(){if(this.lunoraHardTimeoutSeconds===void 0)return;const t=(await this.ctx.storage.get(g)??0)+1;await this.ctx.storage.put(g,t),await this.schedule(this.lunoraHardTimeoutSeconds,"onHardTimeoutExpired",{generation:t})}async resolveSecretsStoreEnv(){const t=this.lunoraSecretsStore;return t===void 0?{}:(this.lunoraSecretsStoreResolved??=(async()=>{const e=this.env,n={};for(const[r,a]of Object.entries(t)){const o=e[a];if(o===void 0||typeof o.get!="function")throw new u("INTERNAL",`container "${this.lunoraName}": secretsStore env "${r}" points at binding "${a}", which is not a Secrets Store binding on the Worker env. Add a \`secrets_store_secrets\` entry binding "${a}".`);const i=await o.get();if(typeof i!="string")throw new TypeError(`container "${this.lunoraName}": Secrets Store binding "${a}" (env "${r}") did not resolve to a string value.`);n[r]=i}return n})().catch(e=>{throw this.lunoraSecretsStoreResolved=void 0,e}),this.lunoraSecretsStoreResolved)}async readOverride(){return this.lunoraOverrideLoaded||(this.lunoraOverride=await this.ctx.storage.get(f),this.lunoraOverrideLoaded=!0),this.lunoraOverride??{}}async recordStartOverride(t,e){const n=t.image!==void 0||t.instanceType!==void 0;if((n||e!==void 0)&&!this.lunoraDurableObjectScheduled)throw new u("BAD_REQUEST",`container "${this.lunoraName}": start({ image | instanceType | snapshot }) needs schedulingPolicy "durable_object" — a default-policy container's image and size are set in lunora/containers.ts`);if(t.image!==void 0&&e!==void 0)throw new u("BAD_REQUEST",`container "${this.lunoraName}": start() takes an image or a snapshot, not both — the snapshot is the filesystem`);t.image!==void 0&&this.resolveImage(t.image);const r=await this.readOverride(),a={...r,...t.envVars===void 0?{}:{envVars:{...t.envVars}},...t.image===void 0?{}:{image:t.image},...t.instanceType===void 0?{}:{instanceType:t.instanceType}};if((this.ctx.container?.running===!0||this.startInFlight!==void 0)&&(e!==void 0||!q({...this.lunoraDefaultSelection,...r},{...this.lunoraDefaultSelection,...a})))throw new u("CONFLICT",`container "${this.lunoraName}": start() on an instance that is already running with a different env, image or size (or a snapshot to restore) — the running container keeps its own. stop() it first (or destroy() it to also drop the override), then start again.`);(n||t.envVars!==void 0)&&(await this.ctx.storage.put(f,a),this.lunoraOverride=a)}async applyStartEnv(){const{envVars:t}=await this.readOverride();this.envVars=t===void 0?{...this.lunoraDeclaredEnv,...await this.resolveSecretsStoreEnv()}:{...t}}async applyStartSelection(t=!1){if(!this.lunoraDurableObjectScheduled)return;const e={...this.lunoraDefaultSelection,...await this.readOverride()};if(e.image===void 0&&!t&&this.ctx.container?.running!==!0)throw new u("BAD_REQUEST",`container "${this.lunoraName}": no image to start — pass start({ image }) or start({ snapshot }), or set a default \`image\` in lunora/containers.ts`);this.instance=e.instanceType,this.image=e.image===void 0?void 0:this.resolveImage(e.image)}resolveImage(t){return tt(this.ctx.container?.images??{},t,this.lunoraName)}async awaitContainerReadiness(){if(this.lunoraReadyOn.length===0)return;const{container:t}=this.ctx;if(t===void 0)return;const e=Date.now()+b;await Promise.all(this.lunoraReadyOn.map(async n=>this.awaitReadinessCheck(t,n,e)))}async awaitReadinessCheck(t,e,n){const r=e.port??this.lunoraDefaultPort;if(r===void 0)throw new u("INTERNAL",`container "${this.lunoraName}": readyOn check "${e.path}" has no port — set the check's \`port\` or the container \`defaultPort\`.`);const a=e.status??200,o=e.path.startsWith("/")?e.path:`/${e.path}`,i=t.getTcpPort(r);for(;;){const c=Math.max(x,n-Date.now()),v=A(void 0,c,()=>new DOMException(`readiness probe timed out after ${String(c)}ms`,"TimeoutError"));try{if((await i.fetch(`http://container${o}`,{signal:v.signal})).status===a)return}catch{}finally{v.dispose()}if(Date.now()>=n)throw new u("INTERNAL",`container "${this.lunoraName}": readiness check "${e.path}" (port ${String(r)}) did not return ${String(a)} within ${String(b)}ms`);await new Promise(m=>{setTimeout(m,x)})}}surfaceInStudioLogs(t){W(this.env,t,this.lunoraJurisdiction).catch(()=>{})}instanceId(){try{const{id:t}=this.ctx;return typeof t?.toString=="function"?t.toString():"unknown"}catch{return"unknown"}}}export{wt as ContainerProxy,pt as LunoraContainer,ft as outboundParams};
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as ContainerSpawnOptions, b as ContainerSnapshot, c as ContainerProcess, d as ContainerRuntimeInstanceType, e as ContainerSpawnRequest, S as SpawnResult, D as DurableObjectJurisdiction, f as DefaultScheduledContainerConfig, g as DurableObjectScheduledContainerConfig, C as ContainerDefinition, h as ContainerImageSource, N as NormalizedContainerImage } from "./packem_shared/jurisdiction.d-
|
|
2
|
-
export type { B as BuildImageSource, i as ContainerBackupStorage, j as ContainerConfig, k as ContainerConfigBase, l as ContainerInstanceType, m as ContainerNamedImageSource, n as ContainerReadinessCheck, o as ContainerRollout, p as CustomContainerInstanceType, q as NamedContainerInstanceType, R as RegistryImageSource } from "./packem_shared/jurisdiction.d-
|
|
1
|
+
import { a as ContainerSpawnOptions, b as ContainerSnapshot, c as ContainerProcess, d as ContainerRuntimeInstanceType, e as ContainerSpawnRequest, S as SpawnResult, D as DurableObjectJurisdiction, f as DefaultScheduledContainerConfig, g as DurableObjectScheduledContainerConfig, C as ContainerDefinition, h as ContainerImageSource, N as NormalizedContainerImage } from "./packem_shared/jurisdiction.d-BwpauCoI.mjs";
|
|
2
|
+
export type { B as BuildImageSource, i as ContainerBackupStorage, j as ContainerConfig, k as ContainerConfigBase, l as ContainerInstanceType, m as ContainerNamedImageSource, n as ContainerReadinessCheck, o as ContainerRollout, p as CustomContainerInstanceType, q as NamedContainerInstanceType, R as RegistryImageSource } from "./packem_shared/jurisdiction.d-BwpauCoI.mjs";
|
|
3
3
|
import { b as ContainerBackupOptions, C as ContainerFileOptions, c as ContainerMountRequest, a as ContainerFileContent, d as ContainerSandboxControls } from "./packem_shared/sandbox-types.d-DXlHnsEg.mjs";
|
|
4
4
|
export type { e as ContainerFiles, f as ContainerMountCredentials } from "./packem_shared/sandbox-types.d-DXlHnsEg.mjs";
|
|
5
5
|
import { DirectoryBackupRecord, S3MountInspection, SandboxDirectoryEntry, SandboxFileStat } from '@cloudflare/sandbox';
|
|
@@ -532,7 +532,32 @@ declare const isContainerDefinition: (value: unknown) => value is ContainerDefin
|
|
|
532
532
|
* credential it was promised yields far worse errors downstream.
|
|
533
533
|
*/
|
|
534
534
|
declare const resolveContainerEnvVariables: (definition: ContainerDefinition, workerEnv: Record<string, unknown>, exportName?: string) => Record<string, string>;
|
|
535
|
-
|
|
535
|
+
/**
|
|
536
|
+
* Cloudflare's documented bounds for a custom container instance type
|
|
537
|
+
* (https://developers.cloudflare.com/containers/platform-details/limits/):
|
|
538
|
+
* 1–4 vCPU, up to 12 GiB memory, up to 20 GB disk at any memory size, and at
|
|
539
|
+
* least 3 GiB memory per vCPU. Below 1 vCPU, Cloudflare points at the named
|
|
540
|
+
* `lite` / `basic` instance types instead.
|
|
541
|
+
*
|
|
542
|
+
* The one copy of these numbers. `defineContainer` checks a `durable_object`
|
|
543
|
+
* container's runtime size against it, and `@lunora/config`'s wrangler validator
|
|
544
|
+
* checks `containers[].instance_type` against it (config already depends on this
|
|
545
|
+
* package, so the import runs one way). Pure data, Node-safe.
|
|
546
|
+
*/
|
|
547
|
+
/** Inclusive bounds for one dimension of a custom instance type. */
|
|
548
|
+
interface InstanceDimensionLimits {
|
|
549
|
+
readonly max: number;
|
|
550
|
+
readonly min: number;
|
|
551
|
+
}
|
|
552
|
+
/** Per-dimension bounds, keyed by the `CustomContainerInstanceType` field names. */
|
|
553
|
+
declare const CUSTOM_INSTANCE_TYPE_LIMITS: Readonly<{
|
|
554
|
+
diskMb: InstanceDimensionLimits;
|
|
555
|
+
memoryMib: InstanceDimensionLimits;
|
|
556
|
+
vcpu: InstanceDimensionLimits;
|
|
557
|
+
}>;
|
|
558
|
+
/** The memory floor per vCPU: 3 GiB. */
|
|
559
|
+
declare const CUSTOM_INSTANCE_MIN_MEMORY_MIB_PER_VCPU = 3072;
|
|
560
|
+
export { CONTAINER_EXEC_PATH, CUSTOM_INSTANCE_MIN_MEMORY_MIB_PER_VCPU, CUSTOM_INSTANCE_TYPE_LIMITS,
|
|
536
561
|
/**
|
|
537
562
|
* `@lunora/container` — Cloudflare Containers for Lunora.
|
|
538
563
|
*
|
|
@@ -642,7 +667,7 @@ type ContainerTestHandler, DURABLE_OBJECT_POLICY_FORBIDDEN_KEYS, type DefaultSch
|
|
|
642
667
|
* (which pulls in `@cloudflare/containers` → `cloudflare:workers`) lives
|
|
643
668
|
* behind the `@lunora/container/do` subpath.
|
|
644
669
|
*/
|
|
645
|
-
type DurableObjectJurisdiction, type DurableObjectScheduledContainerConfig,
|
|
670
|
+
type DurableObjectJurisdiction, type DurableObjectScheduledContainerConfig, type InstanceDimensionLimits,
|
|
646
671
|
/**
|
|
647
672
|
* `@lunora/container` — Cloudflare Containers for Lunora.
|
|
648
673
|
*
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as ContainerSpawnOptions, b as ContainerSnapshot, c as ContainerProcess, d as ContainerRuntimeInstanceType, e as ContainerSpawnRequest, S as SpawnResult, D as DurableObjectJurisdiction, f as DefaultScheduledContainerConfig, g as DurableObjectScheduledContainerConfig, C as ContainerDefinition, h as ContainerImageSource, N as NormalizedContainerImage } from "./packem_shared/jurisdiction.d-
|
|
2
|
-
export type { B as BuildImageSource, i as ContainerBackupStorage, j as ContainerConfig, k as ContainerConfigBase, l as ContainerInstanceType, m as ContainerNamedImageSource, n as ContainerReadinessCheck, o as ContainerRollout, p as CustomContainerInstanceType, q as NamedContainerInstanceType, R as RegistryImageSource } from "./packem_shared/jurisdiction.d-
|
|
1
|
+
import { a as ContainerSpawnOptions, b as ContainerSnapshot, c as ContainerProcess, d as ContainerRuntimeInstanceType, e as ContainerSpawnRequest, S as SpawnResult, D as DurableObjectJurisdiction, f as DefaultScheduledContainerConfig, g as DurableObjectScheduledContainerConfig, C as ContainerDefinition, h as ContainerImageSource, N as NormalizedContainerImage } from "./packem_shared/jurisdiction.d-BwpauCoI.js";
|
|
2
|
+
export type { B as BuildImageSource, i as ContainerBackupStorage, j as ContainerConfig, k as ContainerConfigBase, l as ContainerInstanceType, m as ContainerNamedImageSource, n as ContainerReadinessCheck, o as ContainerRollout, p as CustomContainerInstanceType, q as NamedContainerInstanceType, R as RegistryImageSource } from "./packem_shared/jurisdiction.d-BwpauCoI.js";
|
|
3
3
|
import { b as ContainerBackupOptions, C as ContainerFileOptions, c as ContainerMountRequest, a as ContainerFileContent, d as ContainerSandboxControls } from "./packem_shared/sandbox-types.d-DXlHnsEg.js";
|
|
4
4
|
export type { e as ContainerFiles, f as ContainerMountCredentials } from "./packem_shared/sandbox-types.d-DXlHnsEg.js";
|
|
5
5
|
import { DirectoryBackupRecord, S3MountInspection, SandboxDirectoryEntry, SandboxFileStat } from '@cloudflare/sandbox';
|
|
@@ -532,7 +532,32 @@ declare const isContainerDefinition: (value: unknown) => value is ContainerDefin
|
|
|
532
532
|
* credential it was promised yields far worse errors downstream.
|
|
533
533
|
*/
|
|
534
534
|
declare const resolveContainerEnvVariables: (definition: ContainerDefinition, workerEnv: Record<string, unknown>, exportName?: string) => Record<string, string>;
|
|
535
|
-
|
|
535
|
+
/**
|
|
536
|
+
* Cloudflare's documented bounds for a custom container instance type
|
|
537
|
+
* (https://developers.cloudflare.com/containers/platform-details/limits/):
|
|
538
|
+
* 1–4 vCPU, up to 12 GiB memory, up to 20 GB disk at any memory size, and at
|
|
539
|
+
* least 3 GiB memory per vCPU. Below 1 vCPU, Cloudflare points at the named
|
|
540
|
+
* `lite` / `basic` instance types instead.
|
|
541
|
+
*
|
|
542
|
+
* The one copy of these numbers. `defineContainer` checks a `durable_object`
|
|
543
|
+
* container's runtime size against it, and `@lunora/config`'s wrangler validator
|
|
544
|
+
* checks `containers[].instance_type` against it (config already depends on this
|
|
545
|
+
* package, so the import runs one way). Pure data, Node-safe.
|
|
546
|
+
*/
|
|
547
|
+
/** Inclusive bounds for one dimension of a custom instance type. */
|
|
548
|
+
interface InstanceDimensionLimits {
|
|
549
|
+
readonly max: number;
|
|
550
|
+
readonly min: number;
|
|
551
|
+
}
|
|
552
|
+
/** Per-dimension bounds, keyed by the `CustomContainerInstanceType` field names. */
|
|
553
|
+
declare const CUSTOM_INSTANCE_TYPE_LIMITS: Readonly<{
|
|
554
|
+
diskMb: InstanceDimensionLimits;
|
|
555
|
+
memoryMib: InstanceDimensionLimits;
|
|
556
|
+
vcpu: InstanceDimensionLimits;
|
|
557
|
+
}>;
|
|
558
|
+
/** The memory floor per vCPU: 3 GiB. */
|
|
559
|
+
declare const CUSTOM_INSTANCE_MIN_MEMORY_MIB_PER_VCPU = 3072;
|
|
560
|
+
export { CONTAINER_EXEC_PATH, CUSTOM_INSTANCE_MIN_MEMORY_MIB_PER_VCPU, CUSTOM_INSTANCE_TYPE_LIMITS,
|
|
536
561
|
/**
|
|
537
562
|
* `@lunora/container` — Cloudflare Containers for Lunora.
|
|
538
563
|
*
|
|
@@ -642,7 +667,7 @@ type ContainerTestHandler, DURABLE_OBJECT_POLICY_FORBIDDEN_KEYS, type DefaultSch
|
|
|
642
667
|
* (which pulls in `@cloudflare/containers` → `cloudflare:workers`) lives
|
|
643
668
|
* behind the `@lunora/container/do` subpath.
|
|
644
669
|
*/
|
|
645
|
-
type DurableObjectJurisdiction, type DurableObjectScheduledContainerConfig,
|
|
670
|
+
type DurableObjectJurisdiction, type DurableObjectScheduledContainerConfig, type InstanceDimensionLimits,
|
|
646
671
|
/**
|
|
647
672
|
* `@lunora/container` — Cloudflare Containers for Lunora.
|
|
648
673
|
*
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createContainerContext as
|
|
1
|
+
import{createContainerContext as r,createContainerTestContext as t,getContainer as a}from"./packem_shared/createContainerContext-p6QxWjsm.mjs";import{DURABLE_OBJECT_POLICY_FORBIDDEN_KEYS as i,containerBindingName as C,containerBuildTag as _,containerClassName as E,defineContainer as s,isCloudflareRegistryDigest as I,isContainerDefinition as T,isManagedImage as N,normalizeContainerImage as m,resolveContainerEnvVars as g}from"./packem_shared/DURABLE_OBJECT_POLICY_FORBIDDEN_KEYS-gYi8RBUO.mjs";import{C as f}from"./packem_shared/exec-BYLW70sR.mjs";import{CUSTOM_INSTANCE_MIN_MEMORY_MIB_PER_VCPU as l,CUSTOM_INSTANCE_TYPE_LIMITS as x}from"./packem_shared/CUSTOM_INSTANCE_MIN_MEMORY_MIB_PER_VCPU-D8Hy9Rxv.mjs";export{f as CONTAINER_EXEC_PATH,l as CUSTOM_INSTANCE_MIN_MEMORY_MIB_PER_VCPU,x as CUSTOM_INSTANCE_TYPE_LIMITS,i as DURABLE_OBJECT_POLICY_FORBIDDEN_KEYS,C as containerBindingName,_ as containerBuildTag,E as containerClassName,r as createContainerContext,t as createContainerTestContext,s as defineContainer,a as getContainer,I as isCloudflareRegistryDigest,T as isContainerDefinition,N as isManagedImage,m as normalizeContainerImage,g as resolveContainerEnvVars};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e=Object.freeze({diskMb:Object.freeze({max:2e4,min:1}),memoryMib:Object.freeze({max:12288,min:1}),vcpu:Object.freeze({max:4,min:1})}),M=3072;export{M as CUSTOM_INSTANCE_MIN_MEMORY_MIB_PER_VCPU,e as CUSTOM_INSTANCE_TYPE_LIMITS};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{LunoraError as g}from"@lunora/errors";import{CUSTOM_INSTANCE_TYPE_LIMITS as E,CUSTOM_INSTANCE_MIN_MEMORY_MIB_PER_VCPU as l}from"./CUSTOM_INSTANCE_MIN_MEMORY_MIB_PER_VCPU-D8Hy9Rxv.mjs";const c=new Set(["basic","lite","standard-1","standard-2","standard-3","standard-4"]),u=new Set(["lite","standard-1","standard-2","standard-3","standard-4"]),y="cloudflare/",T=e=>e.startsWith(y),C=["maxInstances","rollout"],p=100,f=128,a=/^[A-Z_]\w*$/i,h=/^\d+[smh]$/,v={h:3600,m:60,s:1},U=e=>{if(typeof e=="number")return Math.floor(e);if(h.exec(e)===null)throw new TypeError(`Invalid duration "${e}" — expected a number of seconds or "<n>[smh]"`);return Number(e.slice(0,-1))*(v[e.slice(-1)]??1)},$=e=>{const t=e.endsWith("/")?e.slice(0,-1):e,n=t.lastIndexOf("/");return n===-1?t:t.slice(n+1)},S=e=>{const t=e.lastIndexOf("/");return t===-1?".":e.slice(0,t)||"/"},L=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($(e).startsWith("Dockerfile"))return{buildContext:S(e),dockerfilePath:e,kind:"dockerfile"};const t=e.endsWith("/")?e.slice(0,-1):e;return{buildContext:t,dockerfilePath:`${t}/Dockerfile`,kind:"dockerfile"}},B=e=>`${e.charAt(0).toUpperCase()}${e.slice(1)}Container`,H=e=>`CONTAINER_${e.replaceAll(/(?<=[a-z0-9])(?=[A-Z])/g,"_").toUpperCase()}`,W=e=>`lunora-${e.replaceAll(/(?<=[a-z0-9])(?=[A-Z])/g,"-").toLowerCase()}:build`,b=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")},N=(e,t,n)=>{for(const[r,o]of Object.entries(e.secretsStore??{})){if(!a.test(r))throw new TypeError(`defineContainer: secretsStore env name "${r}" is not a valid environment variable name`);if(typeof o!="string"||o.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`)}},I=e=>{for(const r of Object.keys(e.env??{}))if(!a.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(!a.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(!a.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`)}N(e,t,n)},d=(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)})`)},k=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&&d(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)})`)}},m=(e,t)=>{if(e!==void 0){if(typeof e=="string"){if(!h.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)})`)}},P=e=>{const{backups:t}=e;if(t!==void 0){if(e.sandbox!==!0)throw new TypeError("defineContainer: `backups` needs `sandbox: true` — directory backups are a Sandbox SDK helper");if(typeof t.bucket!="string"||t.bucket.trim().length===0)throw new TypeError("defineContainer: `backups.bucket` must name an R2 bucket binding");if(t.prefix!==void 0&&(typeof t.prefix!="string"||t.prefix.length>0&&!t.prefix.endsWith("/")))throw new TypeError("defineContainer: `backups.prefix` must be a string ending in `/`, or omitted")}},_=e=>{for(const t of["allowedHosts","deniedHosts"])if(e[t]?.some(r=>typeof r!="string"||r.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")},A=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)d(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(_(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");if(k(e),e.sandbox!==void 0&&typeof e.sandbox!="boolean")throw new TypeError("defineContainer: `sandbox` must be a boolean, or omitted");P(e)},O=e=>typeof e=="string"&&e.startsWith("registry.cloudflare.com/")&&e.includes("@sha256:"),M=(e,t)=>{if(e.length===0||e.length>f)throw new TypeError(`defineContainer: image name "${e}" must be 1–${String(f)} characters`);if(typeof t=="string"){b(t);return}if(!O(t.registry))throw new TypeError(`defineContainer: \`images["${e}"].registry\` must be a digest-pinned (@sha256) reference under registry.cloudflare.com — push other registries' images there first`)},x=e=>{for(const[r,{max:o,min:s}]of Object.entries(E)){const i=e[r];if(typeof i!="number"||Number.isNaN(i)||i<s||i>o){const w=r==="vcpu"?' — below 1 vCPU use a named runtime size such as "lite" or "standard-1"':"";throw new TypeError(`defineContainer: a custom \`instanceType\` under schedulingPolicy "durable_object" needs ${r} in ${String(s)}–${String(o)} (got ${String(i)})${w}`)}}const{memoryMib:t,vcpu:n}=e;if(t<n*l)throw new TypeError(`defineContainer: a custom \`instanceType\` needs ≥ ${String(l)} MiB memory per vCPU (got ${String(t)} MiB for ${String(n)} vCPU)`)},D=e=>{for(const r of C)if(e[r]!==void 0)throw new TypeError(`defineContainer: \`${r}\` is not supported with schedulingPolicy "durable_object"`);const t=Object.entries(e.images??{});if(t.length>p)throw new TypeError(`defineContainer: \`images\` holds at most ${String(p)} entries (got ${String(t.length)})`);for(const[r,o]of t)M(r,o);if(e.image!==void 0&&!Object.hasOwn(e.images??{},e.image)&&!T(e.image))throw new TypeError(`defineContainer: \`image\` "${e.image}" must name an entry of \`images\` or a Cloudflare-managed "${y}…" image`);const{instanceType:n}=e;if(typeof n=="string"&&!u.has(n))throw new TypeError(`defineContainer: \`instanceType\` "${n}" is not a runtime size — use one of ${[...u].join(", ")}, or a full { vcpu, memoryMib, diskMb } object`);typeof n=="object"&&x(n)},j=e=>{if(e.images!==void 0)throw new TypeError('defineContainer: `images` needs schedulingPolicy "durable_object"');b(e.image);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)})`);const n=e.rollout?.gracePeriodSeconds;if(n!==void 0&&(!Number.isInteger(n)||n<0))throw new TypeError(`defineContainer: \`rollout.gracePeriodSeconds\` must be a non-negative integer (got ${String(n)})`);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"&&!c.has(e.instanceType))throw new TypeError(`defineContainer: unknown \`instanceType\` "${e.instanceType}" — use one of ${[...c].join(", ")}, or a custom { vcpu, memoryMib, diskMb } object`)},q=e=>{const t=e.schedulingPolicy;if(e.schedulingPolicy==="durable_object")D(e);else if(t===void 0||t==="default")j(e);else throw new TypeError(`defineContainer: unknown \`schedulingPolicy\` ${JSON.stringify(t)} — use "default" or "durable_object"`);return e.defaultPort!==void 0&&d(e.defaultPort,"defaultPort"),m(e.sleepAfter,"sleepAfter"),m(e.hardTimeout,"hardTimeout"),I(e),A(e),{...e,isLunoraContainer:!0}},z=e=>typeof e=="object"&&e!==null&&e.isLunoraContainer===!0,Y=(e,t,n)=>{const r={...e.env};for(const o of e.secrets??[]){const s=t[o];if(typeof s!="string"){const i=n===void 0?"container":`container "${n}"`;throw new g("INTERNAL",`${i}: declared secret "${o}" is not set on the Worker environment. Add it to .dev.vars for local dev and run \`wrangler secret put ${o}\` for production.`)}r[o]=s}return r};export{C as DURABLE_OBJECT_POLICY_FORBIDDEN_KEYS,H as containerBindingName,W as containerBuildTag,B as containerClassName,q as defineContainer,O as isCloudflareRegistryDigest,z as isContainerDefinition,T as isManagedImage,L as normalizeContainerImage,U as parseDurationSeconds,Y as resolveContainerEnvVars};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{LunoraError as N}from"@lunora/errors";import{e as I,p as J,a as x,r as G}from"./exec-BYLW70sR.mjs";import{S as K}from"./sampling-D21rpu_s.mjs";import{containerBindingName as $}from"./DURABLE_OBJECT_POLICY_FORBIDDEN_KEYS-gYi8RBUO.mjs";import{a as Z}from"./jurisdiction-DtE9s70w.mjs";import"./CUSTOM_INSTANCE_MIN_MEMORY_MIB_PER_VCPU-D8Hy9Rxv.mjs";const ee=(e,t)=>{if(e.length===0)throw new TypeError("spawn: `command` must be a non-empty string");return{command:e,...t.args===void 0?{}:{args:[...t.args]},...t.cwd===void 0?{}:{cwd:t.cwd},...t.env===void 0?{}:{env:{...t.env}},...t.pty===void 0?{}:{pty:{...t.pty}},...t.stdin===void 0?{}:{stdin:t.stdin},...t.timeoutMs===void 0?{}:{timeoutMs:t.timeoutMs}}},te=(e,t)=>{const{control:r}=e,o=r.exitCode();if(t!==void 0){const n=()=>{Promise.resolve(r.kill()).catch(()=>{})};t.aborted?n():(t.addEventListener("abort",n,{once:!0}),o.finally(()=>{t.removeEventListener("abort",n)}).catch(()=>{}))}return{exitCode:o,isPty:e.isPty,kill:async n=>{await r.kill(n)},pid:e.pid,resize:async(n,s)=>{await r.resize(n,s)},stderr:e.stderr,stdin:e.stdin,stdout:e.stdout}},re=1048576,oe=80,ne=24,B=(e,t)=>{const r=Number(e.searchParams.get(t));return Number.isInteger(r)&&r>0?r:void 0},se={pair:()=>{const{WebSocketPair:e}=globalThis;if(e===void 0)throw new TypeError("terminal(): WebSocketPair is not available — open a terminal from a Worker or Durable Object");const t=new e;return[t[0],t[1]]},upgrade:e=>new Response(void 0,{status:101,webSocket:e})},ae=e=>{try{const t=JSON.parse(e);return Number.isInteger(t.cols)&&Number.isInteger(t.rows)?{cols:t.cols,rows:t.rows}:void 0}catch{return}},ie=async e=>{if(e instanceof ArrayBuffer)return new Uint8Array(e);if(ArrayBuffer.isView(e))return new Uint8Array(e.buffer,e.byteOffset,e.byteLength);if(e instanceof Blob)return new Uint8Array(await e.arrayBuffer())},ce=e=>e instanceof ArrayBuffer||ArrayBuffer.isView(e)?e.byteLength:e instanceof Blob?e.size:0,le=async(e,t)=>{const r=e.stdout?.getReader();try{for(;r!==void 0;){const{done:n,value:s}=await r.read();if(n)break;t.send(s)}const o=await e.exitCode;t.close(1e3,`exited with code ${String(o)}`)}catch{t.close(1011,"terminal stream failed")}},de=async(e,t,r={},o=se)=>{if(t.headers.get("upgrade")?.toLowerCase()!=="websocket")return new Response("terminal(): expected a WebSocket upgrade request",{status:426});const n=new URL(t.url),{command:s="sh",cols:c,rows:a,...i}=r,u=await e(s,{...i,env:{TERM:"xterm-256color",...i.env},pty:{cols:c??B(n,"cols")??oe,rows:a??B(n,"rows")??ne},stdin:!0}),[v,m]=o.pair(),d=u.stdin?.getWriter(),f=()=>{u.kill().catch(()=>{}),d?.close().catch(()=>{})};let p=Promise.resolve();const h=y=>{p=p.then(y).catch(f)};let R=0;const S=(y,g)=>{if(R+=y,R>re){m.close(1009,"terminal input backlog too large"),f();return}h(async()=>{try{const E=await g;E!==void 0&&await d?.write(E)}finally{R-=y}})};return m.accept(),m.addEventListener("message",({data:y})=>{if(typeof y!="string"){S(ce(y),ie(y));return}const g=ae(y);if(g===void 0){const E=new TextEncoder().encode(y);S(E.byteLength,Promise.resolve(E))}else h(async()=>u.resize(g.cols,g.rows).catch(()=>{}))}),m.addEventListener("close",f),m.addEventListener("error",f),le(u,m).catch(()=>{}),o.upgrade(v)},ue={EEXIST:"CONFLICT",ENOENT:"NOT_FOUND",ENOTEMPTY:"CONFLICT"},w=(e,t,r)=>new N(ue[e]??"BAD_REQUEST",`test container: ${t} ${r} failed with ${e}`,{data:{errno:e,operation:t,path:r}}),T=(e,t={})=>{const r=e.startsWith("/")?e:`${t.cwd??""}/${e}`;if(!r.startsWith("/"))throw new TypeError(`test container: "${e}" is relative and no absolute \`cwd\` was given`);const o=[];for(const n of r.split("/"))n===".."?o.pop():n!==""&&n!=="."&&o.push(n);return`/${o.join("/")}`},C=e=>e.slice(0,e.lastIndexOf("/"))||"/",fe=async e=>typeof e=="string"?new TextEncoder().encode(e):e instanceof ArrayBuffer?Uint8Array.from(new Uint8Array(e)):ArrayBuffer.isView(e)?new Uint8Array(e.buffer.slice(e.byteOffset,e.byteOffset+e.byteLength)):new Uint8Array(await new Response(e).arrayBuffer()),L=(e,t)=>{const r=new Date;return{accessedAt:r,changedAt:r,gid:0,modifiedAt:r,mode:e==="directory"?16877:33188,size:BigInt(t),type:e,uid:0}},ye=()=>{const e=new Map,t=new Set(["/"]),r=s=>e.has(s)||t.has(s),o=s=>[...e.keys(),...t].filter(c=>c!==s&&C(c)===s).toSorted((c,a)=>c.localeCompare(a)),n=(s,c)=>{const a=C(s);if(e.has(a))throw w("ENOTDIR",c,s);if(!t.has(a))throw w("ENOENT",c,s)};return{mkdir:async(s,c={})=>{const a=T(s,c);if(c.recursive===!0){let i="";for(const u of a.split("/").filter(Boolean)){if(i=`${i}/${u}`,e.has(i))throw w("ENOTDIR","mkdir",i);t.add(i)}return}if(r(a))throw w("EEXIST","mkdir",a);n(a,"mkdir"),t.add(a)},readDirectory:async(s,c)=>{const a=T(s,c);if(e.has(a))throw w("ENOTDIR","readDirectory",a);if(!t.has(a))throw w("ENOENT","readDirectory",a);return o(a).map(i=>({name:i.slice(i.lastIndexOf("/")+1),type:t.has(i)?"directory":"file"}))},readFile:async(s,c)=>{const a=T(s,c),i=e.get(a);if(i===void 0)throw w(t.has(a)?"EISDIR":"ENOENT","readFile",a);return new Response(new Uint8Array(i))},remove:async(s,c={})=>{const a=T(s,c);if(!e.delete(a)){if(!t.has(a)){if(c.force!==!0)throw w("ENOENT","remove",a);return}if(c.recursive!==!0)throw w(o(a).length>0?"ENOTEMPTY":"EISDIR","remove",a);for(const i of[...e.keys(),...t])(i===a||i.startsWith(`${a}/`))&&(e.delete(i),t.delete(i));t.add("/")}},rename:async(s,c,a)=>{const i=T(s,a),u=T(c,a);if(!r(i))throw w("ENOENT","rename",i);if(n(u,"rename"),u.startsWith(`${i}/`))throw w("EINVAL","rename",i);const v=[...e],m=[...t];for(const[d,f]of v)(d===i||d.startsWith(`${i}/`))&&(e.delete(d),e.set(u+d.slice(i.length),f));for(const d of m)(d===i||d.startsWith(`${i}/`))&&(t.delete(d),t.add(u+d.slice(i.length)))},stat:async(s,c)=>{const a=T(s,c),i=e.get(a);if(i!==void 0)return L("file",i.byteLength);if(t.has(a))return L("directory",4096);throw w("ENOENT","stat",a)},writeFile:async(s,c,a)=>{const i=T(s,a);if(t.has(i))throw w("EISDIR","writeFile",i);n(i,"writeFile"),e.set(i,await fe(c))}}},k=3,U=3e4,me=3,we=500,he="cf-container-target-port",M="__lunora",Ee=e=>{const t=e.indexOf(";");return t===-1?e:e.slice(0,t)},ve=e=>{const t=e.split("/").find(r=>r!=="")??"";return Ee(t).toLowerCase()},H=(e,t)=>{const r=typeof e=="string"?e:e.url,o=URL.parse(r.startsWith("/")?`http://container${r}`:r);if(o!==null&&J(o.pathname,n=>ve(n)===M))throw new N("BAD_REQUEST",`${t}: \`/${M}/*\` is reserved for Lunora's own container routes and cannot be reached with \`fetch\`. Use \`exec\` to run a command.`)},W=(e,t)=>{if(typeof e!="string"){const o=new Request(e,t);return o.headers.delete(x),[o,void 0]}if(t?.headers===void 0)return[e,t];const r=new Headers(t.headers);return r.delete(x),[e,{...t,headers:r}]},z=(e,t,r,o)=>{const n=typeof e=="string"&&e.startsWith("/")?new Request(`http://container${e}`,t):new Request(e,t);return r!==void 0&&n.headers.set(he,String(r)),o?.traceparent!==void 0&&n.headers.set("traceparent",o.traceparent),o?.sampleErrors!==void 0&&n.headers.set(K,o.sampleErrors?"1":"0"),n},j=async e=>{e<=0||await new Promise(t=>{setTimeout(t,e)})},pe=/no container instance|not listening|try again later|rate.?limit|provision/i,Te="no Container instance available",Re="Failed to start container:",ge=1024,Ae=async e=>(await G(e.clone().body,ge)).text,X=e=>e instanceof Error&&pe.test(e.message),V=async e=>{if(e.status===429)return!0;if(e.status!==500&&e.status!==503)return!1;try{const t=await Ae(e);return t.includes(Te)||t.startsWith(Re)}catch{return!1}},O=e=>`ctx.containers.${e.exportName}`,_=(e,t,r={},o,n)=>{const s=Math.max(1,r.attempts??me),c=r.backoffMs??we,a=r.maxBackoffMs??U,i=async(u,v)=>{const m=typeof u=="string"?s:1;let d;for(let f=0;f<m;f+=1){const p=f===m-1;f>0&&await j(Math.min(c*2**(f-1),a));try{const h=await e(z(u,v,o,n),v?.signal);if(p||!await V(h))return h}catch(h){if(d=h,p||!X(h))throw h}}throw d instanceof Error?d:new Error("ctx.containers: cold-start retry exhausted")};return{exec:I(i,t),fetch:async(u,v)=>(H(u,t),i(...W(u,v))),port:u=>_(e,t,r,u,n)}},Se=async(e,t)=>{if(t.aborted)throw e.catch(()=>{}),t.reason;const r=new AbortController,o=new Promise((n,s)=>{t.addEventListener("abort",()=>{s(t.reason)},{once:!0,signal:r.signal})});try{return await Promise.race([e,o])}finally{r.abort(),e.catch(()=>{})}},F=async(e,t,r)=>{if(!t.headers.has(x))return e.fetch(t);if(typeof e.lunoraExec!="function")throw new TypeError("ctx.containers: this container DO does not expose lunoraExec() — is @lunora/container/do up to date?");t.headers.delete(x);const o=e.lunoraExec(new Request(t.url,{body:await t.text(),headers:t.headers,method:t.method}));return r?Se(o,r):o},Y=(e,t,r,o,n)=>_(async(s,c)=>F(e.get(e.idFromName(t)),s,c),r,o,void 0,n),Ne=new Set(["lunoraBackup","lunoraDeleteBackup","lunoraInspectMount","lunoraMkdir","lunoraMount","lunoraReadDirectory","lunoraReadFile","lunoraRemove","lunoraRename","lunoraRestore","lunoraStat","lunoraUnmount","lunoraWriteFile"]),l=async(e,t,r,...o)=>{const n=e[t];if(typeof n!="function")throw Ne.has(t)?new N("BAD_REQUEST",`ctx.containers: the "${r}" container DO does not expose ${t}() — sandbox helpers need \`sandbox: true\` in its defineContainer() definition`):new TypeError(`ctx.containers: the "${r}" container DO does not expose ${t}() — is @lunora/container/do up to date?`);return n(...o)},Oe=(e,t)=>({backup:async(r,o)=>l(e(),"lunoraBackup",t,r,o),deleteBackup:async r=>l(e(),"lunoraDeleteBackup",t,r),files:{mkdir:async(r,o)=>l(e(),"lunoraMkdir",t,r,o),readDirectory:async(r,o)=>l(e(),"lunoraReadDirectory",t,r,o),readFile:async(r,o)=>l(e(),"lunoraReadFile",t,r,o),remove:async(r,o)=>l(e(),"lunoraRemove",t,r,o),rename:async(r,o,n)=>l(e(),"lunoraRename",t,r,o,n),stat:async(r,o)=>l(e(),"lunoraStat",t,r,o),writeFile:async(r,o,n)=>l(e(),"lunoraWriteFile",t,r,o,n)},inspectMount:async r=>l(e(),"lunoraInspectMount",t,r),mount:async r=>l(e(),"lunoraMount",t,r),restore:async(r,o)=>l(e(),"lunoraRestore",t,r,o),unmount:async r=>l(e(),"lunoraUnmount",t,r)}),De=(e,t)=>({allow:async r=>l(e(),"allowHost",t,r),deny:async r=>l(e(),"denyHost",t,r),removeAllowed:async r=>l(e(),"removeAllowedHost",t,r),removeDenied:async r=>l(e(),"removeDeniedHost",t,r),setAllowed:async r=>l(e(),"setAllowedHosts",t,[...r]),setDenied:async r=>l(e(),"setDeniedHosts",t,[...r])}),Q=(e,t,r,o,n)=>{const s=()=>e.get(e.idFromName(r)),c=async(a,i={})=>te(await l(s(),"lunoraSpawn",t.binding,ee(a,i)),i.signal);return{..._(async(a,i)=>F(s(),a,i),O(t),o,void 0,n),...Oe(s,t.binding),destroy:async()=>l(s(),"destroy",t.binding),egress:De(s,t.binding),getState:async()=>l(s(),"getState",t.binding),renewActivityTimeout:async()=>l(s(),"renewActivityTimeout",t.binding),snapshot:async a=>l(s(),"lunoraSnapshot",t.binding,a),spawn:c,start:async a=>l(s(),"start",t.binding,a),stop:async a=>l(s(),"stop",t.binding,a),terminal:async(a,i)=>de(c,a,i)}},D="pool-",P=e=>`${D}${String(Math.floor(Math.random()*e))}`,q=(e,t)=>{if(e.startsWith(D))throw new N("BAD_REQUEST",`${O(t)}.get(${JSON.stringify(e)}): the "${D}" prefix is reserved for the instances .any()/.pool() pick, so this name would share a container (disk, lifecycle) with a pool member. Pick a name that does not start with "${D}".`)},xe=e=>e.status>=500,b=(e,t,r={},o,n)=>{const s=r.size??t.maxInstances??k,c=Math.max(1,r.attempts??3),a=r.backoffMs??100,i=r.maxBackoffMs??U,u=`${O(t)}.pool()`,v=(d,f)=>async(p,h)=>{const R=typeof p=="string"?c:1;let S;for(let y=0;y<R;y+=1){y>0&&await j(Math.min(a*2**(y-1),i));const g=z(p,h,o,n);try{const E=await F(e.get(e.idFromName(P(s))),g,h?.signal);if(y===R-1||!await d(E))return E}catch(E){if(S=E,!f(E))throw E}}throw S instanceof Error?S:new Error(`ctx.containers.${t.exportName}.pool(): all ${String(R)} attempts failed`)},m=v(r.retryOn??xe,()=>!0);return{exec:I(v(V,X),u),fetch:async(d,f)=>(H(d,u),m(...W(d,f))),port:d=>b(e,t,r,d,n)}},ke=(e,t,r)=>({any:(o,n)=>Y(e,P(o??t.maxInstances??k),O(t),n,r),get:(o,n)=>(q(o,t),Q(e,t,o,n,r)),pool:o=>b(e,t,o,void 0,r)}),_e=e=>{const t=()=>{throw new N("INTERNAL",`ctx.containers.${e.exportName}: no "${e.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:t,get:t,pool:t}},Fe=(e,t,r,o,n)=>{const s={};for(const c of t){const a=e[c.binding];s[c.exportName]=a&&typeof a.idFromName=="function"&&typeof a.get=="function"?ke(Z(a,r),c,{...n===void 0?{}:{sampleErrors:n},...o===void 0?{}:{traceparent:o}}):_e(c)}return s},$e=(e,t,r,o={})=>{const{jurisdiction:n,...s}=o,c={binding:$(t),exportName:t};return Fe(e,[c],n)[t].get(r,s)},A=e=>()=>Promise.reject(new N("NOT_IMPLEMENTED",`createContainerTestContext: ${e}() needs a real container — the test double has no processes or buckets. Stub the handle method in your test instead.`)),Pe=e=>{const t=new Map,r=o=>{const n=t.get(o)??ye();return t.set(o,n),{allowHost:()=>Promise.resolve(),denyHost:()=>Promise.resolve(),destroy:()=>Promise.resolve(),fetch:s=>Promise.resolve(e(s,{name:o})),lunoraBackup:A("backup"),lunoraDeleteBackup:A("deleteBackup"),lunoraExec:s=>Promise.resolve(e(s,{name:o})),lunoraInspectMount:A("inspectMount"),lunoraMkdir:n.mkdir,lunoraMount:A("mount"),lunoraReadDirectory:n.readDirectory,lunoraReadFile:n.readFile,lunoraRemove:n.remove,lunoraRename:n.rename,lunoraRestore:A("restore"),lunoraSnapshot:s=>Promise.resolve({id:`test-snapshot-${o}`,size:0,...s?.name===void 0?{}:{name:s.name}}),lunoraSpawn:A("spawn"),lunoraStat:n.stat,lunoraUnmount:A("unmount"),lunoraWriteFile:n.writeFile,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=>r(String(o)),idFromName:o=>o}},Ue=e=>{const t={};for(const[r,o]of Object.entries(e)){const n=Pe(o),s={binding:$(r),exportName:r};t[r]={any:(c,a)=>Y(n,P(c??s.maxInstances??k),O(s),{attempts:1,...a}),get:(c,a)=>(q(c,s),Q(n,s,c,{attempts:1,...a})),pool:c=>b(n,s,{...c,attempts:1})}}return t};export{Fe as createContainerContext,Ue as createContainerTestContext,$e as getContainer};
|
|
@@ -102,7 +102,10 @@ type NamedContainerInstanceType = "basic" | "lite" | "standard-1" | "standard-2"
|
|
|
102
102
|
* A custom instance type, available to every Containers account. Cloudflare's
|
|
103
103
|
* documented bounds: 1–4 vCPU, up to 12 GiB memory, up to 20 GB disk at any
|
|
104
104
|
* memory size, and ≥ 3 GiB memory per vCPU. For less than 1 vCPU use the named
|
|
105
|
-
* `lite` or `basic` types. The
|
|
105
|
+
* `lite` or `basic` types. The bounds live in `CUSTOM_INSTANCE_TYPE_LIMITS`
|
|
106
|
+
* (`./instance-limits`), which both `defineContainer` (a `durable_object`
|
|
107
|
+
* container's runtime size) and `@lunora/config`'s wrangler validator
|
|
108
|
+
* (`containers[].instance_type`) enforce.
|
|
106
109
|
*/
|
|
107
110
|
interface CustomContainerInstanceType {
|
|
108
111
|
/** Disk in MB, up to 20000 (20 GB) at any memory size. Cloudflare's default is 2000 (2 GB). */
|
|
@@ -102,7 +102,10 @@ type NamedContainerInstanceType = "basic" | "lite" | "standard-1" | "standard-2"
|
|
|
102
102
|
* A custom instance type, available to every Containers account. Cloudflare's
|
|
103
103
|
* documented bounds: 1–4 vCPU, up to 12 GiB memory, up to 20 GB disk at any
|
|
104
104
|
* memory size, and ≥ 3 GiB memory per vCPU. For less than 1 vCPU use the named
|
|
105
|
-
* `lite` or `basic` types. The
|
|
105
|
+
* `lite` or `basic` types. The bounds live in `CUSTOM_INSTANCE_TYPE_LIMITS`
|
|
106
|
+
* (`./instance-limits`), which both `defineContainer` (a `durable_object`
|
|
107
|
+
* container's runtime size) and `@lunora/config`'s wrangler validator
|
|
108
|
+
* (`containers[].instance_type`) enforce.
|
|
106
109
|
*/
|
|
107
110
|
interface CustomContainerInstanceType {
|
|
108
111
|
/** Disk in MB, up to 20000 (20 GB) at any memory size. Cloudflare's default is 2000 (2 GB). */
|
package/dist/sandbox/index.d.mts
CHANGED
|
@@ -2,7 +2,7 @@ import { SandboxFileStat, SandboxDirectoryEntry, DirectoryBackupRecord, S3MountI
|
|
|
2
2
|
export { DirectoryBackupGateway, S3Gateway } from '@cloudflare/sandbox';
|
|
3
3
|
import { LunoraContainer } from "../do/index.mjs";
|
|
4
4
|
import { C as ContainerFileOptions, a as ContainerFileContent, b as ContainerBackupOptions, c as ContainerMountRequest } from "../packem_shared/sandbox-types.d-DXlHnsEg.mjs";
|
|
5
|
-
import { C as ContainerDefinition } from "../packem_shared/jurisdiction.d-
|
|
5
|
+
import { C as ContainerDefinition } from "../packem_shared/jurisdiction.d-BwpauCoI.mjs";
|
|
6
6
|
import 'cloudflare:workers';
|
|
7
7
|
type DurableObjectContext = ConstructorParameters<typeof LunoraContainer>[0];
|
|
8
8
|
declare class LunoraSandboxContainer<Env = unknown> extends LunoraContainer<Env> {
|
package/dist/sandbox/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { SandboxFileStat, SandboxDirectoryEntry, DirectoryBackupRecord, S3MountI
|
|
|
2
2
|
export { DirectoryBackupGateway, S3Gateway } from '@cloudflare/sandbox';
|
|
3
3
|
import { LunoraContainer } from "../do/index.js";
|
|
4
4
|
import { C as ContainerFileOptions, a as ContainerFileContent, b as ContainerBackupOptions, c as ContainerMountRequest } from "../packem_shared/sandbox-types.d-DXlHnsEg.js";
|
|
5
|
-
import { C as ContainerDefinition } from "../packem_shared/jurisdiction.d-
|
|
5
|
+
import { C as ContainerDefinition } from "../packem_shared/jurisdiction.d-BwpauCoI.js";
|
|
6
6
|
import 'cloudflare:workers';
|
|
7
7
|
type DurableObjectContext = ConstructorParameters<typeof LunoraContainer>[0];
|
|
8
8
|
declare class LunoraSandboxContainer<Env = unknown> extends LunoraContainer<Env> {
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{LunoraError as h}from"@lunora/errors";const d=new Set(["basic","lite","standard-1","standard-2","standard-3","standard-4"]),l=new Set(["lite","standard-1","standard-2","standard-3","standard-4"]),f="cloudflare/",w=e=>e.startsWith(f),g=["maxInstances","rollout"],c=100,u=128,s=/^[A-Z_]\w*$/i,y=/^\d+[smh]$/,E={h:3600,m:60,s:1},D=e=>{if(typeof e=="number")return Math.floor(e);if(y.exec(e)===null)throw new TypeError(`Invalid duration "${e}" — expected a number of seconds or "<n>[smh]"`);return Number(e.slice(0,-1))*(E[e.slice(-1)]??1)},T=e=>{const t=e.endsWith("/")?e.slice(0,-1):e,n=t.lastIndexOf("/");return n===-1?t:t.slice(n+1)},v=e=>{const t=e.lastIndexOf("/");return t===-1?".":e.slice(0,t)||"/"},M=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(T(e).startsWith("Dockerfile"))return{buildContext:v(e),dockerfilePath:e,kind:"dockerfile"};const t=e.endsWith("/")?e.slice(0,-1):e;return{buildContext:t,dockerfilePath:`${t}/Dockerfile`,kind:"dockerfile"}},j=e=>`${e.charAt(0).toUpperCase()}${e.slice(1)}Container`,R=e=>`CONTAINER_${e.replaceAll(/(?<=[a-z0-9])(?=[A-Z])/g,"_").toUpperCase()}`,V=e=>`lunora-${e.replaceAll(/(?<=[a-z0-9])(?=[A-Z])/g,"-").toLowerCase()}:build`,m=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")},C=(e,t,n)=>{for(const[r,o]of Object.entries(e.secretsStore??{})){if(!s.test(r))throw new TypeError(`defineContainer: secretsStore env name "${r}" is not a valid environment variable name`);if(typeof o!="string"||o.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`)}},$=e=>{for(const r of Object.keys(e.env??{}))if(!s.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(!s.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(!s.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`)}C(e,t,n)},i=(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)})`)},k=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&&i(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)})`)}},p=(e,t)=>{if(e!==void 0){if(typeof e=="string"){if(!y.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)})`)}},S=e=>{const{backups:t}=e;if(t!==void 0){if(e.sandbox!==!0)throw new TypeError("defineContainer: `backups` needs `sandbox: true` — directory backups are a Sandbox SDK helper");if(typeof t.bucket!="string"||t.bucket.trim().length===0)throw new TypeError("defineContainer: `backups.bucket` must name an R2 bucket binding");if(t.prefix!==void 0&&(typeof t.prefix!="string"||t.prefix.length>0&&!t.prefix.endsWith("/")))throw new TypeError("defineContainer: `backups.prefix` must be a string ending in `/`, or omitted")}},I=e=>{for(const t of["allowedHosts","deniedHosts"])if(e[t]?.some(r=>typeof r!="string"||r.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")},N=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)i(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(I(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");if(k(e),e.sandbox!==void 0&&typeof e.sandbox!="boolean")throw new TypeError("defineContainer: `sandbox` must be a boolean, or omitted");S(e)},P=e=>typeof e=="string"&&e.startsWith("registry.cloudflare.com/")&&e.includes("@sha256:"),A=(e,t)=>{if(e.length===0||e.length>u)throw new TypeError(`defineContainer: image name "${e}" must be 1–${String(u)} characters`);if(typeof t=="string"){m(t);return}if(!P(t.registry))throw new TypeError(`defineContainer: \`images["${e}"].registry\` must be a digest-pinned (@sha256) reference under registry.cloudflare.com — push other registries' images there first`)},_=e=>{for(const r of g)if(e[r]!==void 0)throw new TypeError(`defineContainer: \`${r}\` is not supported with schedulingPolicy "durable_object"`);const t=Object.entries(e.images??{});if(t.length>c)throw new TypeError(`defineContainer: \`images\` holds at most ${String(c)} entries (got ${String(t.length)})`);for(const[r,o]of t)A(r,o);if(e.image!==void 0&&!Object.hasOwn(e.images??{},e.image)&&!w(e.image))throw new TypeError(`defineContainer: \`image\` "${e.image}" must name an entry of \`images\` or a Cloudflare-managed "${f}…" image`);const{instanceType:n}=e;if(typeof n=="string"&&!l.has(n))throw new TypeError(`defineContainer: \`instanceType\` "${n}" is not a runtime size — use one of ${[...l].join(", ")}, or a full { vcpu, memoryMib, diskMb } object`);if(typeof n=="object"&&[n.vcpu,n.memoryMib,n.diskMb].some(r=>typeof r!="number"||r<=0))throw new TypeError('defineContainer: a custom `instanceType` under schedulingPolicy "durable_object" needs positive vcpu, memoryMib and diskMb')},x=e=>{if(e.images!==void 0)throw new TypeError('defineContainer: `images` needs schedulingPolicy "durable_object"');m(e.image);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)})`);const n=e.rollout?.gracePeriodSeconds;if(n!==void 0&&(!Number.isInteger(n)||n<0))throw new TypeError(`defineContainer: \`rollout.gracePeriodSeconds\` must be a non-negative integer (got ${String(n)})`);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`)},L=e=>{const t=e.schedulingPolicy;if(e.schedulingPolicy==="durable_object")_(e);else if(t===void 0||t==="default")x(e);else throw new TypeError(`defineContainer: unknown \`schedulingPolicy\` ${JSON.stringify(t)} — use "default" or "durable_object"`);return e.defaultPort!==void 0&&i(e.defaultPort,"defaultPort"),p(e.sleepAfter,"sleepAfter"),p(e.hardTimeout,"hardTimeout"),$(e),N(e),{...e,isLunoraContainer:!0}},H=e=>typeof e=="object"&&e!==null&&e.isLunoraContainer===!0,W=(e,t,n)=>{const r={...e.env};for(const o of e.secrets??[]){const a=t[o];if(typeof a!="string"){const b=n===void 0?"container":`container "${n}"`;throw new h("INTERNAL",`${b}: declared secret "${o}" is not set on the Worker environment. Add it to .dev.vars for local dev and run \`wrangler secret put ${o}\` for production.`)}r[o]=a}return r};export{g as DURABLE_OBJECT_POLICY_FORBIDDEN_KEYS,R as containerBindingName,V as containerBuildTag,j as containerClassName,L as defineContainer,P as isCloudflareRegistryDigest,H as isContainerDefinition,w as isManagedImage,M as normalizeContainerImage,D as parseDurationSeconds,W as resolveContainerEnvVars};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{LunoraError as N}from"@lunora/errors";import{e as I,p as J,a as x,r as G}from"./exec-BYLW70sR.mjs";import{S as K}from"./sampling-D21rpu_s.mjs";import{containerBindingName as $}from"./DURABLE_OBJECT_POLICY_FORBIDDEN_KEYS-B9DkqZkf.mjs";import{a as Z}from"./jurisdiction-DtE9s70w.mjs";const ee=(e,t)=>{if(e.length===0)throw new TypeError("spawn: `command` must be a non-empty string");return{command:e,...t.args===void 0?{}:{args:[...t.args]},...t.cwd===void 0?{}:{cwd:t.cwd},...t.env===void 0?{}:{env:{...t.env}},...t.pty===void 0?{}:{pty:{...t.pty}},...t.stdin===void 0?{}:{stdin:t.stdin},...t.timeoutMs===void 0?{}:{timeoutMs:t.timeoutMs}}},te=(e,t)=>{const{control:r}=e,o=r.exitCode();if(t!==void 0){const n=()=>{Promise.resolve(r.kill()).catch(()=>{})};t.aborted?n():(t.addEventListener("abort",n,{once:!0}),o.finally(()=>{t.removeEventListener("abort",n)}).catch(()=>{}))}return{exitCode:o,isPty:e.isPty,kill:async n=>{await r.kill(n)},pid:e.pid,resize:async(n,s)=>{await r.resize(n,s)},stderr:e.stderr,stdin:e.stdin,stdout:e.stdout}},re=1048576,oe=80,ne=24,B=(e,t)=>{const r=Number(e.searchParams.get(t));return Number.isInteger(r)&&r>0?r:void 0},se={pair:()=>{const{WebSocketPair:e}=globalThis;if(e===void 0)throw new TypeError("terminal(): WebSocketPair is not available — open a terminal from a Worker or Durable Object");const t=new e;return[t[0],t[1]]},upgrade:e=>new Response(void 0,{status:101,webSocket:e})},ae=e=>{try{const t=JSON.parse(e);return Number.isInteger(t.cols)&&Number.isInteger(t.rows)?{cols:t.cols,rows:t.rows}:void 0}catch{return}},ie=async e=>{if(e instanceof ArrayBuffer)return new Uint8Array(e);if(ArrayBuffer.isView(e))return new Uint8Array(e.buffer,e.byteOffset,e.byteLength);if(e instanceof Blob)return new Uint8Array(await e.arrayBuffer())},ce=e=>e instanceof ArrayBuffer||ArrayBuffer.isView(e)?e.byteLength:e instanceof Blob?e.size:0,le=async(e,t)=>{const r=e.stdout?.getReader();try{for(;r!==void 0;){const{done:n,value:s}=await r.read();if(n)break;t.send(s)}const o=await e.exitCode;t.close(1e3,`exited with code ${String(o)}`)}catch{t.close(1011,"terminal stream failed")}},de=async(e,t,r={},o=se)=>{if(t.headers.get("upgrade")?.toLowerCase()!=="websocket")return new Response("terminal(): expected a WebSocket upgrade request",{status:426});const n=new URL(t.url),{command:s="sh",cols:c,rows:a,...i}=r,u=await e(s,{...i,env:{TERM:"xterm-256color",...i.env},pty:{cols:c??B(n,"cols")??oe,rows:a??B(n,"rows")??ne},stdin:!0}),[v,m]=o.pair(),d=u.stdin?.getWriter(),f=()=>{u.kill().catch(()=>{}),d?.close().catch(()=>{})};let p=Promise.resolve();const h=y=>{p=p.then(y).catch(f)};let R=0;const S=(y,g)=>{if(R+=y,R>re){m.close(1009,"terminal input backlog too large"),f();return}h(async()=>{try{const E=await g;E!==void 0&&await d?.write(E)}finally{R-=y}})};return m.accept(),m.addEventListener("message",({data:y})=>{if(typeof y!="string"){S(ce(y),ie(y));return}const g=ae(y);if(g===void 0){const E=new TextEncoder().encode(y);S(E.byteLength,Promise.resolve(E))}else h(async()=>u.resize(g.cols,g.rows).catch(()=>{}))}),m.addEventListener("close",f),m.addEventListener("error",f),le(u,m).catch(()=>{}),o.upgrade(v)},ue={EEXIST:"CONFLICT",ENOENT:"NOT_FOUND",ENOTEMPTY:"CONFLICT"},w=(e,t,r)=>new N(ue[e]??"BAD_REQUEST",`test container: ${t} ${r} failed with ${e}`,{data:{errno:e,operation:t,path:r}}),T=(e,t={})=>{const r=e.startsWith("/")?e:`${t.cwd??""}/${e}`;if(!r.startsWith("/"))throw new TypeError(`test container: "${e}" is relative and no absolute \`cwd\` was given`);const o=[];for(const n of r.split("/"))n===".."?o.pop():n!==""&&n!=="."&&o.push(n);return`/${o.join("/")}`},C=e=>e.slice(0,e.lastIndexOf("/"))||"/",fe=async e=>typeof e=="string"?new TextEncoder().encode(e):e instanceof ArrayBuffer?Uint8Array.from(new Uint8Array(e)):ArrayBuffer.isView(e)?new Uint8Array(e.buffer.slice(e.byteOffset,e.byteOffset+e.byteLength)):new Uint8Array(await new Response(e).arrayBuffer()),L=(e,t)=>{const r=new Date;return{accessedAt:r,changedAt:r,gid:0,modifiedAt:r,mode:e==="directory"?16877:33188,size:BigInt(t),type:e,uid:0}},ye=()=>{const e=new Map,t=new Set(["/"]),r=s=>e.has(s)||t.has(s),o=s=>[...e.keys(),...t].filter(c=>c!==s&&C(c)===s).toSorted((c,a)=>c.localeCompare(a)),n=(s,c)=>{const a=C(s);if(e.has(a))throw w("ENOTDIR",c,s);if(!t.has(a))throw w("ENOENT",c,s)};return{mkdir:async(s,c={})=>{const a=T(s,c);if(c.recursive===!0){let i="";for(const u of a.split("/").filter(Boolean)){if(i=`${i}/${u}`,e.has(i))throw w("ENOTDIR","mkdir",i);t.add(i)}return}if(r(a))throw w("EEXIST","mkdir",a);n(a,"mkdir"),t.add(a)},readDirectory:async(s,c)=>{const a=T(s,c);if(e.has(a))throw w("ENOTDIR","readDirectory",a);if(!t.has(a))throw w("ENOENT","readDirectory",a);return o(a).map(i=>({name:i.slice(i.lastIndexOf("/")+1),type:t.has(i)?"directory":"file"}))},readFile:async(s,c)=>{const a=T(s,c),i=e.get(a);if(i===void 0)throw w(t.has(a)?"EISDIR":"ENOENT","readFile",a);return new Response(new Uint8Array(i))},remove:async(s,c={})=>{const a=T(s,c);if(!e.delete(a)){if(!t.has(a)){if(c.force!==!0)throw w("ENOENT","remove",a);return}if(c.recursive!==!0)throw w(o(a).length>0?"ENOTEMPTY":"EISDIR","remove",a);for(const i of[...e.keys(),...t])(i===a||i.startsWith(`${a}/`))&&(e.delete(i),t.delete(i));t.add("/")}},rename:async(s,c,a)=>{const i=T(s,a),u=T(c,a);if(!r(i))throw w("ENOENT","rename",i);if(n(u,"rename"),u.startsWith(`${i}/`))throw w("EINVAL","rename",i);const v=[...e],m=[...t];for(const[d,f]of v)(d===i||d.startsWith(`${i}/`))&&(e.delete(d),e.set(u+d.slice(i.length),f));for(const d of m)(d===i||d.startsWith(`${i}/`))&&(t.delete(d),t.add(u+d.slice(i.length)))},stat:async(s,c)=>{const a=T(s,c),i=e.get(a);if(i!==void 0)return L("file",i.byteLength);if(t.has(a))return L("directory",4096);throw w("ENOENT","stat",a)},writeFile:async(s,c,a)=>{const i=T(s,a);if(t.has(i))throw w("EISDIR","writeFile",i);n(i,"writeFile"),e.set(i,await fe(c))}}},k=3,U=3e4,me=3,we=500,he="cf-container-target-port",M="__lunora",Ee=e=>{const t=e.indexOf(";");return t===-1?e:e.slice(0,t)},ve=e=>{const t=e.split("/").find(r=>r!=="")??"";return Ee(t).toLowerCase()},H=(e,t)=>{const r=typeof e=="string"?e:e.url,o=URL.parse(r.startsWith("/")?`http://container${r}`:r);if(o!==null&&J(o.pathname,n=>ve(n)===M))throw new N("BAD_REQUEST",`${t}: \`/${M}/*\` is reserved for Lunora's own container routes and cannot be reached with \`fetch\`. Use \`exec\` to run a command.`)},W=(e,t)=>{if(typeof e!="string"){const o=new Request(e,t);return o.headers.delete(x),[o,void 0]}if(t?.headers===void 0)return[e,t];const r=new Headers(t.headers);return r.delete(x),[e,{...t,headers:r}]},z=(e,t,r,o)=>{const n=typeof e=="string"&&e.startsWith("/")?new Request(`http://container${e}`,t):new Request(e,t);return r!==void 0&&n.headers.set(he,String(r)),o?.traceparent!==void 0&&n.headers.set("traceparent",o.traceparent),o?.sampleErrors!==void 0&&n.headers.set(K,o.sampleErrors?"1":"0"),n},j=async e=>{e<=0||await new Promise(t=>{setTimeout(t,e)})},pe=/no container instance|not listening|try again later|rate.?limit|provision/i,Te="no Container instance available",Re="Failed to start container:",ge=1024,Ae=async e=>(await G(e.clone().body,ge)).text,X=e=>e instanceof Error&&pe.test(e.message),V=async e=>{if(e.status===429)return!0;if(e.status!==500&&e.status!==503)return!1;try{const t=await Ae(e);return t.includes(Te)||t.startsWith(Re)}catch{return!1}},O=e=>`ctx.containers.${e.exportName}`,_=(e,t,r={},o,n)=>{const s=Math.max(1,r.attempts??me),c=r.backoffMs??we,a=r.maxBackoffMs??U,i=async(u,v)=>{const m=typeof u=="string"?s:1;let d;for(let f=0;f<m;f+=1){const p=f===m-1;f>0&&await j(Math.min(c*2**(f-1),a));try{const h=await e(z(u,v,o,n),v?.signal);if(p||!await V(h))return h}catch(h){if(d=h,p||!X(h))throw h}}throw d instanceof Error?d:new Error("ctx.containers: cold-start retry exhausted")};return{exec:I(i,t),fetch:async(u,v)=>(H(u,t),i(...W(u,v))),port:u=>_(e,t,r,u,n)}},Se=async(e,t)=>{if(t.aborted)throw e.catch(()=>{}),t.reason;const r=new AbortController,o=new Promise((n,s)=>{t.addEventListener("abort",()=>{s(t.reason)},{once:!0,signal:r.signal})});try{return await Promise.race([e,o])}finally{r.abort(),e.catch(()=>{})}},F=async(e,t,r)=>{if(!t.headers.has(x))return e.fetch(t);if(typeof e.lunoraExec!="function")throw new TypeError("ctx.containers: this container DO does not expose lunoraExec() — is @lunora/container/do up to date?");t.headers.delete(x);const o=e.lunoraExec(new Request(t.url,{body:await t.text(),headers:t.headers,method:t.method}));return r?Se(o,r):o},Y=(e,t,r,o,n)=>_(async(s,c)=>F(e.get(e.idFromName(t)),s,c),r,o,void 0,n),Ne=new Set(["lunoraBackup","lunoraDeleteBackup","lunoraInspectMount","lunoraMkdir","lunoraMount","lunoraReadDirectory","lunoraReadFile","lunoraRemove","lunoraRename","lunoraRestore","lunoraStat","lunoraUnmount","lunoraWriteFile"]),l=async(e,t,r,...o)=>{const n=e[t];if(typeof n!="function")throw Ne.has(t)?new N("BAD_REQUEST",`ctx.containers: the "${r}" container DO does not expose ${t}() — sandbox helpers need \`sandbox: true\` in its defineContainer() definition`):new TypeError(`ctx.containers: the "${r}" container DO does not expose ${t}() — is @lunora/container/do up to date?`);return n(...o)},Oe=(e,t)=>({backup:async(r,o)=>l(e(),"lunoraBackup",t,r,o),deleteBackup:async r=>l(e(),"lunoraDeleteBackup",t,r),files:{mkdir:async(r,o)=>l(e(),"lunoraMkdir",t,r,o),readDirectory:async(r,o)=>l(e(),"lunoraReadDirectory",t,r,o),readFile:async(r,o)=>l(e(),"lunoraReadFile",t,r,o),remove:async(r,o)=>l(e(),"lunoraRemove",t,r,o),rename:async(r,o,n)=>l(e(),"lunoraRename",t,r,o,n),stat:async(r,o)=>l(e(),"lunoraStat",t,r,o),writeFile:async(r,o,n)=>l(e(),"lunoraWriteFile",t,r,o,n)},inspectMount:async r=>l(e(),"lunoraInspectMount",t,r),mount:async r=>l(e(),"lunoraMount",t,r),restore:async(r,o)=>l(e(),"lunoraRestore",t,r,o),unmount:async r=>l(e(),"lunoraUnmount",t,r)}),De=(e,t)=>({allow:async r=>l(e(),"allowHost",t,r),deny:async r=>l(e(),"denyHost",t,r),removeAllowed:async r=>l(e(),"removeAllowedHost",t,r),removeDenied:async r=>l(e(),"removeDeniedHost",t,r),setAllowed:async r=>l(e(),"setAllowedHosts",t,[...r]),setDenied:async r=>l(e(),"setDeniedHosts",t,[...r])}),Q=(e,t,r,o,n)=>{const s=()=>e.get(e.idFromName(r)),c=async(a,i={})=>te(await l(s(),"lunoraSpawn",t.binding,ee(a,i)),i.signal);return{..._(async(a,i)=>F(s(),a,i),O(t),o,void 0,n),...Oe(s,t.binding),destroy:async()=>l(s(),"destroy",t.binding),egress:De(s,t.binding),getState:async()=>l(s(),"getState",t.binding),renewActivityTimeout:async()=>l(s(),"renewActivityTimeout",t.binding),snapshot:async a=>l(s(),"lunoraSnapshot",t.binding,a),spawn:c,start:async a=>l(s(),"start",t.binding,a),stop:async a=>l(s(),"stop",t.binding,a),terminal:async(a,i)=>de(c,a,i)}},D="pool-",P=e=>`${D}${String(Math.floor(Math.random()*e))}`,q=(e,t)=>{if(e.startsWith(D))throw new N("BAD_REQUEST",`${O(t)}.get(${JSON.stringify(e)}): the "${D}" prefix is reserved for the instances .any()/.pool() pick, so this name would share a container (disk, lifecycle) with a pool member. Pick a name that does not start with "${D}".`)},xe=e=>e.status>=500,b=(e,t,r={},o,n)=>{const s=r.size??t.maxInstances??k,c=Math.max(1,r.attempts??3),a=r.backoffMs??100,i=r.maxBackoffMs??U,u=`${O(t)}.pool()`,v=(d,f)=>async(p,h)=>{const R=typeof p=="string"?c:1;let S;for(let y=0;y<R;y+=1){y>0&&await j(Math.min(a*2**(y-1),i));const g=z(p,h,o,n);try{const E=await F(e.get(e.idFromName(P(s))),g,h?.signal);if(y===R-1||!await d(E))return E}catch(E){if(S=E,!f(E))throw E}}throw S instanceof Error?S:new Error(`ctx.containers.${t.exportName}.pool(): all ${String(R)} attempts failed`)},m=v(r.retryOn??xe,()=>!0);return{exec:I(v(V,X),u),fetch:async(d,f)=>(H(d,u),m(...W(d,f))),port:d=>b(e,t,r,d,n)}},ke=(e,t,r)=>({any:(o,n)=>Y(e,P(o??t.maxInstances??k),O(t),n,r),get:(o,n)=>(q(o,t),Q(e,t,o,n,r)),pool:o=>b(e,t,o,void 0,r)}),_e=e=>{const t=()=>{throw new N("INTERNAL",`ctx.containers.${e.exportName}: no "${e.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:t,get:t,pool:t}},Fe=(e,t,r,o,n)=>{const s={};for(const c of t){const a=e[c.binding];s[c.exportName]=a&&typeof a.idFromName=="function"&&typeof a.get=="function"?ke(Z(a,r),c,{...n===void 0?{}:{sampleErrors:n},...o===void 0?{}:{traceparent:o}}):_e(c)}return s},Ie=(e,t,r,o={})=>{const{jurisdiction:n,...s}=o,c={binding:$(t),exportName:t};return Fe(e,[c],n)[t].get(r,s)},A=e=>()=>Promise.reject(new N("NOT_IMPLEMENTED",`createContainerTestContext: ${e}() needs a real container — the test double has no processes or buckets. Stub the handle method in your test instead.`)),Pe=e=>{const t=new Map,r=o=>{const n=t.get(o)??ye();return t.set(o,n),{allowHost:()=>Promise.resolve(),denyHost:()=>Promise.resolve(),destroy:()=>Promise.resolve(),fetch:s=>Promise.resolve(e(s,{name:o})),lunoraBackup:A("backup"),lunoraDeleteBackup:A("deleteBackup"),lunoraExec:s=>Promise.resolve(e(s,{name:o})),lunoraInspectMount:A("inspectMount"),lunoraMkdir:n.mkdir,lunoraMount:A("mount"),lunoraReadDirectory:n.readDirectory,lunoraReadFile:n.readFile,lunoraRemove:n.remove,lunoraRename:n.rename,lunoraRestore:A("restore"),lunoraSnapshot:s=>Promise.resolve({id:`test-snapshot-${o}`,size:0,...s?.name===void 0?{}:{name:s.name}}),lunoraSpawn:A("spawn"),lunoraStat:n.stat,lunoraUnmount:A("unmount"),lunoraWriteFile:n.writeFile,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=>r(String(o)),idFromName:o=>o}},$e=e=>{const t={};for(const[r,o]of Object.entries(e)){const n=Pe(o),s={binding:$(r),exportName:r};t[r]={any:(c,a)=>Y(n,P(c??s.maxInstances??k),O(s),{attempts:1,...a}),get:(c,a)=>(q(c,s),Q(n,s,c,{attempts:1,...a})),pool:c=>b(n,s,{...c,attempts:1})}}return t};export{Fe as createContainerContext,$e as createContainerTestContext,Ie as getContainer};
|