@lunora/container 1.0.0-alpha.3 → 1.0.0-alpha.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/do/index.mjs CHANGED
@@ -1,138 +1 @@
1
- import { Container } from '@cloudflare/containers';
2
- import { resolveContainerEnvVars as resolveContainerEnvVariables } from '../packem_shared/containerBindingName-BGdSdFNA.mjs';
3
-
4
- const LUNORA_EVENT_SOURCE = "lunora";
5
- const buildContainerLifecycleEvent = (container, instance, event, message) => {
6
- return {
7
- container,
8
- event,
9
- instance,
10
- level: event === "error" ? "error" : "info",
11
- message,
12
- source: LUNORA_EVENT_SOURCE,
13
- ts: Date.now(),
14
- type: "container"
15
- };
16
- };
17
- const emitContainerLifecycle = (container, instance, event, message) => {
18
- const envelope = buildContainerLifecycleEvent(container, instance, event, message);
19
- const line = JSON.stringify(envelope);
20
- if (event === "error") {
21
- console.error(line);
22
- } else {
23
- console.log(line);
24
- }
25
- return envelope;
26
- };
27
-
28
- const RECORD_CONTAINER_EVENT_OP = "__lunora_admin__:recordContainerEvent";
29
- const ROOT_SHARD_NAME = "__root__";
30
- const applyJurisdiction = (namespace, jurisdiction) => {
31
- if (jurisdiction === void 0) {
32
- return namespace;
33
- }
34
- if (typeof namespace.jurisdiction !== "function") {
35
- throw new TypeError(
36
- `@lunora/container: Durable Object namespace does not support jurisdiction("${jurisdiction}") — update @cloudflare/workers-types or remove the jurisdiction option`
37
- );
38
- }
39
- return namespace.jurisdiction(jurisdiction);
40
- };
41
- const isShardNamespace = (value) => {
42
- if (value === null || typeof value !== "object") {
43
- return false;
44
- }
45
- const candidate = value;
46
- return typeof candidate.get === "function" && typeof candidate.idFromName === "function";
47
- };
48
- const resolveRootShard = (namespace, jurisdiction) => {
49
- const pinned = applyJurisdiction(namespace, jurisdiction);
50
- if (typeof pinned.getByName === "function") {
51
- return pinned.getByName(ROOT_SHARD_NAME);
52
- }
53
- return pinned.get(pinned.idFromName(ROOT_SHARD_NAME));
54
- };
55
- const reportContainerLifecycle = async (env, envelope, jurisdiction) => {
56
- try {
57
- const envRecord = env ?? {};
58
- const namespace = envRecord["SHARD"];
59
- if (!isShardNamespace(namespace)) {
60
- return;
61
- }
62
- const adminBearer = typeof envRecord["LUNORA_ADMIN_TOKEN"] === "string" ? envRecord["LUNORA_ADMIN_TOKEN"] : void 0;
63
- if (!adminBearer || adminBearer.length === 0) {
64
- return;
65
- }
66
- const request = new Request("https://shard.internal/rpc", {
67
- body: JSON.stringify({ args: { event: envelope }, functionPath: RECORD_CONTAINER_EVENT_OP }),
68
- headers: { authorization: `Bearer ${adminBearer}`, "content-type": "application/json" },
69
- method: "POST"
70
- });
71
- await resolveRootShard(namespace, jurisdiction).fetch(request);
72
- } catch {
73
- }
74
- };
75
-
76
- class LunoraContainer extends Container {
77
- /**
78
- * Data-residency jurisdiction the app's DOs are pinned to (codegen passes the
79
- * schema's `.jurisdiction("…")`). Used to pin the best-effort lifecycle report
80
- * to the same region as the root shard. `undefined` ⇒ un-pinned.
81
- */
82
- lunoraJurisdiction;
83
- /** The `lunora/containers.ts` export name, for lifecycle log correlation. */
84
- lunoraName;
85
- constructor(context, env, definition, exportName, jurisdiction) {
86
- super(context, env, {
87
- defaultPort: definition.defaultPort,
88
- envVars: resolveContainerEnvVariables(definition, env, exportName),
89
- sleepAfter: definition.sleepAfter
90
- });
91
- if (definition.enableInternet !== void 0) {
92
- this.enableInternet = definition.enableInternet;
93
- }
94
- this.lunoraName = exportName ?? "container";
95
- this.lunoraJurisdiction = jurisdiction;
96
- }
97
- onError(error) {
98
- const envelope = emitContainerLifecycle(this.lunoraName, this.instanceId(), "error", error instanceof Error ? error.message : String(error));
99
- this.surfaceInStudioLogs(envelope);
100
- return super.onError(error);
101
- }
102
- async onStart() {
103
- const envelope = emitContainerLifecycle(this.lunoraName, this.instanceId(), "start");
104
- this.surfaceInStudioLogs(envelope);
105
- await super.onStart();
106
- }
107
- async onStop(parameters) {
108
- const envelope = emitContainerLifecycle(this.lunoraName, this.instanceId(), "stop", `${parameters.reason} (exit ${String(parameters.exitCode)})`);
109
- this.surfaceInStudioLogs(envelope);
110
- await super.onStop(parameters);
111
- }
112
- /**
113
- * Best-effort push of `envelope` into the root ShardDO's log buffer so it
114
- * also appears in the Studio Logs panel (the terminal already has it via
115
- * `emitContainerLifecycle`). Fire-and-forget and fully swallowed: a missing
116
- * `SHARD` binding, a missing admin token, or a fetch failure NEVER throws
117
- * out of a lifecycle hook — the `console` path stays the source of truth.
118
- */
119
- surfaceInStudioLogs(envelope) {
120
- reportContainerLifecycle(this.env, envelope, this.lunoraJurisdiction).catch(() => {
121
- });
122
- }
123
- /**
124
- * Per-instance correlation id: the Durable Object id, which Cloudflare also
125
- * injects into the container as `CLOUDFLARE_DURABLE_OBJECT_ID`. Read
126
- * defensively — the id shape varies and isn't worth crashing a hook over.
127
- */
128
- instanceId() {
129
- try {
130
- const { id } = this.ctx;
131
- return typeof id?.toString === "function" ? id.toString() : "unknown";
132
- } catch {
133
- return "unknown";
134
- }
135
- }
136
- }
137
-
138
- export { LunoraContainer as default };
1
+ import{LunoraError as u}from"@lunora/errors";import{resolveContainerEnvVars as m,parseDurationSeconds as y}from"../packem_shared/containerBindingName-D01FopQt.mjs";import{a as g}from"../packem_shared/jurisdiction-DtE9s70w.mjs";import{Container as f}from"../packem_shared/ContainerProxy-fTW_pXWY.mjs";import{ContainerProxy as b,outboundParams as x}from"../packem_shared/ContainerProxy-fTW_pXWY.mjs";const w="lunora",E=(s,t,r,e)=>({container:s,event:r,instance:t,level:r==="error"?"error":"info",message:e,source:w,ts:Date.now(),type:"container"}),i=(s,t,r,e)=>{const o=E(s,t,r,e),n=JSON.stringify(o);return r==="error"?console.error(n):console.log(n),o},N="__lunora_admin__:recordContainerEvent",d="__root__",R=s=>{if(s===null||typeof s!="object")return!1;const t=s;return typeof t.get=="function"&&typeof t.idFromName=="function"},_=(s,t)=>{const r=g(s,t);return typeof r.getByName=="function"?r.getByName(d):r.get(r.idFromName(d))},T=async(s,t,r)=>{try{const e=s??{},o=e.SHARD;if(!R(o))return;const n=typeof e.LUNORA_ADMIN_TOKEN=="string"?e.LUNORA_ADMIN_TOKEN:void 0;if(!n||n.length===0)return;const a=new Request("https://shard.internal/rpc",{body:JSON.stringify({args:{event:t},functionPath:N}),headers:{authorization:`Bearer ${n}`,"content-type":"application/json"},method:"POST"});await _(o,r).fetch(a)}catch{}},p=500,S=3e4,h="__lunoraHardTimeoutGeneration";class L extends f{lunoraJurisdiction;lunoraName;lunoraDefaultPort;lunoraHardTimeoutSeconds;lunoraReadyOn;lunoraSecretsStore;lunoraSecretsStoreResolved;constructor(t,r,e,o,n){super(t,r,{defaultPort:e.defaultPort,entrypoint:e.entrypoint?[...e.entrypoint]:void 0,envVars:m(e,r,o),sleepAfter:e.sleepAfter}),e.enableInternet!==void 0&&(this.enableInternet=e.enableInternet),e.requiredPorts!==void 0&&(this.requiredPorts=[...e.requiredPorts]),e.interceptHttps!==void 0&&(this.interceptHttps=e.interceptHttps),e.allowedHosts!==void 0&&(this.allowedHosts=[...e.allowedHosts]),e.deniedHosts!==void 0&&(this.deniedHosts=[...e.deniedHosts]),e.pingEndpoint!==void 0&&(this.pingEndpoint=e.pingEndpoint),e.labels!==void 0&&(this.labels={...e.labels}),this.lunoraName=o??"container",this.lunoraJurisdiction=n,this.lunoraDefaultPort=e.defaultPort,this.lunoraReadyOn=e.readyOn?[...e.readyOn]:[],this.lunoraHardTimeoutSeconds=e.hardTimeout===void 0?void 0:y(e.hardTimeout),this.lunoraSecretsStore=e.secretsStore}async containerFetch(...t){return await this.resolveSecretsStoreEnv(),super.containerFetch(...t)}async start(...t){const[r]=t;return r?.envVars===void 0&&await this.resolveSecretsStoreEnv(),super.start(...t)}async onActivityExpired(){const t=i(this.lunoraName,this.instanceId(),"sleep");this.surfaceInStudioLogs(t),await super.onActivityExpired()}onError(t){const r=i(this.lunoraName,this.instanceId(),"error",t instanceof Error?t.message:String(t));return this.surfaceInStudioLogs(r),super.onError(t)}async onStart(){const t=i(this.lunoraName,this.instanceId(),"start");this.surfaceInStudioLogs(t),await super.onStart(),await this.armHardTimeout(),await this.awaitContainerReadiness()}async onHardTimeoutExpired(t){const r=await this.ctx.storage.get(h);if(t?.generation!==void 0&&t.generation!==r||this.ctx.container?.running!==!0)return;const e=i(this.lunoraName,this.instanceId(),"stop","hard timeout reached");this.surfaceInStudioLogs(e),await this.stop()}async onStop(t){const r=i(this.lunoraName,this.instanceId(),"stop",`${t.reason} (exit ${String(t.exitCode)})`);this.surfaceInStudioLogs(r),await super.onStop(t)}async armHardTimeout(){if(this.lunoraHardTimeoutSeconds===void 0)return;const t=(await this.ctx.storage.get(h)??0)+1;await this.ctx.storage.put(h,t),await this.schedule(this.lunoraHardTimeoutSeconds,"onHardTimeoutExpired",{generation:t})}async resolveSecretsStoreEnv(){const t=this.lunoraSecretsStore;t!==void 0&&(this.lunoraSecretsStoreResolved??=(async()=>{const r=this.env,e={};for(const[o,n]of Object.entries(t)){const a=r[n];if(a===void 0||typeof a.get!="function")throw new u("INTERNAL",`container "${this.lunoraName}": secretsStore env "${o}" points at binding "${n}", which is not a Secrets Store binding on the Worker env. Add a \`secrets_store_secrets\` entry binding "${n}".`);const c=await a.get();if(typeof c!="string")throw new TypeError(`container "${this.lunoraName}": Secrets Store binding "${n}" (env "${o}") did not resolve to a string value.`);e[o]=c}this.envVars={...this.envVars,...e}})().catch(r=>{throw this.lunoraSecretsStoreResolved=void 0,r}),await this.lunoraSecretsStoreResolved)}async awaitContainerReadiness(){if(this.lunoraReadyOn.length===0)return;const{container:t}=this.ctx;if(t===void 0)return;const r=Date.now()+S;await Promise.all(this.lunoraReadyOn.map(async e=>this.awaitReadinessCheck(t,e,r)))}async awaitReadinessCheck(t,r,e){const o=r.port??this.lunoraDefaultPort;if(o===void 0)throw new u("INTERNAL",`container "${this.lunoraName}": readyOn check "${r.path}" has no port — set the check's \`port\` or the container \`defaultPort\`.`);const n=r.status??200,a=r.path.startsWith("/")?r.path:`/${r.path}`,c=t.getTcpPort(o);for(;;){const v=Math.max(p,e-Date.now());try{if((await c.fetch(`http://container${a}`,{signal:AbortSignal.timeout(v)})).status===n)return}catch{}if(Date.now()>=e)throw new u("INTERNAL",`container "${this.lunoraName}": readiness check "${r.path}" (port ${String(o)}) did not return ${String(n)} within ${String(S)}ms`);await new Promise(l=>{setTimeout(l,p)})}}surfaceInStudioLogs(t){T(this.env,t,this.lunoraJurisdiction).catch(()=>{})}instanceId(){try{const{id:t}=this.ctx;return typeof t?.toString=="function"?t.toString():"unknown"}catch{return"unknown"}}}export{b as ContainerProxy,L as LunoraContainer,x as outboundParams};