@lunora/container 1.0.0-alpha.31 → 1.0.0-alpha.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bridge.d.mts +0 -6
- package/dist/bridge.d.ts +0 -6
- package/dist/do/index.d.mts +1 -2
- package/dist/do/index.d.ts +1 -2
- package/dist/do/index.mjs +1 -1
- package/dist/index.d.mts +91 -24
- package/dist/index.d.ts +91 -24
- package/dist/index.mjs +1 -1
- package/dist/otel.d.mts +0 -7
- package/dist/otel.d.ts +0 -7
- package/dist/otel.mjs +1 -1
- package/dist/packem_shared/CONTAINER_EXEC_PATH-CAqlIhLq.mjs +1 -0
- package/dist/packem_shared/abort-deadline-jwbW9Ala.mjs +1 -0
- package/dist/packem_shared/createContainerContext-IYvMMEfq.mjs +1 -0
- package/dist/packem_shared/exec-KlR4eMip.mjs +1 -0
- package/dist/packem_shared/{jurisdiction.d-DeQ3rtYK.d.mts → jurisdiction.d-KcQu2ZT_.d.mts} +1 -13
- package/dist/packem_shared/{jurisdiction.d-DeQ3rtYK.d.ts → jurisdiction.d-KcQu2ZT_.d.ts} +1 -13
- package/package.json +1 -1
- package/dist/packem_shared/createContainerContext-C8pmlFAX.mjs +0 -1
package/dist/bridge.d.mts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { LunoraError } from '@lunora/errors';
|
|
2
2
|
/**
|
|
3
3
|
* A `fetch` implementation — defaults to the runtime global.
|
|
4
|
-
* @experimental
|
|
5
4
|
*/
|
|
6
5
|
type FetchLike = (input: string, init: {
|
|
7
6
|
body: string;
|
|
@@ -15,7 +14,6 @@ type FetchLike = (input: string, init: {
|
|
|
15
14
|
}>;
|
|
16
15
|
/**
|
|
17
16
|
* `ContainerBridgeOptions` is part of the experimental `@lunora/container` API and may change without a major version bump.
|
|
18
|
-
* @experimental
|
|
19
17
|
*/
|
|
20
18
|
interface ContainerBridgeOptions {
|
|
21
19
|
/**
|
|
@@ -35,7 +33,6 @@ interface ContainerBridgeOptions {
|
|
|
35
33
|
}
|
|
36
34
|
/**
|
|
37
35
|
* Thrown when a Lunora function returns an error envelope. A `LunoraError` subclass carrying the wire `code`.
|
|
38
|
-
* @experimental
|
|
39
36
|
*/
|
|
40
37
|
declare class ContainerBridgeError extends LunoraError {
|
|
41
38
|
constructor(code: string, message: string);
|
|
@@ -46,7 +43,6 @@ declare class ContainerBridgeError extends LunoraError {
|
|
|
46
43
|
* imported) so the bridge stays dependency-free and its `.d.ts` is
|
|
47
44
|
* self-contained; the `__lunoraPhantom` shape matches, so a real `api.x.y`
|
|
48
45
|
* reference is assignable and its arg/return types are inferable.
|
|
49
|
-
* @experimental
|
|
50
46
|
*/
|
|
51
47
|
interface BridgeFunctionReference<Args = unknown, Result = unknown> {
|
|
52
48
|
readonly __lunoraPhantom?: {
|
|
@@ -69,7 +65,6 @@ type ResultOfReference<Reference> = Reference extends {
|
|
|
69
65
|
} ? Result : never;
|
|
70
66
|
/**
|
|
71
67
|
* `ContainerBridge` is part of the experimental `@lunora/container` API and may change without a major version bump.
|
|
72
|
-
* @experimental
|
|
73
68
|
*/
|
|
74
69
|
interface ContainerBridge {
|
|
75
70
|
/** Call an `action` by `namespace:fn` path. Alias of {@link ContainerBridge.call} for intent. */
|
|
@@ -100,7 +95,6 @@ interface ContainerBridge {
|
|
|
100
95
|
* `query`/`mutation`/`action` are intent-revealing aliases of one `call` — the
|
|
101
96
|
* wire is identical and the server dispatches by the function's registered
|
|
102
97
|
* kind, so a query path called via `.mutation(...)` still runs as a query.
|
|
103
|
-
* @experimental
|
|
104
98
|
*/
|
|
105
99
|
declare const createContainerBridge: (options: ContainerBridgeOptions) => ContainerBridge;
|
|
106
100
|
export { type BridgeFunctionReference, type ContainerBridge, ContainerBridgeError, type ContainerBridgeOptions, type FetchLike, createContainerBridge };
|
package/dist/bridge.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { LunoraError } from '@lunora/errors';
|
|
2
2
|
/**
|
|
3
3
|
* A `fetch` implementation — defaults to the runtime global.
|
|
4
|
-
* @experimental
|
|
5
4
|
*/
|
|
6
5
|
type FetchLike = (input: string, init: {
|
|
7
6
|
body: string;
|
|
@@ -15,7 +14,6 @@ type FetchLike = (input: string, init: {
|
|
|
15
14
|
}>;
|
|
16
15
|
/**
|
|
17
16
|
* `ContainerBridgeOptions` is part of the experimental `@lunora/container` API and may change without a major version bump.
|
|
18
|
-
* @experimental
|
|
19
17
|
*/
|
|
20
18
|
interface ContainerBridgeOptions {
|
|
21
19
|
/**
|
|
@@ -35,7 +33,6 @@ interface ContainerBridgeOptions {
|
|
|
35
33
|
}
|
|
36
34
|
/**
|
|
37
35
|
* Thrown when a Lunora function returns an error envelope. A `LunoraError` subclass carrying the wire `code`.
|
|
38
|
-
* @experimental
|
|
39
36
|
*/
|
|
40
37
|
declare class ContainerBridgeError extends LunoraError {
|
|
41
38
|
constructor(code: string, message: string);
|
|
@@ -46,7 +43,6 @@ declare class ContainerBridgeError extends LunoraError {
|
|
|
46
43
|
* imported) so the bridge stays dependency-free and its `.d.ts` is
|
|
47
44
|
* self-contained; the `__lunoraPhantom` shape matches, so a real `api.x.y`
|
|
48
45
|
* reference is assignable and its arg/return types are inferable.
|
|
49
|
-
* @experimental
|
|
50
46
|
*/
|
|
51
47
|
interface BridgeFunctionReference<Args = unknown, Result = unknown> {
|
|
52
48
|
readonly __lunoraPhantom?: {
|
|
@@ -69,7 +65,6 @@ type ResultOfReference<Reference> = Reference extends {
|
|
|
69
65
|
} ? Result : never;
|
|
70
66
|
/**
|
|
71
67
|
* `ContainerBridge` is part of the experimental `@lunora/container` API and may change without a major version bump.
|
|
72
|
-
* @experimental
|
|
73
68
|
*/
|
|
74
69
|
interface ContainerBridge {
|
|
75
70
|
/** Call an `action` by `namespace:fn` path. Alias of {@link ContainerBridge.call} for intent. */
|
|
@@ -100,7 +95,6 @@ interface ContainerBridge {
|
|
|
100
95
|
* `query`/`mutation`/`action` are intent-revealing aliases of one `call` — the
|
|
101
96
|
* wire is identical and the server dispatches by the function's registered
|
|
102
97
|
* kind, so a query path called via `.mutation(...)` still runs as a query.
|
|
103
|
-
* @experimental
|
|
104
98
|
*/
|
|
105
99
|
declare const createContainerBridge: (options: ContainerBridgeOptions) => ContainerBridge;
|
|
106
100
|
export { type BridgeFunctionReference, type ContainerBridge, ContainerBridgeError, type ContainerBridgeOptions, type FetchLike, createContainerBridge };
|
package/dist/do/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { C as ContainerDefinition, D as DurableObjectJurisdiction } from "../packem_shared/jurisdiction.d-KcQu2ZT_.mjs";
|
|
2
2
|
import { DurableObject, WorkerEntrypoint } from 'cloudflare:workers';
|
|
3
3
|
/**
|
|
4
4
|
* ContainerStartOptions as they come from worker types
|
|
@@ -500,7 +500,6 @@ type DurableObjectContext = ConstructorParameters<typeof Container>[0];
|
|
|
500
500
|
* }
|
|
501
501
|
* }
|
|
502
502
|
* ```
|
|
503
|
-
* @experimental
|
|
504
503
|
*/
|
|
505
504
|
declare class LunoraContainer<Env = unknown> extends Container<Env> {
|
|
506
505
|
/**
|
package/dist/do/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { C as ContainerDefinition, D as DurableObjectJurisdiction } from "../packem_shared/jurisdiction.d-KcQu2ZT_.js";
|
|
2
2
|
import { DurableObject, WorkerEntrypoint } from 'cloudflare:workers';
|
|
3
3
|
/**
|
|
4
4
|
* ContainerStartOptions as they come from worker types
|
|
@@ -500,7 +500,6 @@ type DurableObjectContext = ConstructorParameters<typeof Container>[0];
|
|
|
500
500
|
* }
|
|
501
501
|
* }
|
|
502
502
|
* ```
|
|
503
|
-
* @experimental
|
|
504
503
|
*/
|
|
505
504
|
declare class LunoraContainer<Env = unknown> extends Container<Env> {
|
|
506
505
|
/**
|
package/dist/do/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{LunoraError as u}from"@lunora/errors";import{resolveContainerEnvVars as
|
|
1
|
+
import{LunoraError as u}from"@lunora/errors";import{a as f}from"../packem_shared/abort-deadline-jwbW9Ala.mjs";import{resolveContainerEnvVars as y,parseDurationSeconds as g}from"../packem_shared/containerBindingName-D01FopQt.mjs";import{a as w}from"../packem_shared/jurisdiction-DtE9s70w.mjs";import{Container as E}from"../packem_shared/ContainerProxy-fTW_pXWY.mjs";import{ContainerProxy as C,outboundParams as M}from"../packem_shared/ContainerProxy-fTW_pXWY.mjs";const N="lunora",R=(s,t,r,e)=>({container:s,event:r,instance:t,level:r==="error"?"error":"info",message:e,source:N,ts:Date.now(),type:"container"}),i=(s,t,r,e)=>{const o=R(s,t,r,e),n=JSON.stringify(o);return r==="error"?console.error(n):console.log(n),o},T="__lunora_admin__:recordContainerEvent",S="__root__",_=s=>{if(s===null||typeof s!="object")return!1;const t=s;return typeof t.get=="function"&&typeof t.idFromName=="function"},I=(s,t)=>{const r=w(s,t);return typeof r.getByName=="function"?r.getByName(S):r.get(r.idFromName(S))},O=async(s,t,r)=>{try{const e=s??{},o=e.SHARD;if(!_(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:T}),headers:{authorization:`Bearer ${n}`,"content-type":"application/json"},method:"POST"});await I(o,r).fetch(a)}catch{}},m=500,v=3e4,d="__lunoraHardTimeoutGeneration";class b extends E{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:y(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:g(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(d);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(d)??0)+1;await this.ctx.storage.put(d,t),await this.schedule(this.lunoraHardTimeoutSeconds,"onHardTimeoutExpired",{generation:t})}async resolveSecretsStoreEnv(){const t=this.lunoraSecretsStore;t!==void 0&&(this.lunoraSecretsStoreResolved??=(async()=>{const 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()+v;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 l=Math.max(m,e-Date.now()),h=f(void 0,l,()=>new DOMException(`readiness probe timed out after ${String(l)}ms`,"TimeoutError"));try{if((await c.fetch(`http://container${a}`,{signal:h.signal})).status===n)return}catch{}finally{h.dispose()}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(v)}ms`);await new Promise(p=>{setTimeout(p,m)})}}surfaceInStudioLogs(t){O(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{C as ContainerProxy,b as LunoraContainer,M as outboundParams};
|
package/dist/index.d.mts
CHANGED
|
@@ -1,8 +1,74 @@
|
|
|
1
|
-
import { D as DurableObjectJurisdiction,
|
|
2
|
-
export type { B as BuildImageSource, c as ContainerInstanceType, d as ContainerReadinessCheck, e as ContainerRollout, f as CustomContainerInstanceType, g as NamedContainerInstanceType, R as RegistryImageSource } from "./packem_shared/jurisdiction.d-
|
|
1
|
+
import { D as DurableObjectJurisdiction, a as ContainerConfig, C as ContainerDefinition, b as ContainerImageSource, N as NormalizedContainerImage } from "./packem_shared/jurisdiction.d-KcQu2ZT_.mjs";
|
|
2
|
+
export type { B as BuildImageSource, c as ContainerInstanceType, d as ContainerReadinessCheck, e as ContainerRollout, f as CustomContainerInstanceType, g as NamedContainerInstanceType, R as RegistryImageSource } from "./packem_shared/jurisdiction.d-KcQu2ZT_.mjs";
|
|
3
|
+
/**
|
|
4
|
+
* Per-call options for `ContainerHandle.exec`.
|
|
5
|
+
*/
|
|
6
|
+
interface ContainerExecOptions {
|
|
7
|
+
/** Arguments passed to `command`, unshelled — the container must not concatenate them into a shell string. */
|
|
8
|
+
args?: ReadonlyArray<string>;
|
|
9
|
+
/** Working directory for the command, relative to the container's own root. */
|
|
10
|
+
cwd?: string;
|
|
11
|
+
/** Extra environment for this command only, merged over the container's env. */
|
|
12
|
+
env?: Readonly<Record<string, string>>;
|
|
13
|
+
/**
|
|
14
|
+
* Cap on the response body, in bytes. The whole `{code,stdout,stderr}`
|
|
15
|
+
* document has to be held in memory to be parsed, and a build box or job
|
|
16
|
+
* runner routinely writes tens of megabytes — a single unbounded `exec`
|
|
17
|
+
* that returns more than the isolate's memory limit kills the isolate and
|
|
18
|
+
* every other in-flight request sharing it, not just this call. So a
|
|
19
|
+
* runner that overruns the cap fails the call loudly rather than taking
|
|
20
|
+
* the shard down with it; raise this (or have the runner cap its own
|
|
21
|
+
* output) when a command legitimately produces more.
|
|
22
|
+
*
|
|
23
|
+
* Default {@link DEFAULT_EXEC_MAX_OUTPUT_BYTES} (1MB).
|
|
24
|
+
*/
|
|
25
|
+
maxOutputBytes?: number;
|
|
26
|
+
/**
|
|
27
|
+
* Abort the call. Composed with {@link ContainerExecOptions.timeoutMs} when
|
|
28
|
+
* both are given — whichever fires first wins.
|
|
29
|
+
*/
|
|
30
|
+
signal?: AbortSignal;
|
|
31
|
+
/**
|
|
32
|
+
* Give up after this many ms. Covers the whole call — the request *and*
|
|
33
|
+
* reading the response body, since `fetch` resolves on headers and a
|
|
34
|
+
* runner that answers `200` and then stalls mid-body would otherwise sit
|
|
35
|
+
* past the deadline. Sent to the container as well, so a well-behaved
|
|
36
|
+
* runner can kill the process rather than leak it when the caller walks
|
|
37
|
+
* away.
|
|
38
|
+
*/
|
|
39
|
+
timeoutMs?: number;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The outcome of a `ContainerHandle.exec` call.
|
|
43
|
+
*
|
|
44
|
+
* A non-zero `code` is **not** an error: a command that ran and failed is a
|
|
45
|
+
* result, and the caller decides what to do with it. Only a failure to *run*
|
|
46
|
+
* the command — transport, a non-2xx from the runner, an unparseable body —
|
|
47
|
+
* throws.
|
|
48
|
+
*/
|
|
49
|
+
interface ContainerExecResult {
|
|
50
|
+
/** Process exit code. Non-zero means the command ran and failed. */
|
|
51
|
+
code: number;
|
|
52
|
+
/** Everything the command wrote to stderr. */
|
|
53
|
+
stderr: string;
|
|
54
|
+
/** Everything the command wrote to stdout. */
|
|
55
|
+
stdout: string;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The route `ContainerHandle.exec` POSTs to. Namespaced under
|
|
59
|
+
* `/__lunora/` so it cannot collide with an application route the container
|
|
60
|
+
* already serves — the previous ad-hoc convention was a bare `/exec`, which an
|
|
61
|
+
* app could plausibly own for its own purposes.
|
|
62
|
+
*
|
|
63
|
+
* Exported because it is a *contract*, not an implementation detail: a
|
|
64
|
+
* container image has to serve exactly this route, and `@lunora/agent`'s
|
|
65
|
+
* human-in-the-loop gate has to recognise it. Both had it hard-coded as a
|
|
66
|
+
* second literal, which drifts silently — and on the gate's side, drift
|
|
67
|
+
* un-gates model-chosen command execution.
|
|
68
|
+
*/
|
|
69
|
+
declare const CONTAINER_EXEC_PATH = "/__lunora/exec";
|
|
3
70
|
/**
|
|
4
71
|
* Options for explicitly starting an instance (mirrors `@cloudflare/containers`).
|
|
5
|
-
* @experimental
|
|
6
72
|
*/
|
|
7
73
|
interface ContainerStartOptions {
|
|
8
74
|
/** Override outbound internet access for this start. */
|
|
@@ -16,7 +82,6 @@ interface ContainerStartOptions {
|
|
|
16
82
|
}
|
|
17
83
|
/**
|
|
18
84
|
* A container instance's runtime state, as returned by `getState()`. Structural — the platform adds fields over time.
|
|
19
|
-
* @experimental
|
|
20
85
|
*/
|
|
21
86
|
interface ContainerInstanceState {
|
|
22
87
|
[key: string]: unknown;
|
|
@@ -44,7 +109,6 @@ interface ContainerStubLike {
|
|
|
44
109
|
}
|
|
45
110
|
/**
|
|
46
111
|
* What the client needs from a Durable Object namespace binding.
|
|
47
|
-
* @experimental
|
|
48
112
|
*/
|
|
49
113
|
interface ContainerNamespaceLike {
|
|
50
114
|
get: (id: unknown) => ContainerStubLike;
|
|
@@ -57,9 +121,24 @@ interface ContainerNamespaceLike {
|
|
|
57
121
|
}
|
|
58
122
|
/**
|
|
59
123
|
* A handle on one container instance (one Durable Object).
|
|
60
|
-
* @experimental
|
|
61
124
|
*/
|
|
62
125
|
interface ContainerHandle {
|
|
126
|
+
/**
|
|
127
|
+
* Run a command inside the container and return its exit code and output.
|
|
128
|
+
*
|
|
129
|
+
* A container is an HTTP server, so there is no platform-level exec to call:
|
|
130
|
+
* the command is POSTed to `/__lunora/exec` and the container app
|
|
131
|
+
* serves that route. What this method adds over hand-rolling that fetch is
|
|
132
|
+
* the **contract** — a pinned path, a typed request and response, and the
|
|
133
|
+
* distinction between "the command failed" (a `code`) and "the command could
|
|
134
|
+
* not be run" (a throw). Before it existed every caller invented its own
|
|
135
|
+
* `/exec` convention and read the raw body back as if it were output, which
|
|
136
|
+
* silently turned a runner's 500 into a successful-looking result.
|
|
137
|
+
*
|
|
138
|
+
* The container side must accept `{ args, command, cwd, env, timeoutMs }`
|
|
139
|
+
* and answer `{ code, stdout, stderr }` as JSON.
|
|
140
|
+
*/
|
|
141
|
+
exec: (command: string, options?: ContainerExecOptions) => Promise<ContainerExecResult>;
|
|
63
142
|
/**
|
|
64
143
|
* Send an HTTP (or WebSocket-upgrade) request to the container. A path
|
|
65
144
|
* string (`"/transcode"`) is resolved against a synthetic origin; a full
|
|
@@ -82,7 +161,6 @@ interface ContainerHandle {
|
|
|
82
161
|
* game, a job runner per id) often needs to tear down or inspect the instance
|
|
83
162
|
* rather than wait for `sleepAfter`, so these wrap the container DO's
|
|
84
163
|
* `start`/`stop`/`destroy`/`getState`.
|
|
85
|
-
* @experimental
|
|
86
164
|
*/
|
|
87
165
|
interface ContainerInstanceHandle extends ContainerHandle {
|
|
88
166
|
/** Stop and discard the instance (its ephemeral disk is lost). */
|
|
@@ -117,7 +195,6 @@ interface ContainerInstanceHandle extends ContainerHandle {
|
|
|
117
195
|
* Each maps to the corresponding `@cloudflare/containers` `Container` RPC, so
|
|
118
196
|
* an app can tighten or relax a single instance's allowed/denied hosts after
|
|
119
197
|
* start without redeploying.
|
|
120
|
-
* @experimental
|
|
121
198
|
*/
|
|
122
199
|
interface ContainerEgressControls {
|
|
123
200
|
/** Add one hostname (or glob) to the allow-list. */
|
|
@@ -135,7 +212,6 @@ interface ContainerEgressControls {
|
|
|
135
212
|
}
|
|
136
213
|
/**
|
|
137
214
|
* The per-definition accessor exposed as `ctx.containers.<exportName>`.
|
|
138
|
-
* @experimental
|
|
139
215
|
*/
|
|
140
216
|
interface ContainerAccessor {
|
|
141
217
|
/**
|
|
@@ -178,7 +254,6 @@ interface ContainerAccessor {
|
|
|
178
254
|
}
|
|
179
255
|
/**
|
|
180
256
|
* Tuning for a pooled, retrying container handle. See {@link ContainerAccessor.pool}.
|
|
181
|
-
* @experimental
|
|
182
257
|
*/
|
|
183
258
|
interface PoolOptions {
|
|
184
259
|
/** Total attempts before giving up (each on a freshly-picked instance). Default 3. */
|
|
@@ -195,6 +270,10 @@ interface PoolOptions {
|
|
|
195
270
|
* Whether a *returned* response should be retried on another instance.
|
|
196
271
|
* Defaults to retrying any `5xx`. A thrown error (network/start failure) is
|
|
197
272
|
* always retried regardless of this predicate.
|
|
273
|
+
*
|
|
274
|
+
* Applies to `fetch` only. `exec` retries on the cold-start transients
|
|
275
|
+
* alone, because its caller never chose the request and a command that
|
|
276
|
+
* already ran must not be re-run just because the runner failed afterwards.
|
|
198
277
|
*/
|
|
199
278
|
retryOn?: (response: Response) => boolean;
|
|
200
279
|
/** Pool size to spread picks across. Defaults to the definition's `maxInstances`, else 3. */
|
|
@@ -205,7 +284,6 @@ interface PoolOptions {
|
|
|
205
284
|
* only on the platform's provisioning transients (no-instance / not-listening /
|
|
206
285
|
* rate-limited — see {@link isColdStartTransient}), which is why it's safe by
|
|
207
286
|
* default: those responses mean the request never reached the container.
|
|
208
|
-
* @experimental
|
|
209
287
|
*/
|
|
210
288
|
interface InstanceRetryOptions {
|
|
211
289
|
/**
|
|
@@ -221,7 +299,6 @@ interface InstanceRetryOptions {
|
|
|
221
299
|
}
|
|
222
300
|
/**
|
|
223
301
|
* Wiring info for one definition, emitted by codegen into the generated DO.
|
|
224
|
-
* @experimental
|
|
225
302
|
*/
|
|
226
303
|
interface ContainerBindingSpec {
|
|
227
304
|
/** Durable Object binding name, e.g. `CONTAINER_TRANSCODER`. */
|
|
@@ -240,12 +317,10 @@ interface ContainerBindingSpec {
|
|
|
240
317
|
* `traceparent` (the inbound RPC's W3C trace context, forwarded by the runtime
|
|
241
318
|
* and read off the request by the DO) is stamped onto every outbound container
|
|
242
319
|
* `fetch`, so the container's own spans stitch under the Worker's trace.
|
|
243
|
-
* @experimental
|
|
244
320
|
*/
|
|
245
321
|
declare const createContainerContext: (env: Record<string, unknown>, specs: ReadonlyArray<ContainerBindingSpec>, jurisdiction?: DurableObjectJurisdiction, traceparent?: string) => Record<string, ContainerAccessor>;
|
|
246
322
|
/**
|
|
247
323
|
* A test handler: receives the request plus the targeted instance name.
|
|
248
|
-
* @experimental
|
|
249
324
|
*/
|
|
250
325
|
type ContainerTestHandler = (request: Request, instance: {
|
|
251
326
|
name: string;
|
|
@@ -260,7 +335,6 @@ type ContainerTestHandler = (request: Request, instance: {
|
|
|
260
335
|
* transcoder: (request) => new Response("ok"),
|
|
261
336
|
* });
|
|
262
337
|
* ```
|
|
263
|
-
* @experimental
|
|
264
338
|
*/
|
|
265
339
|
declare const createContainerTestContext: (handlers: Record<string, ContainerTestHandler>) => Record<string, ContainerAccessor>;
|
|
266
340
|
/**
|
|
@@ -272,7 +346,6 @@ declare const createContainerTestContext: (handlers: Record<string, ContainerTes
|
|
|
272
346
|
* `Dockerfile.dev` also counts) is used as-is with its directory as the build
|
|
273
347
|
* context; any other path is treated as the build-context directory and the
|
|
274
348
|
* Dockerfile is expected at `<dir>/Dockerfile`.
|
|
275
|
-
* @experimental
|
|
276
349
|
*/
|
|
277
350
|
declare const normalizeContainerImage: (image: ContainerImageSource) => NormalizedContainerImage;
|
|
278
351
|
/**
|
|
@@ -280,7 +353,6 @@ declare const normalizeContainerImage: (image: ContainerImageSource) => Normaliz
|
|
|
280
353
|
* `transcoder` → `TranscoderContainer`. wrangler's `containers[].class_name`
|
|
281
354
|
* and the Durable Object binding's `class_name` both reference it, so codegen
|
|
282
355
|
* and the config layer MUST derive it identically — always via this helper.
|
|
283
|
-
* @experimental
|
|
284
356
|
*/
|
|
285
357
|
declare const containerClassName: (exportName: string) => string;
|
|
286
358
|
/**
|
|
@@ -288,7 +360,6 @@ declare const containerClassName: (exportName: string) => string;
|
|
|
288
360
|
* `CONTAINER_TRANSCODER`, `imageResizer` → `CONTAINER_IMAGE_RESIZER`. The
|
|
289
361
|
* `CONTAINER_` prefix namespaces these away from `SHARD`/`SESSION`/`SCHEDULER`
|
|
290
362
|
* so a container export can never collide with the built-in bindings.
|
|
291
|
-
* @experimental
|
|
292
363
|
*/
|
|
293
364
|
declare const containerBindingName: (exportName: string) => string;
|
|
294
365
|
/**
|
|
@@ -297,17 +368,14 @@ declare const containerBindingName: (exportName: string) => string;
|
|
|
297
368
|
* it as the wrangler `containers[].image`, and `lunora deploy` builds that tag
|
|
298
369
|
* with Railpack and `wrangler containers push`es it before deploying — so all
|
|
299
370
|
* three derive the tag from this one helper and can never disagree.
|
|
300
|
-
* @experimental
|
|
301
371
|
*/
|
|
302
372
|
declare const containerBuildTag: (exportName: string) => string;
|
|
303
373
|
/**
|
|
304
374
|
* `defineContainer` is part of the experimental `@lunora/container` API and may change without a major version bump.
|
|
305
|
-
* @experimental
|
|
306
375
|
*/
|
|
307
376
|
declare const defineContainer: (config: ContainerConfig) => ContainerDefinition;
|
|
308
377
|
/**
|
|
309
378
|
* True when a value is a `defineContainer` result (the runtime brand check).
|
|
310
|
-
* @experimental
|
|
311
379
|
*/
|
|
312
380
|
declare const isContainerDefinition: (value: unknown) => value is ContainerDefinition;
|
|
313
381
|
/**
|
|
@@ -315,10 +383,9 @@ declare const isContainerDefinition: (value: unknown) => value is ContainerDefin
|
|
|
315
383
|
* plus every declared secret resolved from the Worker `env`. A declared secret
|
|
316
384
|
* missing from the Worker env fails fast — starting the container without a
|
|
317
385
|
* credential it was promised yields far worse errors downstream.
|
|
318
|
-
* @experimental
|
|
319
386
|
*/
|
|
320
387
|
declare const resolveContainerEnvVariables: (definition: ContainerDefinition, workerEnv: Record<string, unknown>, exportName?: string) => Record<string, string>;
|
|
321
|
-
export {
|
|
388
|
+
export { CONTAINER_EXEC_PATH,
|
|
322
389
|
/**
|
|
323
390
|
* `@lunora/container` — Cloudflare Containers for Lunora.
|
|
324
391
|
*
|
|
@@ -351,7 +418,7 @@ type ContainerBindingSpec, type ContainerConfig, type ContainerDefinition,
|
|
|
351
418
|
* (which pulls in `@cloudflare/containers` → `cloudflare:workers`) lives
|
|
352
419
|
* behind the `@lunora/container/do` subpath.
|
|
353
420
|
*/
|
|
354
|
-
type ContainerEgressControls,
|
|
421
|
+
type ContainerEgressControls, type ContainerExecOptions, type ContainerExecResult,
|
|
355
422
|
/**
|
|
356
423
|
* `@lunora/container` — Cloudflare Containers for Lunora.
|
|
357
424
|
*
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,74 @@
|
|
|
1
|
-
import { D as DurableObjectJurisdiction,
|
|
2
|
-
export type { B as BuildImageSource, c as ContainerInstanceType, d as ContainerReadinessCheck, e as ContainerRollout, f as CustomContainerInstanceType, g as NamedContainerInstanceType, R as RegistryImageSource } from "./packem_shared/jurisdiction.d-
|
|
1
|
+
import { D as DurableObjectJurisdiction, a as ContainerConfig, C as ContainerDefinition, b as ContainerImageSource, N as NormalizedContainerImage } from "./packem_shared/jurisdiction.d-KcQu2ZT_.js";
|
|
2
|
+
export type { B as BuildImageSource, c as ContainerInstanceType, d as ContainerReadinessCheck, e as ContainerRollout, f as CustomContainerInstanceType, g as NamedContainerInstanceType, R as RegistryImageSource } from "./packem_shared/jurisdiction.d-KcQu2ZT_.js";
|
|
3
|
+
/**
|
|
4
|
+
* Per-call options for `ContainerHandle.exec`.
|
|
5
|
+
*/
|
|
6
|
+
interface ContainerExecOptions {
|
|
7
|
+
/** Arguments passed to `command`, unshelled — the container must not concatenate them into a shell string. */
|
|
8
|
+
args?: ReadonlyArray<string>;
|
|
9
|
+
/** Working directory for the command, relative to the container's own root. */
|
|
10
|
+
cwd?: string;
|
|
11
|
+
/** Extra environment for this command only, merged over the container's env. */
|
|
12
|
+
env?: Readonly<Record<string, string>>;
|
|
13
|
+
/**
|
|
14
|
+
* Cap on the response body, in bytes. The whole `{code,stdout,stderr}`
|
|
15
|
+
* document has to be held in memory to be parsed, and a build box or job
|
|
16
|
+
* runner routinely writes tens of megabytes — a single unbounded `exec`
|
|
17
|
+
* that returns more than the isolate's memory limit kills the isolate and
|
|
18
|
+
* every other in-flight request sharing it, not just this call. So a
|
|
19
|
+
* runner that overruns the cap fails the call loudly rather than taking
|
|
20
|
+
* the shard down with it; raise this (or have the runner cap its own
|
|
21
|
+
* output) when a command legitimately produces more.
|
|
22
|
+
*
|
|
23
|
+
* Default {@link DEFAULT_EXEC_MAX_OUTPUT_BYTES} (1MB).
|
|
24
|
+
*/
|
|
25
|
+
maxOutputBytes?: number;
|
|
26
|
+
/**
|
|
27
|
+
* Abort the call. Composed with {@link ContainerExecOptions.timeoutMs} when
|
|
28
|
+
* both are given — whichever fires first wins.
|
|
29
|
+
*/
|
|
30
|
+
signal?: AbortSignal;
|
|
31
|
+
/**
|
|
32
|
+
* Give up after this many ms. Covers the whole call — the request *and*
|
|
33
|
+
* reading the response body, since `fetch` resolves on headers and a
|
|
34
|
+
* runner that answers `200` and then stalls mid-body would otherwise sit
|
|
35
|
+
* past the deadline. Sent to the container as well, so a well-behaved
|
|
36
|
+
* runner can kill the process rather than leak it when the caller walks
|
|
37
|
+
* away.
|
|
38
|
+
*/
|
|
39
|
+
timeoutMs?: number;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The outcome of a `ContainerHandle.exec` call.
|
|
43
|
+
*
|
|
44
|
+
* A non-zero `code` is **not** an error: a command that ran and failed is a
|
|
45
|
+
* result, and the caller decides what to do with it. Only a failure to *run*
|
|
46
|
+
* the command — transport, a non-2xx from the runner, an unparseable body —
|
|
47
|
+
* throws.
|
|
48
|
+
*/
|
|
49
|
+
interface ContainerExecResult {
|
|
50
|
+
/** Process exit code. Non-zero means the command ran and failed. */
|
|
51
|
+
code: number;
|
|
52
|
+
/** Everything the command wrote to stderr. */
|
|
53
|
+
stderr: string;
|
|
54
|
+
/** Everything the command wrote to stdout. */
|
|
55
|
+
stdout: string;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The route `ContainerHandle.exec` POSTs to. Namespaced under
|
|
59
|
+
* `/__lunora/` so it cannot collide with an application route the container
|
|
60
|
+
* already serves — the previous ad-hoc convention was a bare `/exec`, which an
|
|
61
|
+
* app could plausibly own for its own purposes.
|
|
62
|
+
*
|
|
63
|
+
* Exported because it is a *contract*, not an implementation detail: a
|
|
64
|
+
* container image has to serve exactly this route, and `@lunora/agent`'s
|
|
65
|
+
* human-in-the-loop gate has to recognise it. Both had it hard-coded as a
|
|
66
|
+
* second literal, which drifts silently — and on the gate's side, drift
|
|
67
|
+
* un-gates model-chosen command execution.
|
|
68
|
+
*/
|
|
69
|
+
declare const CONTAINER_EXEC_PATH = "/__lunora/exec";
|
|
3
70
|
/**
|
|
4
71
|
* Options for explicitly starting an instance (mirrors `@cloudflare/containers`).
|
|
5
|
-
* @experimental
|
|
6
72
|
*/
|
|
7
73
|
interface ContainerStartOptions {
|
|
8
74
|
/** Override outbound internet access for this start. */
|
|
@@ -16,7 +82,6 @@ interface ContainerStartOptions {
|
|
|
16
82
|
}
|
|
17
83
|
/**
|
|
18
84
|
* A container instance's runtime state, as returned by `getState()`. Structural — the platform adds fields over time.
|
|
19
|
-
* @experimental
|
|
20
85
|
*/
|
|
21
86
|
interface ContainerInstanceState {
|
|
22
87
|
[key: string]: unknown;
|
|
@@ -44,7 +109,6 @@ interface ContainerStubLike {
|
|
|
44
109
|
}
|
|
45
110
|
/**
|
|
46
111
|
* What the client needs from a Durable Object namespace binding.
|
|
47
|
-
* @experimental
|
|
48
112
|
*/
|
|
49
113
|
interface ContainerNamespaceLike {
|
|
50
114
|
get: (id: unknown) => ContainerStubLike;
|
|
@@ -57,9 +121,24 @@ interface ContainerNamespaceLike {
|
|
|
57
121
|
}
|
|
58
122
|
/**
|
|
59
123
|
* A handle on one container instance (one Durable Object).
|
|
60
|
-
* @experimental
|
|
61
124
|
*/
|
|
62
125
|
interface ContainerHandle {
|
|
126
|
+
/**
|
|
127
|
+
* Run a command inside the container and return its exit code and output.
|
|
128
|
+
*
|
|
129
|
+
* A container is an HTTP server, so there is no platform-level exec to call:
|
|
130
|
+
* the command is POSTed to `/__lunora/exec` and the container app
|
|
131
|
+
* serves that route. What this method adds over hand-rolling that fetch is
|
|
132
|
+
* the **contract** — a pinned path, a typed request and response, and the
|
|
133
|
+
* distinction between "the command failed" (a `code`) and "the command could
|
|
134
|
+
* not be run" (a throw). Before it existed every caller invented its own
|
|
135
|
+
* `/exec` convention and read the raw body back as if it were output, which
|
|
136
|
+
* silently turned a runner's 500 into a successful-looking result.
|
|
137
|
+
*
|
|
138
|
+
* The container side must accept `{ args, command, cwd, env, timeoutMs }`
|
|
139
|
+
* and answer `{ code, stdout, stderr }` as JSON.
|
|
140
|
+
*/
|
|
141
|
+
exec: (command: string, options?: ContainerExecOptions) => Promise<ContainerExecResult>;
|
|
63
142
|
/**
|
|
64
143
|
* Send an HTTP (or WebSocket-upgrade) request to the container. A path
|
|
65
144
|
* string (`"/transcode"`) is resolved against a synthetic origin; a full
|
|
@@ -82,7 +161,6 @@ interface ContainerHandle {
|
|
|
82
161
|
* game, a job runner per id) often needs to tear down or inspect the instance
|
|
83
162
|
* rather than wait for `sleepAfter`, so these wrap the container DO's
|
|
84
163
|
* `start`/`stop`/`destroy`/`getState`.
|
|
85
|
-
* @experimental
|
|
86
164
|
*/
|
|
87
165
|
interface ContainerInstanceHandle extends ContainerHandle {
|
|
88
166
|
/** Stop and discard the instance (its ephemeral disk is lost). */
|
|
@@ -117,7 +195,6 @@ interface ContainerInstanceHandle extends ContainerHandle {
|
|
|
117
195
|
* Each maps to the corresponding `@cloudflare/containers` `Container` RPC, so
|
|
118
196
|
* an app can tighten or relax a single instance's allowed/denied hosts after
|
|
119
197
|
* start without redeploying.
|
|
120
|
-
* @experimental
|
|
121
198
|
*/
|
|
122
199
|
interface ContainerEgressControls {
|
|
123
200
|
/** Add one hostname (or glob) to the allow-list. */
|
|
@@ -135,7 +212,6 @@ interface ContainerEgressControls {
|
|
|
135
212
|
}
|
|
136
213
|
/**
|
|
137
214
|
* The per-definition accessor exposed as `ctx.containers.<exportName>`.
|
|
138
|
-
* @experimental
|
|
139
215
|
*/
|
|
140
216
|
interface ContainerAccessor {
|
|
141
217
|
/**
|
|
@@ -178,7 +254,6 @@ interface ContainerAccessor {
|
|
|
178
254
|
}
|
|
179
255
|
/**
|
|
180
256
|
* Tuning for a pooled, retrying container handle. See {@link ContainerAccessor.pool}.
|
|
181
|
-
* @experimental
|
|
182
257
|
*/
|
|
183
258
|
interface PoolOptions {
|
|
184
259
|
/** Total attempts before giving up (each on a freshly-picked instance). Default 3. */
|
|
@@ -195,6 +270,10 @@ interface PoolOptions {
|
|
|
195
270
|
* Whether a *returned* response should be retried on another instance.
|
|
196
271
|
* Defaults to retrying any `5xx`. A thrown error (network/start failure) is
|
|
197
272
|
* always retried regardless of this predicate.
|
|
273
|
+
*
|
|
274
|
+
* Applies to `fetch` only. `exec` retries on the cold-start transients
|
|
275
|
+
* alone, because its caller never chose the request and a command that
|
|
276
|
+
* already ran must not be re-run just because the runner failed afterwards.
|
|
198
277
|
*/
|
|
199
278
|
retryOn?: (response: Response) => boolean;
|
|
200
279
|
/** Pool size to spread picks across. Defaults to the definition's `maxInstances`, else 3. */
|
|
@@ -205,7 +284,6 @@ interface PoolOptions {
|
|
|
205
284
|
* only on the platform's provisioning transients (no-instance / not-listening /
|
|
206
285
|
* rate-limited — see {@link isColdStartTransient}), which is why it's safe by
|
|
207
286
|
* default: those responses mean the request never reached the container.
|
|
208
|
-
* @experimental
|
|
209
287
|
*/
|
|
210
288
|
interface InstanceRetryOptions {
|
|
211
289
|
/**
|
|
@@ -221,7 +299,6 @@ interface InstanceRetryOptions {
|
|
|
221
299
|
}
|
|
222
300
|
/**
|
|
223
301
|
* Wiring info for one definition, emitted by codegen into the generated DO.
|
|
224
|
-
* @experimental
|
|
225
302
|
*/
|
|
226
303
|
interface ContainerBindingSpec {
|
|
227
304
|
/** Durable Object binding name, e.g. `CONTAINER_TRANSCODER`. */
|
|
@@ -240,12 +317,10 @@ interface ContainerBindingSpec {
|
|
|
240
317
|
* `traceparent` (the inbound RPC's W3C trace context, forwarded by the runtime
|
|
241
318
|
* and read off the request by the DO) is stamped onto every outbound container
|
|
242
319
|
* `fetch`, so the container's own spans stitch under the Worker's trace.
|
|
243
|
-
* @experimental
|
|
244
320
|
*/
|
|
245
321
|
declare const createContainerContext: (env: Record<string, unknown>, specs: ReadonlyArray<ContainerBindingSpec>, jurisdiction?: DurableObjectJurisdiction, traceparent?: string) => Record<string, ContainerAccessor>;
|
|
246
322
|
/**
|
|
247
323
|
* A test handler: receives the request plus the targeted instance name.
|
|
248
|
-
* @experimental
|
|
249
324
|
*/
|
|
250
325
|
type ContainerTestHandler = (request: Request, instance: {
|
|
251
326
|
name: string;
|
|
@@ -260,7 +335,6 @@ type ContainerTestHandler = (request: Request, instance: {
|
|
|
260
335
|
* transcoder: (request) => new Response("ok"),
|
|
261
336
|
* });
|
|
262
337
|
* ```
|
|
263
|
-
* @experimental
|
|
264
338
|
*/
|
|
265
339
|
declare const createContainerTestContext: (handlers: Record<string, ContainerTestHandler>) => Record<string, ContainerAccessor>;
|
|
266
340
|
/**
|
|
@@ -272,7 +346,6 @@ declare const createContainerTestContext: (handlers: Record<string, ContainerTes
|
|
|
272
346
|
* `Dockerfile.dev` also counts) is used as-is with its directory as the build
|
|
273
347
|
* context; any other path is treated as the build-context directory and the
|
|
274
348
|
* Dockerfile is expected at `<dir>/Dockerfile`.
|
|
275
|
-
* @experimental
|
|
276
349
|
*/
|
|
277
350
|
declare const normalizeContainerImage: (image: ContainerImageSource) => NormalizedContainerImage;
|
|
278
351
|
/**
|
|
@@ -280,7 +353,6 @@ declare const normalizeContainerImage: (image: ContainerImageSource) => Normaliz
|
|
|
280
353
|
* `transcoder` → `TranscoderContainer`. wrangler's `containers[].class_name`
|
|
281
354
|
* and the Durable Object binding's `class_name` both reference it, so codegen
|
|
282
355
|
* and the config layer MUST derive it identically — always via this helper.
|
|
283
|
-
* @experimental
|
|
284
356
|
*/
|
|
285
357
|
declare const containerClassName: (exportName: string) => string;
|
|
286
358
|
/**
|
|
@@ -288,7 +360,6 @@ declare const containerClassName: (exportName: string) => string;
|
|
|
288
360
|
* `CONTAINER_TRANSCODER`, `imageResizer` → `CONTAINER_IMAGE_RESIZER`. The
|
|
289
361
|
* `CONTAINER_` prefix namespaces these away from `SHARD`/`SESSION`/`SCHEDULER`
|
|
290
362
|
* so a container export can never collide with the built-in bindings.
|
|
291
|
-
* @experimental
|
|
292
363
|
*/
|
|
293
364
|
declare const containerBindingName: (exportName: string) => string;
|
|
294
365
|
/**
|
|
@@ -297,17 +368,14 @@ declare const containerBindingName: (exportName: string) => string;
|
|
|
297
368
|
* it as the wrangler `containers[].image`, and `lunora deploy` builds that tag
|
|
298
369
|
* with Railpack and `wrangler containers push`es it before deploying — so all
|
|
299
370
|
* three derive the tag from this one helper and can never disagree.
|
|
300
|
-
* @experimental
|
|
301
371
|
*/
|
|
302
372
|
declare const containerBuildTag: (exportName: string) => string;
|
|
303
373
|
/**
|
|
304
374
|
* `defineContainer` is part of the experimental `@lunora/container` API and may change without a major version bump.
|
|
305
|
-
* @experimental
|
|
306
375
|
*/
|
|
307
376
|
declare const defineContainer: (config: ContainerConfig) => ContainerDefinition;
|
|
308
377
|
/**
|
|
309
378
|
* True when a value is a `defineContainer` result (the runtime brand check).
|
|
310
|
-
* @experimental
|
|
311
379
|
*/
|
|
312
380
|
declare const isContainerDefinition: (value: unknown) => value is ContainerDefinition;
|
|
313
381
|
/**
|
|
@@ -315,10 +383,9 @@ declare const isContainerDefinition: (value: unknown) => value is ContainerDefin
|
|
|
315
383
|
* plus every declared secret resolved from the Worker `env`. A declared secret
|
|
316
384
|
* missing from the Worker env fails fast — starting the container without a
|
|
317
385
|
* credential it was promised yields far worse errors downstream.
|
|
318
|
-
* @experimental
|
|
319
386
|
*/
|
|
320
387
|
declare const resolveContainerEnvVariables: (definition: ContainerDefinition, workerEnv: Record<string, unknown>, exportName?: string) => Record<string, string>;
|
|
321
|
-
export {
|
|
388
|
+
export { CONTAINER_EXEC_PATH,
|
|
322
389
|
/**
|
|
323
390
|
* `@lunora/container` — Cloudflare Containers for Lunora.
|
|
324
391
|
*
|
|
@@ -351,7 +418,7 @@ type ContainerBindingSpec, type ContainerConfig, type ContainerDefinition,
|
|
|
351
418
|
* (which pulls in `@cloudflare/containers` → `cloudflare:workers`) lives
|
|
352
419
|
* behind the `@lunora/container/do` subpath.
|
|
353
420
|
*/
|
|
354
|
-
type ContainerEgressControls,
|
|
421
|
+
type ContainerEgressControls, type ContainerExecOptions, type ContainerExecResult,
|
|
355
422
|
/**
|
|
356
423
|
* `@lunora/container` — Cloudflare Containers for Lunora.
|
|
357
424
|
*
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createContainerContext as
|
|
1
|
+
import{createContainerContext as o,createContainerTestContext as r}from"./packem_shared/createContainerContext-IYvMMEfq.mjs";import{containerBindingName as a,containerBuildTag as i,containerClassName as C,defineContainer as m,isContainerDefinition as s,normalizeContainerImage as c,resolveContainerEnvVars as f}from"./packem_shared/containerBindingName-D01FopQt.mjs";import{C as l}from"./packem_shared/exec-KlR4eMip.mjs";export{l as CONTAINER_EXEC_PATH,a as containerBindingName,i as containerBuildTag,C as containerClassName,o as createContainerContext,r as createContainerTestContext,m as defineContainer,s as isContainerDefinition,c as normalizeContainerImage,f as resolveContainerEnvVars};
|
package/dist/otel.d.mts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* An attribute value carried on a span or log.
|
|
3
|
-
* @experimental
|
|
4
3
|
*/
|
|
5
4
|
type ContainerAttributeValue = boolean | number | string;
|
|
6
5
|
/**
|
|
@@ -9,7 +8,6 @@ type ContainerAttributeValue = boolean | number | string;
|
|
|
9
8
|
* cancels the response `body` so Node/undici can release the socket for
|
|
10
9
|
* keep-alive reuse instead of leaving it occupied by an unread stream. It reads
|
|
11
10
|
* `ok`/`status` to detect a rejected export and nothing else from the response.
|
|
12
|
-
* @experimental
|
|
13
11
|
*/
|
|
14
12
|
type OtelFetchLike = (input: string, init: {
|
|
15
13
|
body: string;
|
|
@@ -25,7 +23,6 @@ type OtelFetchLike = (input: string, init: {
|
|
|
25
23
|
}>;
|
|
26
24
|
/**
|
|
27
25
|
* A single span the container process asks the exporter to record.
|
|
28
|
-
* @experimental
|
|
29
26
|
*/
|
|
30
27
|
interface ContainerSpanInput {
|
|
31
28
|
/** Attributes attached to the span (rendered under the OTLP `attributes` list). */
|
|
@@ -44,7 +41,6 @@ interface ContainerSpanInput {
|
|
|
44
41
|
}
|
|
45
42
|
/**
|
|
46
43
|
* A single log line the container process asks the exporter to record.
|
|
47
|
-
* @experimental
|
|
48
44
|
*/
|
|
49
45
|
interface ContainerLogInput {
|
|
50
46
|
/** Attributes attached to the log record. */
|
|
@@ -58,7 +54,6 @@ interface ContainerLogInput {
|
|
|
58
54
|
}
|
|
59
55
|
/**
|
|
60
56
|
* Options for {@link createContainerTelemetry}.
|
|
61
|
-
* @experimental
|
|
62
57
|
*/
|
|
63
58
|
interface ContainerTelemetryOptions {
|
|
64
59
|
/**
|
|
@@ -124,7 +119,6 @@ interface ContainerTelemetryOptions {
|
|
|
124
119
|
}
|
|
125
120
|
/**
|
|
126
121
|
* The exporter handle {@link createContainerTelemetry} returns.
|
|
127
|
-
* @experimental
|
|
128
122
|
*/
|
|
129
123
|
interface ContainerTelemetry {
|
|
130
124
|
/** Record one log line (no-op when disabled). */
|
|
@@ -155,7 +149,6 @@ interface ContainerTelemetry {
|
|
|
155
149
|
* `serviceName`, `traceparent`) always fall back to their `LUNORA_*` env var;
|
|
156
150
|
* resource fields (`serviceVersion`, `deploymentEnvironment`) only do so under
|
|
157
151
|
* `detectResources: true`.
|
|
158
|
-
* @experimental
|
|
159
152
|
*/
|
|
160
153
|
declare const createContainerTelemetry: (options?: ContainerTelemetryOptions) => ContainerTelemetry;
|
|
161
154
|
export { type ContainerAttributeValue, type ContainerLogInput, type ContainerSpanInput, type ContainerTelemetry, type ContainerTelemetryOptions, type OtelFetchLike, createContainerTelemetry };
|
package/dist/otel.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* An attribute value carried on a span or log.
|
|
3
|
-
* @experimental
|
|
4
3
|
*/
|
|
5
4
|
type ContainerAttributeValue = boolean | number | string;
|
|
6
5
|
/**
|
|
@@ -9,7 +8,6 @@ type ContainerAttributeValue = boolean | number | string;
|
|
|
9
8
|
* cancels the response `body` so Node/undici can release the socket for
|
|
10
9
|
* keep-alive reuse instead of leaving it occupied by an unread stream. It reads
|
|
11
10
|
* `ok`/`status` to detect a rejected export and nothing else from the response.
|
|
12
|
-
* @experimental
|
|
13
11
|
*/
|
|
14
12
|
type OtelFetchLike = (input: string, init: {
|
|
15
13
|
body: string;
|
|
@@ -25,7 +23,6 @@ type OtelFetchLike = (input: string, init: {
|
|
|
25
23
|
}>;
|
|
26
24
|
/**
|
|
27
25
|
* A single span the container process asks the exporter to record.
|
|
28
|
-
* @experimental
|
|
29
26
|
*/
|
|
30
27
|
interface ContainerSpanInput {
|
|
31
28
|
/** Attributes attached to the span (rendered under the OTLP `attributes` list). */
|
|
@@ -44,7 +41,6 @@ interface ContainerSpanInput {
|
|
|
44
41
|
}
|
|
45
42
|
/**
|
|
46
43
|
* A single log line the container process asks the exporter to record.
|
|
47
|
-
* @experimental
|
|
48
44
|
*/
|
|
49
45
|
interface ContainerLogInput {
|
|
50
46
|
/** Attributes attached to the log record. */
|
|
@@ -58,7 +54,6 @@ interface ContainerLogInput {
|
|
|
58
54
|
}
|
|
59
55
|
/**
|
|
60
56
|
* Options for {@link createContainerTelemetry}.
|
|
61
|
-
* @experimental
|
|
62
57
|
*/
|
|
63
58
|
interface ContainerTelemetryOptions {
|
|
64
59
|
/**
|
|
@@ -124,7 +119,6 @@ interface ContainerTelemetryOptions {
|
|
|
124
119
|
}
|
|
125
120
|
/**
|
|
126
121
|
* The exporter handle {@link createContainerTelemetry} returns.
|
|
127
|
-
* @experimental
|
|
128
122
|
*/
|
|
129
123
|
interface ContainerTelemetry {
|
|
130
124
|
/** Record one log line (no-op when disabled). */
|
|
@@ -155,7 +149,6 @@ interface ContainerTelemetry {
|
|
|
155
149
|
* `serviceName`, `traceparent`) always fall back to their `LUNORA_*` env var;
|
|
156
150
|
* resource fields (`serviceVersion`, `deploymentEnvironment`) only do so under
|
|
157
151
|
* `detectResources: true`.
|
|
158
|
-
* @experimental
|
|
159
152
|
*/
|
|
160
153
|
declare const createContainerTelemetry: (options?: ContainerTelemetryOptions) => ContainerTelemetry;
|
|
161
154
|
export { type ContainerAttributeValue, type ContainerLogInput, type ContainerSpanInput, type ContainerTelemetry, type ContainerTelemetryOptions, type OtelFetchLike, createContainerTelemetry };
|
package/dist/otel.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const
|
|
1
|
+
import{a as U}from"./packem_shared/abort-deadline-jwbW9Ala.mjs";const x={debug:5,error:17,fatal:21,info:9,log:9,trace:1,warn:13},N=e=>`${String(Math.round(e))}000000`,R=e=>{const t=new Uint8Array(e);crypto.getRandomValues(t);let r="";for(const n of t)r+=n.toString(16).padStart(2,"0");return r},b=/^[0-9a-f]+$/,C=e=>{if(e==null)return;const t=e.trim().toLowerCase().split("-"),[r,n,o,s]=t;if(!(t.length<4||r===void 0||r.length!==2||!b.test(r)||r==="ff"||r==="00"&&t.length!==4||n===void 0||o===void 0||s===void 0||s.length!==2||!b.test(s)||n.length!==32||o.length!==16||!b.test(n)||!b.test(o)||n==="00000000000000000000000000000000"||o==="0000000000000000"))return{parentSpanId:o,sampled:(Number.parseInt(s,16)&1)===1,traceId:n}},v=(e,t)=>typeof t=="boolean"?{key:e,value:{boolValue:t}}:typeof t=="number"?Number.isFinite(t)?Number.isSafeInteger(t)?{key:e,value:{intValue:String(t)}}:{key:e,value:{doubleValue:t}}:{key:e,value:{stringValue:String(t)}}:{key:e,value:{stringValue:t}},M=e=>e===void 0?[]:Object.entries(e).map(([t,r])=>v(t,r)),D=(e,t,r)=>{const n={},o=new Map,s=(c,d)=>{const p=c.toLowerCase(),E=o.get(p);E===void 0?(o.set(p,c),n[c]=d):n[E]=d};for(const[c,d]of Object.entries(e))s(c,d);for(const[c,d]of Object.entries(t??{}))s(c,d);return r!==void 0&&r.length>0&&s("authorization",`Bearer ${r}`),n},_=e=>Array.isArray(e)?e:[e],I=(e,t)=>{const r={"service.name":e};for(const[n,o]of Object.entries(t??{}))r[n]=o;return Object.entries(r).map(([n,o])=>v(n,o))},P=(e,t,r,n)=>({resourceSpans:[{resource:{attributes:I(r,n)},scopeSpans:[{scope:{name:t},spans:_(e)}]}]}),H=(e,t,r,n)=>({resourceLogs:[{resource:{attributes:I(r,n)},scopeLogs:[{logRecords:_(e),scope:{name:t}}]}]}),$=e=>{const t={},r=e("SERVICE_VERSION")??e("CF_VERSION_METADATA")??e("VERCEL_GIT_COMMIT_SHA")??e("GITHUB_SHA")??e("COMMIT_SHA");r!==void 0&&(t["service.version"]=r);const n=e("DEPLOYMENT_ENVIRONMENT")??e("ENVIRONMENT")??e("NODE_ENV");return n!==void 0&&(t["deployment.environment"]=n),t},j=(e,t)=>{const r={},n=e("HOSTNAME")??e("COMPUTERNAME");n!==void 0&&(r["host.name"]=n);const o=e("KUBERNETES_POD_NAME")??e("HOSTNAME");return o!==void 0&&e("KUBERNETES_SERVICE_HOST")!==void 0&&(r["k8s.pod.name"]=o),t!==void 0&&Number.isFinite(t)&&(r["process.pid"]=t),r},w=(...e)=>{const t={};for(const r of e)if(r!==void 0)for(const[n,o]of Object.entries(r))t[n]=o;return t},B=1e4,u=e=>process.env[e],F=()=>{const e=typeof process.pid!="number"?void 0:process.pid;return w($(u),j(u,e))},K=e=>e??(typeof globalThis.fetch=="function"?globalThis.fetch:void 0),Y=(e,t,r,n)=>{const o=M(e.attributes);e.error?.type!==void 0&&o.push(v("error.type",e.error.type));const s={attributes:o,endTimeUnixNano:N(e.endMs),kind:1,name:e.name,...r===void 0?{}:{parentSpanId:r.parentSpanId},spanId:R(8),startTimeUnixNano:N(e.startMs),status:e.error===void 0?{code:1}:{code:2,message:e.error.message},traceId:r?.traceId??R(16)};return e.error&&(s.events=[{attributes:[v("exception.type",e.error.type??"Error"),v("exception.message",e.error.message)],name:"exception",timeUnixNano:N(e.endMs)}]),P(s,"@lunora/container",t,n)},G=(e,t,r,n)=>{const o=e.level??"info",s={attributes:M(e.attributes),body:{stringValue:e.message},severityNumber:x[o],severityText:o.toUpperCase(),timeUnixNano:N(e.ts??r)};return H(s,"@lunora/container",t,n)},X=(e={})=>{const t=e.endpoint??u("LUNORA_OTLP_ENDPOINT"),r=t!==void 0&&t.length>0,n=e.token??u("LUNORA_OTLP_TOKEN"),o=e.serviceName??u("LUNORA_SERVICE_NAME")??"lunora-container",s=C(e.traceparent??u("LUNORA_TRACEPARENT")),c=e.timeoutMs??B,d=K(e.fetch),p=D({"content-type":"application/json"},e.headers,n),E=e.detectResources===!0?F():void 0,g={};e.serviceVersion!==void 0&&(g["service.version"]=e.serviceVersion),e.deploymentEnvironment!==void 0&&(g["deployment.environment"]=e.deploymentEnvironment),e.resourceAttributes!==void 0&&Object.assign(g,e.resourceAttributes);const O=w(E,g);let l=t??"";for(;l.endsWith("/");)l=l.slice(0,-1);const L=`${l}/v1/traces`,V=`${l}/v1/logs`,y=new Set,A=(a,h)=>{if(d===void 0){e.onError?.(new TypeError("createContainerTelemetry: no `fetch` available — pass `fetch` in options for this runtime."));return}const f=(async()=>{const i=U(void 0,c,()=>new DOMException(`OTLP export to ${a} timed out after ${String(c)}ms`,"TimeoutError"));try{const m=await d(a,{body:JSON.stringify(h),headers:p,method:"POST",signal:i.signal});m.ok||e.onError?.(new Error(`createContainerTelemetry: OTLP export to ${a} failed with status ${String(m.status)}.`));try{await m.body?.cancel()}catch{}}catch(m){e.onError?.(m)}finally{i.dispose()}})().finally(()=>{y.delete(f)});y.add(f)},T=a=>{r&&A(L,Y(a,o,s,O))};return{emitLog:a=>{r&&A(V,G(a,o,Date.now(),O))},emitSpan:T,enabled:r,flush:async()=>{await Promise.allSettled(y)},trace:async(a,h,S)=>{const f=Date.now();try{const i=await h();return T({attributes:S,endMs:Date.now(),name:a,startMs:f}),i}catch(i){throw T({attributes:S,endMs:Date.now(),error:{message:i instanceof Error?i.message:String(i),type:i instanceof Error?i.name:void 0},name:a,startMs:f}),i}}}};export{X as createContainerTelemetry};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./abort-deadline-jwbW9Ala.mjs";import{C as r,e as t}from"./exec-KlR4eMip.mjs";export{r as CONTAINER_EXEC_PATH,t as execViaFetch};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const i=(o,e,t)=>{if(e===void 0)return{dispose:()=>{},signal:o};const r=new AbortController,n=setTimeout(()=>{r.abort(t())},e);return{dispose:()=>{clearTimeout(n)},signal:o===void 0?r.signal:AbortSignal.any([o,r.signal])}};export{i as a};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{LunoraError as R}from"@lunora/errors";import{containerBindingName as k}from"./containerBindingName-D01FopQt.mjs";import{e as F,r as U}from"./exec-KlR4eMip.mjs";import{a as I}from"./jurisdiction-DtE9s70w.mjs";const N=3,D=3e4,q=3,W=500,j="cf-container-target-port",v="__lunora",p=t=>t.split("/").find(e=>e!=="")??"",C=(t,e)=>{const o=typeof t=="string"?t:t.url,n=URL.parse(o.startsWith("/")?`http://container${o}`:o);if(n===null)return;const{pathname:r}=n;let s=r;try{s=decodeURIComponent(r)}catch{}if(p(r)===v||p(s)===v)throw new R("BAD_REQUEST",`${e}: \`/${v}/*\` is reserved for Lunora's own container routes and cannot be reached with \`fetch\`. Use \`exec\` to run a command.`)},P=(t,e,o,n)=>{const r=typeof t=="string"&&t.startsWith("/")?new Request(`http://container${t}`,e):new Request(t,e);return o!==void 0&&r.headers.set(j,String(o)),n!==void 0&&r.headers.set("traceparent",n),r},L=async t=>{t<=0||await new Promise(e=>{setTimeout(e,t)})},Y=/no container instance|not listening|try again later|rate.?limit|provision/i,z="no Container instance available",G="Failed to start container:",K=1024,V=async t=>(await U(t.clone().body,K)).text,O=t=>t instanceof Error&&Y.test(t.message),H=async t=>{if(t.status===429)return!0;if(t.status!==500&&t.status!==503)return!1;try{const e=await V(t);return e.includes(z)||e.startsWith(G)}catch{return!1}},w=t=>`ctx.containers.${t.exportName}`,S=(t,e,o={},n,r)=>{const s=Math.max(1,o.attempts??q),a=o.backoffMs??W,A=o.maxBackoffMs??D,g=async(i,f)=>{const T=typeof i=="string"?s:1;let l;for(let d=0;d<T;d+=1){const u=d===T-1;d>0&&await L(Math.min(a*2**(d-1),A));try{const m=await t(P(i,f,n,r));if(u||!await H(m))return m}catch(m){if(l=m,u||!O(m))throw m}}throw l instanceof Error?l:new Error("ctx.containers: cold-start retry exhausted")};return{exec:F(g,e),fetch:async(i,f)=>(C(i,e),g(i,f)),port:i=>S(t,e,o,i,r)}},x=(t,e,o,n,r)=>S(async s=>t.get(t.idFromName(e)).fetch(s),o,n,void 0,r),c=async(t,e,o,n)=>{const r=t[e];if(typeof r!="function")throw new TypeError(`ctx.containers: the "${o}" container DO does not expose ${e}() — is @lunora/container/do up to date?`);return r(n)},J=(t,e)=>({allow:async o=>c(t(),"allowHost",e,o),deny:async o=>c(t(),"denyHost",e,o),removeAllowed:async o=>c(t(),"removeAllowedHost",e,o),removeDenied:async o=>c(t(),"removeDeniedHost",e,o),setAllowed:async o=>c(t(),"setAllowedHosts",e,[...o]),setDenied:async o=>c(t(),"setDeniedHosts",e,[...o])}),b=(t,e,o,n,r)=>{const s=()=>t.get(t.idFromName(o));return{...S(async a=>s().fetch(a),w(e),n,void 0,r),destroy:async()=>c(s(),"destroy",e.binding),egress:J(s,e.binding),getState:async()=>c(s(),"getState",e.binding),renewActivityTimeout:async()=>c(s(),"renewActivityTimeout",e.binding),start:async a=>c(s(),"start",e.binding,a),stop:async a=>c(s(),"stop",e.binding,a)}},B=t=>`pool-${String(Math.floor(Math.random()*t))}`,Q=t=>t.status>=500,M=(t,e,o={},n,r)=>{const s=o.size??e.maxInstances??N,a=Math.max(1,o.attempts??3),A=o.backoffMs??100,g=o.maxBackoffMs??D,i=`${w(e)}.pool()`,f=(l,d)=>async(u,m)=>{const E=typeof u=="string"?a:1;let _;for(let h=0;h<E;h+=1){h>0&&await L(Math.min(A*2**(h-1),g));const $=P(u,m,n,r);try{const y=await t.get(t.idFromName(B(s))).fetch($);if(h===E-1||!await l(y))return y}catch(y){if(_=y,!d(y))throw y}}throw _ instanceof Error?_:new Error(`ctx.containers.${e.exportName}.pool(): all ${String(E)} attempts failed`)},T=f(o.retryOn??Q,()=>!0);return{exec:F(f(H,O),i),fetch:async(l,d)=>(C(l,i),T(l,d)),port:l=>M(t,e,o,l,r)}},X=(t,e,o)=>({any:(n,r)=>x(t,B(n??e.maxInstances??N),w(e),r,o),get:(n,r)=>b(t,e,n,r,o),pool:n=>M(t,e,n,void 0,o)}),Z=t=>{const e=()=>{throw new R("INTERNAL",`ctx.containers.${t.exportName}: no "${t.binding}" Durable Object binding found. Run \`lunora dev\` (or \`lunora deploy\`) to reconcile wrangler.jsonc, and make sure the worker entry re-exports the generated container classes.`)};return{any:e,get:e,pool:e}},st=(t,e,o,n)=>{const r={};for(const s of e){const a=t[s.binding];r[s.exportName]=a&&typeof a.idFromName=="function"&&typeof a.get=="function"?X(I(a,o),s,n):Z(s)}return r},tt=t=>{const e=o=>({allowHost:()=>Promise.resolve(),denyHost:()=>Promise.resolve(),destroy:()=>Promise.resolve(),fetch:n=>Promise.resolve(t(n,{name:o})),getState:()=>Promise.resolve({lastChange:0}),removeAllowedHost:()=>Promise.resolve(),removeDeniedHost:()=>Promise.resolve(),renewActivityTimeout:()=>Promise.resolve(),setAllowedHosts:()=>Promise.resolve(),setDeniedHosts:()=>Promise.resolve(),start:()=>Promise.resolve(),stop:()=>Promise.resolve()});return{get:o=>e(String(o)),idFromName:o=>o}},at=t=>{const e={};for(const[o,n]of Object.entries(t)){const r=tt(n),s={binding:k(o),exportName:o};e[o]={any:()=>x(r,"pool-0",w(s),{attempts:1}),get:a=>b(r,s,a,{attempts:1}),pool:()=>x(r,"pool-0",`${w(s)}.pool()`,{attempts:1})}}return e};export{st as createContainerContext,at as createContainerTestContext};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{LunoraError as d}from"@lunora/errors";import{a as E}from"./abort-deadline-jwbW9Ala.mjs";const w=async(o,t,r)=>{if(o===null)return{overflowed:!1,text:""};const e=o.getReader(),n=new TextDecoder,a=()=>{e.cancel().catch(()=>{})};r?.addEventListener("abort",a);let s="",c=0,u=!1;try{for(;r?.aborted!==!0;){const{done:f,value:i}=await e.read();if(f)break;if(c+i.byteLength>t){u=!0,s+=n.decode(i.subarray(0,t-c),{stream:!0});break}c+=i.byteLength,s+=n.decode(i,{stream:!0})}s+=n.decode()}finally{r?.removeEventListener("abort",a),e.cancel().catch(()=>{})}if(r?.aborted===!0)throw r.reason;return{overflowed:u,text:s}},l="/__lunora/exec",h=512,b=1e6,v="<redacted>",x=(o,t)=>{if(t===void 0)return o;let r=o;for(const e of Object.values(t))e.length>0&&(r=r.split(e).join(v));return r},m=o=>{if(o===null||typeof o!="object")return;const{code:t,stderr:r,stdout:e}=o;if(typeof t=="number")return{code:t,stderr:typeof r=="string"?r:"",stdout:typeof e=="string"?e:""}},g=(o,t)=>E(o,t,()=>new d("INTERNAL",`ctx.containers: exec timed out after ${String(t)}ms`)),N=(o,t)=>async(r,e={})=>{if(typeof r!="string"||r.length===0)throw new d("BAD_REQUEST",`${t}: exec requires a non-empty \`command\``);const n=e.maxOutputBytes??b;if(!Number.isSafeInteger(n)||n<=0)throw new d("BAD_REQUEST",`${t}: exec \`maxOutputBytes\` must be a positive whole number of bytes, got ${String(n)}`);const a=g(e.signal,e.timeoutMs);try{const s=await o(l,{body:JSON.stringify({args:e.args??[],command:r,...e.cwd===void 0?{}:{cwd:e.cwd},...e.env===void 0?{}:{env:e.env},...e.timeoutMs===void 0?{}:{timeoutMs:e.timeoutMs}}),headers:{"content-type":"application/json"},method:"POST",...a.signal===void 0?{}:{signal:a.signal}});if(!s.ok){const i=await w(s.body,h,a.signal).then(y=>x(y.text,e.env),()=>"<unreadable body>");throw new d("INTERNAL",`${t}: exec failed — the container answered ${String(s.status)} for POST ${l}. Does it serve that route? Body: ${i}`)}const c=await w(s.body,n,a.signal);if(c.overflowed)throw new d("INTERNAL",`${t}: exec response exceeded ${String(n)} bytes and was abandoned — it is buffered whole to be parsed, so an unbounded one would exhaust the isolate. Cap the command's output, or raise \`maxOutputBytes\`.`);let u;try{u=JSON.parse(c.text)}catch{throw new d("INTERNAL",`${t}: exec response was not JSON — expected {"code","stdout","stderr"} from POST ${l}`)}const f=m(u);if(f===void 0)throw new d("INTERNAL",`${t}: exec response is missing a numeric \`code\` — expected {"code","stdout","stderr"} from POST ${l}`);return f}finally{a.dispose()}};export{l as C,N as e,w as r};
|
|
@@ -7,14 +7,12 @@
|
|
|
7
7
|
*/
|
|
8
8
|
/**
|
|
9
9
|
* Named instance types Cloudflare Containers provides.
|
|
10
|
-
* @experimental
|
|
11
10
|
*/
|
|
12
11
|
type NamedContainerInstanceType = "basic" | "lite" | "standard-1" | "standard-2" | "standard-3" | "standard-4";
|
|
13
12
|
/**
|
|
14
13
|
* A custom instance type. Cloudflare's bounds at the time of writing: up to
|
|
15
14
|
* 4 vCPU, 12 GiB memory, 20 GB disk, ≥ 3 GiB memory per vCPU and ≤ 2 GB disk
|
|
16
15
|
* per GiB memory. The config-layer validator enforces the documented ranges.
|
|
17
|
-
* @experimental
|
|
18
16
|
*/
|
|
19
17
|
interface CustomContainerInstanceType {
|
|
20
18
|
/** Disk in MB. Cloudflare's default is 2000 (2 GB). */
|
|
@@ -26,12 +24,10 @@ interface CustomContainerInstanceType {
|
|
|
26
24
|
}
|
|
27
25
|
/**
|
|
28
26
|
* `ContainerInstanceType` is part of the experimental `@lunora/container` API and may change without a major version bump.
|
|
29
|
-
* @experimental
|
|
30
27
|
*/
|
|
31
28
|
type ContainerInstanceType = CustomContainerInstanceType | NamedContainerInstanceType;
|
|
32
29
|
/**
|
|
33
30
|
* Rolling-deploy tuning for a container.
|
|
34
|
-
* @experimental
|
|
35
31
|
*/
|
|
36
32
|
interface ContainerRollout {
|
|
37
33
|
/** Seconds an active instance runs before it's eligible for update (wrangler `rollout_active_grace_period`). */
|
|
@@ -43,7 +39,6 @@ interface ContainerRollout {
|
|
|
43
39
|
* A pre-built image pulled from a registry — the Cloudflare Registry, Docker
|
|
44
40
|
* Hub, or Amazon ECR (the registries `wrangler deploy` supports). The
|
|
45
41
|
* reference must be fully qualified, e.g. `docker.io/acme/transcoder:1.4`.
|
|
46
|
-
* @experimental
|
|
47
42
|
*/
|
|
48
43
|
interface RegistryImageSource {
|
|
49
44
|
registry: string;
|
|
@@ -53,7 +48,6 @@ interface RegistryImageSource {
|
|
|
53
48
|
* source directory and `lunora deploy` builds an OCI image with Railpack
|
|
54
49
|
* (needs a BuildKit instance) and pushes it to the Cloudflare Registry before
|
|
55
50
|
* wrangler runs. Opt-in — the Dockerfile path is the zero-extra-deps default.
|
|
56
|
-
* @experimental
|
|
57
51
|
*/
|
|
58
52
|
interface BuildImageSource {
|
|
59
53
|
build: string;
|
|
@@ -63,7 +57,6 @@ interface BuildImageSource {
|
|
|
63
57
|
* either a directory containing a `Dockerfile` (normalized to
|
|
64
58
|
* `<dir>/Dockerfile` with the directory as the build context) or a path to
|
|
65
59
|
* the Dockerfile itself — while `{ registry }` is a pre-built image reference.
|
|
66
|
-
* @experimental
|
|
67
60
|
*/
|
|
68
61
|
type ContainerImageSource = BuildImageSource | RegistryImageSource | string;
|
|
69
62
|
/**
|
|
@@ -76,7 +69,6 @@ type ContainerImageSource = BuildImageSource | RegistryImageSource | string;
|
|
|
76
69
|
* functions), so codegen and the config layer can read it without evaluating
|
|
77
70
|
* code. (Upstream cloudflare/containers#188 expresses the same idea as handler
|
|
78
71
|
* functions; the Lunora config is data-only, so it's modelled as descriptors.)
|
|
79
|
-
* @experimental
|
|
80
72
|
*/
|
|
81
73
|
interface ContainerReadinessCheck {
|
|
82
74
|
/** HTTP path probed on the container, e.g. `"/ready"` (a leading slash is optional). */
|
|
@@ -88,7 +80,6 @@ interface ContainerReadinessCheck {
|
|
|
88
80
|
}
|
|
89
81
|
/**
|
|
90
82
|
* `ContainerConfig` is part of the experimental `@lunora/container` API and may change without a major version bump.
|
|
91
|
-
* @experimental
|
|
92
83
|
*/
|
|
93
84
|
interface ContainerConfig {
|
|
94
85
|
/**
|
|
@@ -246,7 +237,6 @@ interface ContainerConfig {
|
|
|
246
237
|
/**
|
|
247
238
|
* The value `defineContainer` returns: the validated config plus a brand the
|
|
248
239
|
* codegen discovery and the generated Container DO class key on.
|
|
249
|
-
* @experimental
|
|
250
240
|
*/
|
|
251
241
|
interface ContainerDefinition extends ContainerConfig {
|
|
252
242
|
/** Brand marking a value as a Lunora container definition. */
|
|
@@ -254,7 +244,6 @@ interface ContainerDefinition extends ContainerConfig {
|
|
|
254
244
|
}
|
|
255
245
|
/**
|
|
256
246
|
* A normalized image source, as written into `wrangler.jsonc`.
|
|
257
|
-
* @experimental
|
|
258
247
|
*/
|
|
259
248
|
type NormalizedContainerImage = {
|
|
260
249
|
/** Build context directory (wrangler `image_build_context`). */
|
|
@@ -275,7 +264,6 @@ type NormalizedContainerImage = {
|
|
|
275
264
|
* Cloudflare Durable Object data-residency jurisdiction. Widening union —
|
|
276
265
|
* Cloudflare adds values over time.
|
|
277
266
|
* @see https://developers.cloudflare.com/durable-objects/reference/data-location/
|
|
278
|
-
* @experimental
|
|
279
267
|
*/
|
|
280
268
|
type DurableObjectJurisdiction = "eu" | "fedramp" | "us";
|
|
281
|
-
export { BuildImageSource as B,
|
|
269
|
+
export { BuildImageSource as B, ContainerDefinition as C, DurableObjectJurisdiction as D, NormalizedContainerImage as N, RegistryImageSource as R, ContainerConfig as a, ContainerImageSource as b, ContainerInstanceType as c, ContainerReadinessCheck as d, ContainerRollout as e, CustomContainerInstanceType as f, NamedContainerInstanceType as g };
|
|
@@ -7,14 +7,12 @@
|
|
|
7
7
|
*/
|
|
8
8
|
/**
|
|
9
9
|
* Named instance types Cloudflare Containers provides.
|
|
10
|
-
* @experimental
|
|
11
10
|
*/
|
|
12
11
|
type NamedContainerInstanceType = "basic" | "lite" | "standard-1" | "standard-2" | "standard-3" | "standard-4";
|
|
13
12
|
/**
|
|
14
13
|
* A custom instance type. Cloudflare's bounds at the time of writing: up to
|
|
15
14
|
* 4 vCPU, 12 GiB memory, 20 GB disk, ≥ 3 GiB memory per vCPU and ≤ 2 GB disk
|
|
16
15
|
* per GiB memory. The config-layer validator enforces the documented ranges.
|
|
17
|
-
* @experimental
|
|
18
16
|
*/
|
|
19
17
|
interface CustomContainerInstanceType {
|
|
20
18
|
/** Disk in MB. Cloudflare's default is 2000 (2 GB). */
|
|
@@ -26,12 +24,10 @@ interface CustomContainerInstanceType {
|
|
|
26
24
|
}
|
|
27
25
|
/**
|
|
28
26
|
* `ContainerInstanceType` is part of the experimental `@lunora/container` API and may change without a major version bump.
|
|
29
|
-
* @experimental
|
|
30
27
|
*/
|
|
31
28
|
type ContainerInstanceType = CustomContainerInstanceType | NamedContainerInstanceType;
|
|
32
29
|
/**
|
|
33
30
|
* Rolling-deploy tuning for a container.
|
|
34
|
-
* @experimental
|
|
35
31
|
*/
|
|
36
32
|
interface ContainerRollout {
|
|
37
33
|
/** Seconds an active instance runs before it's eligible for update (wrangler `rollout_active_grace_period`). */
|
|
@@ -43,7 +39,6 @@ interface ContainerRollout {
|
|
|
43
39
|
* A pre-built image pulled from a registry — the Cloudflare Registry, Docker
|
|
44
40
|
* Hub, or Amazon ECR (the registries `wrangler deploy` supports). The
|
|
45
41
|
* reference must be fully qualified, e.g. `docker.io/acme/transcoder:1.4`.
|
|
46
|
-
* @experimental
|
|
47
42
|
*/
|
|
48
43
|
interface RegistryImageSource {
|
|
49
44
|
registry: string;
|
|
@@ -53,7 +48,6 @@ interface RegistryImageSource {
|
|
|
53
48
|
* source directory and `lunora deploy` builds an OCI image with Railpack
|
|
54
49
|
* (needs a BuildKit instance) and pushes it to the Cloudflare Registry before
|
|
55
50
|
* wrangler runs. Opt-in — the Dockerfile path is the zero-extra-deps default.
|
|
56
|
-
* @experimental
|
|
57
51
|
*/
|
|
58
52
|
interface BuildImageSource {
|
|
59
53
|
build: string;
|
|
@@ -63,7 +57,6 @@ interface BuildImageSource {
|
|
|
63
57
|
* either a directory containing a `Dockerfile` (normalized to
|
|
64
58
|
* `<dir>/Dockerfile` with the directory as the build context) or a path to
|
|
65
59
|
* the Dockerfile itself — while `{ registry }` is a pre-built image reference.
|
|
66
|
-
* @experimental
|
|
67
60
|
*/
|
|
68
61
|
type ContainerImageSource = BuildImageSource | RegistryImageSource | string;
|
|
69
62
|
/**
|
|
@@ -76,7 +69,6 @@ type ContainerImageSource = BuildImageSource | RegistryImageSource | string;
|
|
|
76
69
|
* functions), so codegen and the config layer can read it without evaluating
|
|
77
70
|
* code. (Upstream cloudflare/containers#188 expresses the same idea as handler
|
|
78
71
|
* functions; the Lunora config is data-only, so it's modelled as descriptors.)
|
|
79
|
-
* @experimental
|
|
80
72
|
*/
|
|
81
73
|
interface ContainerReadinessCheck {
|
|
82
74
|
/** HTTP path probed on the container, e.g. `"/ready"` (a leading slash is optional). */
|
|
@@ -88,7 +80,6 @@ interface ContainerReadinessCheck {
|
|
|
88
80
|
}
|
|
89
81
|
/**
|
|
90
82
|
* `ContainerConfig` is part of the experimental `@lunora/container` API and may change without a major version bump.
|
|
91
|
-
* @experimental
|
|
92
83
|
*/
|
|
93
84
|
interface ContainerConfig {
|
|
94
85
|
/**
|
|
@@ -246,7 +237,6 @@ interface ContainerConfig {
|
|
|
246
237
|
/**
|
|
247
238
|
* The value `defineContainer` returns: the validated config plus a brand the
|
|
248
239
|
* codegen discovery and the generated Container DO class key on.
|
|
249
|
-
* @experimental
|
|
250
240
|
*/
|
|
251
241
|
interface ContainerDefinition extends ContainerConfig {
|
|
252
242
|
/** Brand marking a value as a Lunora container definition. */
|
|
@@ -254,7 +244,6 @@ interface ContainerDefinition extends ContainerConfig {
|
|
|
254
244
|
}
|
|
255
245
|
/**
|
|
256
246
|
* A normalized image source, as written into `wrangler.jsonc`.
|
|
257
|
-
* @experimental
|
|
258
247
|
*/
|
|
259
248
|
type NormalizedContainerImage = {
|
|
260
249
|
/** Build context directory (wrangler `image_build_context`). */
|
|
@@ -275,7 +264,6 @@ type NormalizedContainerImage = {
|
|
|
275
264
|
* Cloudflare Durable Object data-residency jurisdiction. Widening union —
|
|
276
265
|
* Cloudflare adds values over time.
|
|
277
266
|
* @see https://developers.cloudflare.com/durable-objects/reference/data-location/
|
|
278
|
-
* @experimental
|
|
279
267
|
*/
|
|
280
268
|
type DurableObjectJurisdiction = "eu" | "fedramp" | "us";
|
|
281
|
-
export { BuildImageSource as B,
|
|
269
|
+
export { BuildImageSource as B, ContainerDefinition as C, DurableObjectJurisdiction as D, NormalizedContainerImage as N, RegistryImageSource as R, ContainerConfig as a, ContainerImageSource as b, ContainerInstanceType as c, ContainerReadinessCheck as d, ContainerRollout as e, CustomContainerInstanceType as f, NamedContainerInstanceType as g };
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{LunoraError as F}from"@lunora/errors";import{containerBindingName as p}from"./containerBindingName-D01FopQt.mjs";import{a as N}from"./jurisdiction-DtE9s70w.mjs";const h=3,v=3e4,C=3,O=500,P="cf-container-target-port",_=(t,e,o,n)=>{const r=typeof t=="string"&&t.startsWith("/")?new Request(`http://container${t}`,e):new Request(t,e);return o!==void 0&&r.headers.set(P,String(o)),n!==void 0&&r.headers.set("traceparent",n),r},E=async t=>{t<=0||await new Promise(e=>{setTimeout(e,t)})},H=/no container instance|not listening|try again later|rate.?limit|provision/i,L="no Container instance available",b="Failed to start container:",B=1024,M=async t=>{const e=t.clone().body;if(e===null)return"";const o=e.getReader(),n=new TextDecoder;let r="";try{for(;r.length<B;){const{done:s,value:a}=await o.read();if(s)break;r+=n.decode(a,{stream:!0})}}finally{await o.cancel()}return r},k=t=>t instanceof Error&&H.test(t.message),$=async t=>{if(t.status===429)return!0;if(t.status!==500&&t.status!==503)return!1;try{const e=await M(t);return e.includes(L)||e.startsWith(b)}catch{return!1}},A=(t,e={},o,n)=>{const r=Math.max(1,e.attempts??C),s=e.backoffMs??O,a=e.maxBackoffMs??v;return{fetch:async(m,T)=>{const u=typeof m=="string"?r:1;let l;for(let d=0;d<u;d+=1){const f=d===u-1;d>0&&await E(Math.min(s*2**(d-1),a));try{const i=await t(_(m,T,o,n));if(f||!await $(i))return i}catch(i){if(l=i,f||!k(i))throw i}}throw l instanceof Error?l:new Error("ctx.containers: cold-start retry exhausted")},port:m=>A(t,e,m,n)}},w=(t,e,o,n)=>A(async r=>t.get(t.idFromName(e)).fetch(r),o,void 0,n),c=async(t,e,o,n)=>{const r=t[e];if(typeof r!="function")throw new TypeError(`ctx.containers: the "${o}" container DO does not expose ${e}() — is @lunora/container/do up to date?`);return r(n)},I=(t,e)=>({allow:async o=>c(t(),"allowHost",e,o),deny:async o=>c(t(),"denyHost",e,o),removeAllowed:async o=>c(t(),"removeAllowedHost",e,o),removeDenied:async o=>c(t(),"removeDeniedHost",e,o),setAllowed:async o=>c(t(),"setAllowedHosts",e,[...o]),setDenied:async o=>c(t(),"setDeniedHosts",e,[...o])}),S=(t,e,o,n,r)=>{const s=()=>t.get(t.idFromName(o));return{...A(async a=>s().fetch(a),n,void 0,r),destroy:async()=>c(s(),"destroy",e.binding),egress:I(s,e.binding),getState:async()=>c(s(),"getState",e.binding),renewActivityTimeout:async()=>c(s(),"renewActivityTimeout",e.binding),start:async a=>c(s(),"start",e.binding,a),stop:async a=>c(s(),"stop",e.binding,a)}},x=t=>`pool-${String(Math.floor(Math.random()*t))}`,q=t=>t.status>=500,R=(t,e,o={},n,r)=>{const s=o.size??e.maxInstances??h,a=Math.max(1,o.attempts??3),m=o.backoffMs??100,T=o.maxBackoffMs??v,u=o.retryOn??q;return{fetch:async(l,d)=>{const f=typeof l=="string"?a:1;let i;for(let y=0;y<f;y+=1){y>0&&await E(Math.min(m*2**(y-1),T));const D=_(l,d,n,r);try{const g=await t.get(t.idFromName(x(s))).fetch(D);if(y===f-1||!u(g))return g}catch(g){i=g}}throw i instanceof Error?i:new Error(`ctx.containers.${e.exportName}.pool(): all ${String(f)} attempts failed`)},port:l=>R(t,e,o,l,r)}},U=(t,e,o)=>({any:(n,r)=>w(t,x(n??e.maxInstances??h),r,o),get:(n,r)=>S(t,e,n,r,o),pool:n=>R(t,e,n,void 0,o)}),j=t=>{const e=()=>{throw new F("INTERNAL",`ctx.containers.${t.exportName}: no "${t.binding}" Durable Object binding found. Run \`lunora dev\` (or \`lunora deploy\`) to reconcile wrangler.jsonc, and make sure the worker entry re-exports the generated container classes.`)};return{any:e,get:e,pool:e}},G=(t,e,o,n)=>{const r={};for(const s of e){const a=t[s.binding];r[s.exportName]=a&&typeof a.idFromName=="function"&&typeof a.get=="function"?U(N(a,o),s,n):j(s)}return r},Y=t=>{const e=o=>({allowHost:()=>Promise.resolve(),denyHost:()=>Promise.resolve(),destroy:()=>Promise.resolve(),fetch:n=>Promise.resolve(t(n,{name:o})),getState:()=>Promise.resolve({lastChange:0}),removeAllowedHost:()=>Promise.resolve(),removeDeniedHost:()=>Promise.resolve(),renewActivityTimeout:()=>Promise.resolve(),setAllowedHosts:()=>Promise.resolve(),setDeniedHosts:()=>Promise.resolve(),start:()=>Promise.resolve(),stop:()=>Promise.resolve()});return{get:o=>e(String(o)),idFromName:o=>o}},J=t=>{const e={};for(const[o,n]of Object.entries(t)){const r=Y(n),s={binding:p(o)};e[o]={any:()=>w(r,"pool-0",{attempts:1}),get:a=>S(r,s,a,{attempts:1}),pool:()=>w(r,"pool-0",{attempts:1})}}return e};export{G as createContainerContext,J as createContainerTestContext};
|