@lunora/container 1.0.0-alpha.8 → 1.0.0-alpha.80

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.
Files changed (36) hide show
  1. package/README.md +24 -5
  2. package/dist/bridge.d.mts +43 -33
  3. package/dist/bridge.d.ts +43 -33
  4. package/dist/bridge.mjs +1 -79
  5. package/dist/do/index.d.mts +353 -91
  6. package/dist/do/index.d.ts +353 -91
  7. package/dist/do/index.mjs +1 -328
  8. package/dist/index.d.mts +584 -153
  9. package/dist/index.d.ts +584 -153
  10. package/dist/index.mjs +1 -2
  11. package/dist/otel.d.mts +217 -0
  12. package/dist/otel.d.ts +217 -0
  13. package/dist/otel.mjs +1 -0
  14. package/dist/packem_shared/CONTAINER_EXEC_PATH-7Hcd6-zs.mjs +1 -0
  15. package/dist/packem_shared/CUSTOM_INSTANCE_MIN_MEMORY_MIB_PER_VCPU-D8Hy9Rxv.mjs +1 -0
  16. package/dist/packem_shared/ContainerProxy-CfXjS6ZI.mjs +27 -0
  17. package/dist/packem_shared/DURABLE_OBJECT_POLICY_FORBIDDEN_KEYS-gYi8RBUO.mjs +1 -0
  18. package/dist/packem_shared/LunoraSandboxContainer-DPnhJUu1.mjs +1 -0
  19. package/dist/packem_shared/abort-deadline-jwbW9Ala.mjs +1 -0
  20. package/dist/packem_shared/createContainerContext-p6QxWjsm.mjs +1 -0
  21. package/dist/packem_shared/exec-BYLW70sR.mjs +1 -0
  22. package/dist/packem_shared/jurisdiction-DtE9s70w.mjs +1 -0
  23. package/dist/packem_shared/jurisdiction.d-BwpauCoI.d.mts +471 -0
  24. package/dist/packem_shared/jurisdiction.d-BwpauCoI.d.ts +471 -0
  25. package/dist/packem_shared/sampling-D21rpu_s.mjs +1 -0
  26. package/dist/packem_shared/sandbox-types.d-DXlHnsEg.d.mts +110 -0
  27. package/dist/packem_shared/sandbox-types.d-DXlHnsEg.d.ts +110 -0
  28. package/dist/sandbox/index.d.mts +66 -0
  29. package/dist/sandbox/index.d.ts +66 -0
  30. package/dist/sandbox/index.mjs +1 -0
  31. package/package.json +12 -2
  32. package/dist/packem_shared/ContainerProxy-DWqUX_re.mjs +0 -1474
  33. package/dist/packem_shared/containerBindingName-D5KTrblH.mjs +0 -218
  34. package/dist/packem_shared/createContainerContext-Cibbh7AE.mjs +0 -228
  35. package/dist/packem_shared/types.d-BlNwNY44.d.mts +0 -250
  36. package/dist/packem_shared/types.d-BlNwNY44.d.ts +0 -250
package/README.md CHANGED
@@ -10,6 +10,8 @@
10
10
 
11
11
  <!-- END_PACKAGE_OG_IMAGE_PLACEHOLDER -->
12
12
 
13
+ > **Experimental** — this package is outside the Lunora 1.0 stability promise: its API may change in any release, without a major version bump.
14
+
13
15
  <br />
14
16
 
15
17
  <div align="center">
@@ -125,7 +127,7 @@ export const worker = defineContainer({
125
127
 
126
128
  ### Secrets and Secrets Store
127
129
 
128
- `secrets` forwards plain Worker secrets into the container env; `secretsStore` maps a _container env-var name → Cloudflare [Secrets Store](https://developers.cloudflare.com/secrets-store/) binding name_ and resolves each with its async `.get()` at first start (memoised). A collision with `env`/`secrets` is rejected at authoring time; a missing binding fails the start — the same fail-closed stance as `secrets`. Like `env`/`secrets`, these injected values only apply to implicit starts or a bare `start()`; a per-instance `start({ envVars })` replaces the env set wholesale (and skips Secrets Store resolution entirely).
130
+ `secrets` forwards plain Worker secrets into the container env; `secretsStore` maps a _container env-var name → Cloudflare [Secrets Store](https://developers.cloudflare.com/secrets-store/) binding name_ and resolves each with its async `.get()` at first start (memoised). A collision with `env`/`secrets` is rejected at authoring time; a missing binding fails the start — the same fail-closed stance as `secrets`. A per-instance `start({ envVars })` replaces the env set wholesale (and skips Secrets Store resolution entirely), and is persisted for that instance: later starts, including implicit restarts, reuse it until `destroy()`. The override cannot change while the container is running or still starting: a differing `start({ envVars })` is rejected with `CONFLICT` in either case, so `stop()` it (and let any start in flight finish) first.
129
131
 
130
132
  ```ts
131
133
  export const worker = defineContainer({
@@ -170,13 +172,30 @@ export const api = defineContainer({
170
172
 
171
173
  ### Hard timeout
172
174
 
173
- `sleepAfter` caps _idle_ time; `hardTimeout` caps _total_ lifetime — a runaway-cost backstop measured from start, regardless of activity (same grammar as `sleepAfter`). When it elapses the generated class's `onHardTimeoutExpired` hook runs (default: `stop()`); the timer is run-generation-stamped so a stale timer from a slept/crashed run can't kill a fresh one.
175
+ `sleepAfter` caps _idle_ time; `hardTimeout` caps _total_ lifetime — a runaway-cost backstop measured from start, regardless of activity (same grammar as `sleepAfter`). When it elapses the generated class's `onHardTimeoutExpired` hook runs (default: `stop()`, i.e. SIGTERM with no escalation — a container that ignores SIGTERM outlives its cap unless you override the hook and follow up with `destroy()`); the timer is run-generation-stamped so a stale timer from a slept/crashed run can't kill a fresh one.
174
176
 
175
177
  ```ts
176
178
  export const job = defineContainer({
177
179
  image: "./containers/job",
178
- hardTimeout: "1h", // never run longer than an hour, busy or not
180
+ hardTimeout: "1h", // stopped after an hour, busy or not
181
+ });
182
+ ```
183
+
184
+ ### Per-instance images and snapshots
185
+
186
+ `schedulingPolicy: "durable_object"` (Cloudflare public beta) lets each instance pick its image and size at start, and save and restore its filesystem:
187
+
188
+ ```ts
189
+ export const agentComputer = defineContainer({
190
+ schedulingPolicy: "durable_object",
191
+ images: { base: "./containers/base" },
192
+ image: "base",
179
193
  });
194
+
195
+ const computer = ctx.containers.agentComputer.get(taskId);
196
+ await computer.start({ image: "base", instanceType: "standard-2" });
197
+ const saved = await computer.snapshot();
198
+ await ctx.containers.agentComputer.get(otherId).start({ snapshot: saved });
180
199
  ```
181
200
 
182
201
  ### Calling Lunora from inside a container
@@ -194,7 +213,7 @@ await lunora.mutation("jobs:markDone", { id: pending[0].id });
194
213
 
195
214
  The token is a bearer your Worker's `resolveIdentity` recognizes — pass it to the container as a `secret`. Non-JS containers can `POST /_lunora/rpc` with `{ functionPath, args }` directly.
196
215
 
197
- Secure the bridge in `resolveIdentity`: read `request.headers.get("authorization")`, strip the `Bearer ` prefix, and compare the token against a Worker secret (e.g. `env.LUNORA_CONTAINER_TOKEN`) you also forward to the container. Return a `{ userId }` identity only on a match and `null` otherwise — an unrecognised request then runs anonymously and is rejected by your functions' own authorization checks. See [Securing the bridge](https://lunora.sh/docs/addons/containers#securing-the-bridge) for the full example.
216
+ Secure the bridge in `resolveIdentity`: read `request.headers.get("authorization")`, strip the `Bearer ` prefix, and compare the token against a Worker secret (e.g. `env.LUNORA_CONTAINER_TOKEN`) you also forward to the container. Return a `{ userId }` identity only on a match and `null` otherwise — an unrecognised request then runs anonymously and is rejected by your functions' own authorization checks. See [Securing the bridge](https://lunora.sh/docs/packages/container#securing-the-bridge) for the full example.
198
217
 
199
218
  ### Entry points
200
219
 
@@ -202,7 +221,7 @@ Secure the bridge in `resolveIdentity`: read `request.headers.get("authorization
202
221
  - `@lunora/container/do` — workerd-only: the `LunoraContainer` base class the generated DO classes extend (pulls in `@cloudflare/containers`).
203
222
  - `@lunora/container/bridge` — runtime-agnostic: `createContainerBridge` for calling Lunora functions from inside a container.
204
223
 
205
- > This README covers the basics. For the full API, options, and guides, see the **[documentation](https://lunora.sh/docs/addons/containers)**.
224
+ > This README covers the basics. For the full API, options, and guides, see the **[documentation](https://lunora.sh/docs/packages/container)**.
206
225
 
207
226
  ### Known platform limitations
208
227
 
package/dist/bridge.d.mts CHANGED
@@ -1,5 +1,7 @@
1
1
  import { LunoraError } from '@lunora/errors';
2
- /** A `fetch` implementation — defaults to the runtime global. */
2
+ /**
3
+ * A `fetch` implementation — defaults to the runtime global.
4
+ */
3
5
  type FetchLike = (input: string, init: {
4
6
  body: string;
5
7
  headers: Record<string, string>;
@@ -10,33 +12,38 @@ type FetchLike = (input: string, init: {
10
12
  status: number;
11
13
  statusText?: string;
12
14
  }>;
15
+ /**
16
+ * `ContainerBridgeOptions` is part of the experimental `@lunora/container` API and may change without a major version bump.
17
+ */
13
18
  interface ContainerBridgeOptions {
14
19
  /**
15
- * Base URL of the deployed Lunora Worker (no trailing `/_lunora/rpc`), e.g.
16
- * `https://my-app.workers.dev`. In a Lunora container, surface it as an
17
- * `env` value on the definition.
18
- */
20
+ * Base URL of the deployed Lunora Worker (no trailing `/_lunora/rpc`), e.g.
21
+ * `https://my-app.workers.dev`. In a Lunora container, surface it as an
22
+ * `env` value on the definition.
23
+ */
19
24
  baseUrl: string;
20
25
  /** Injectable `fetch` (tests / non-global runtimes). Defaults to `globalThis.fetch`. */
21
26
  fetch?: FetchLike;
22
27
  /**
23
- * Bearer token sent as `Authorization: Bearer &lt;token>`. Your Worker's
24
- * `resolveIdentity` maps it to the identity the called functions run as.
25
- * Pass it to the container as a `secret`, never bake it into the image.
26
- */
28
+ * Bearer token sent as `Authorization: Bearer <token>`. Your Worker's
29
+ * `resolveIdentity` maps it to the identity the called functions run as.
30
+ * Pass it to the container as a `secret`, never bake it into the image.
31
+ */
27
32
  token?: string;
28
33
  }
29
- /** Thrown when a Lunora function returns an error envelope. A `LunoraError` subclass carrying the wire `code`. */
34
+ /**
35
+ * Thrown when a Lunora function returns an error envelope. A `LunoraError` subclass carrying the wire `code`.
36
+ */
30
37
  declare class ContainerBridgeError extends LunoraError {
31
38
  constructor(code: string, message: string);
32
39
  }
33
40
  /**
34
- * Structural mirror of `@lunora/client`'s `FunctionReference` — the typed
35
- * handle the generated `_generated/api` object carries. Declared locally (not
36
- * imported) so the bridge stays dependency-free and its `.d.ts` is
37
- * self-contained; the `__lunoraPhantom` shape matches, so a real `api.x.y`
38
- * reference is assignable and its arg/return types are inferable.
39
- */
41
+ * Structural mirror of `@lunora/client`'s `FunctionReference` — the typed
42
+ * handle the generated `_generated/api` object carries. Declared locally (not
43
+ * imported) so the bridge stays dependency-free and its `.d.ts` is
44
+ * self-contained; the `__lunoraPhantom` shape matches, so a real `api.x.y`
45
+ * reference is assignable and its arg/return types are inferable.
46
+ */
40
47
  interface BridgeFunctionReference<Args = unknown, Result = unknown> {
41
48
  readonly __lunoraPhantom?: {
42
49
  args: Args;
@@ -56,6 +63,9 @@ type ResultOfReference<Reference> = Reference extends {
56
63
  returns: infer Result;
57
64
  };
58
65
  } ? Result : never;
66
+ /**
67
+ * `ContainerBridge` is part of the experimental `@lunora/container` API and may change without a major version bump.
68
+ */
59
69
  interface ContainerBridge {
60
70
  /** Call an `action` by `namespace:fn` path. Alias of {@link ContainerBridge.call} for intent. */
61
71
  action: <Result = unknown>(functionPath: string, args?: Record<string, unknown>, shardKey?: string) => Promise<Result>;
@@ -66,25 +76,25 @@ interface ContainerBridge {
66
76
  /** Call a `query` by `namespace:fn` path. Alias of {@link ContainerBridge.call} for intent. */
67
77
  query: <Result = unknown>(functionPath: string, args?: Record<string, unknown>, shardKey?: string) => Promise<Result>;
68
78
  /**
69
- * Fully-typed call via a generated function reference. Pass a reference from
70
- * the project's `_generated/api` (e.g. `api.messages.list`) and the args +
71
- * result are inferred from it — the typed counterpart to {@link ContainerBridge.call}
72
- * for JS/TS containers that can import the generated `api`.
73
- */
79
+ * Fully-typed call via a generated function reference. Pass a reference from
80
+ * the project's `_generated/api` (e.g. `api.messages.list`) and the args +
81
+ * result are inferred from it — the typed counterpart to {@link ContainerBridge.call}
82
+ * for JS/TS containers that can import the generated `api`.
83
+ */
74
84
  run: <Reference extends BridgeFunctionReference>(reference: Reference, args: ArgsOfReference<Reference>, shardKey?: string) => Promise<ResultOfReference<Reference>>;
75
85
  }
76
86
  /**
77
- * Build a container→Lunora bridge bound to a Worker URL + token.
78
- *
79
- * ```ts
80
- * const lunora = createContainerBridge({ baseUrl: process.env.LUNORA_URL!, token: process.env.LUNORA_TOKEN });
81
- * const messages = await lunora.query("messages:list", { limit: 20 });
82
- * await lunora.mutation("messages:markProcessed", { id });
83
- * ```
84
- *
85
- * `query`/`mutation`/`action` are intent-revealing aliases of one `call` — the
86
- * wire is identical and the server dispatches by the function's registered
87
- * kind, so a query path called via `.mutation(...)` still runs as a query.
88
- */
87
+ * Build a container→Lunora bridge bound to a Worker URL + token.
88
+ *
89
+ * ```ts
90
+ * const lunora = createContainerBridge({ baseUrl: process.env.LUNORA_URL!, token: process.env.LUNORA_TOKEN });
91
+ * const messages = await lunora.query("messages:list", { limit: 20 });
92
+ * await lunora.mutation("messages:markProcessed", { id });
93
+ * ```
94
+ *
95
+ * `query`/`mutation`/`action` are intent-revealing aliases of one `call` — the
96
+ * wire is identical and the server dispatches by the function's registered
97
+ * kind, so a query path called via `.mutation(...)` still runs as a query.
98
+ */
89
99
  declare const createContainerBridge: (options: ContainerBridgeOptions) => ContainerBridge;
90
100
  export { type BridgeFunctionReference, type ContainerBridge, ContainerBridgeError, type ContainerBridgeOptions, type FetchLike, createContainerBridge };
package/dist/bridge.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import { LunoraError } from '@lunora/errors';
2
- /** A `fetch` implementation — defaults to the runtime global. */
2
+ /**
3
+ * A `fetch` implementation — defaults to the runtime global.
4
+ */
3
5
  type FetchLike = (input: string, init: {
4
6
  body: string;
5
7
  headers: Record<string, string>;
@@ -10,33 +12,38 @@ type FetchLike = (input: string, init: {
10
12
  status: number;
11
13
  statusText?: string;
12
14
  }>;
15
+ /**
16
+ * `ContainerBridgeOptions` is part of the experimental `@lunora/container` API and may change without a major version bump.
17
+ */
13
18
  interface ContainerBridgeOptions {
14
19
  /**
15
- * Base URL of the deployed Lunora Worker (no trailing `/_lunora/rpc`), e.g.
16
- * `https://my-app.workers.dev`. In a Lunora container, surface it as an
17
- * `env` value on the definition.
18
- */
20
+ * Base URL of the deployed Lunora Worker (no trailing `/_lunora/rpc`), e.g.
21
+ * `https://my-app.workers.dev`. In a Lunora container, surface it as an
22
+ * `env` value on the definition.
23
+ */
19
24
  baseUrl: string;
20
25
  /** Injectable `fetch` (tests / non-global runtimes). Defaults to `globalThis.fetch`. */
21
26
  fetch?: FetchLike;
22
27
  /**
23
- * Bearer token sent as `Authorization: Bearer &lt;token>`. Your Worker's
24
- * `resolveIdentity` maps it to the identity the called functions run as.
25
- * Pass it to the container as a `secret`, never bake it into the image.
26
- */
28
+ * Bearer token sent as `Authorization: Bearer <token>`. Your Worker's
29
+ * `resolveIdentity` maps it to the identity the called functions run as.
30
+ * Pass it to the container as a `secret`, never bake it into the image.
31
+ */
27
32
  token?: string;
28
33
  }
29
- /** Thrown when a Lunora function returns an error envelope. A `LunoraError` subclass carrying the wire `code`. */
34
+ /**
35
+ * Thrown when a Lunora function returns an error envelope. A `LunoraError` subclass carrying the wire `code`.
36
+ */
30
37
  declare class ContainerBridgeError extends LunoraError {
31
38
  constructor(code: string, message: string);
32
39
  }
33
40
  /**
34
- * Structural mirror of `@lunora/client`'s `FunctionReference` — the typed
35
- * handle the generated `_generated/api` object carries. Declared locally (not
36
- * imported) so the bridge stays dependency-free and its `.d.ts` is
37
- * self-contained; the `__lunoraPhantom` shape matches, so a real `api.x.y`
38
- * reference is assignable and its arg/return types are inferable.
39
- */
41
+ * Structural mirror of `@lunora/client`'s `FunctionReference` — the typed
42
+ * handle the generated `_generated/api` object carries. Declared locally (not
43
+ * imported) so the bridge stays dependency-free and its `.d.ts` is
44
+ * self-contained; the `__lunoraPhantom` shape matches, so a real `api.x.y`
45
+ * reference is assignable and its arg/return types are inferable.
46
+ */
40
47
  interface BridgeFunctionReference<Args = unknown, Result = unknown> {
41
48
  readonly __lunoraPhantom?: {
42
49
  args: Args;
@@ -56,6 +63,9 @@ type ResultOfReference<Reference> = Reference extends {
56
63
  returns: infer Result;
57
64
  };
58
65
  } ? Result : never;
66
+ /**
67
+ * `ContainerBridge` is part of the experimental `@lunora/container` API and may change without a major version bump.
68
+ */
59
69
  interface ContainerBridge {
60
70
  /** Call an `action` by `namespace:fn` path. Alias of {@link ContainerBridge.call} for intent. */
61
71
  action: <Result = unknown>(functionPath: string, args?: Record<string, unknown>, shardKey?: string) => Promise<Result>;
@@ -66,25 +76,25 @@ interface ContainerBridge {
66
76
  /** Call a `query` by `namespace:fn` path. Alias of {@link ContainerBridge.call} for intent. */
67
77
  query: <Result = unknown>(functionPath: string, args?: Record<string, unknown>, shardKey?: string) => Promise<Result>;
68
78
  /**
69
- * Fully-typed call via a generated function reference. Pass a reference from
70
- * the project's `_generated/api` (e.g. `api.messages.list`) and the args +
71
- * result are inferred from it — the typed counterpart to {@link ContainerBridge.call}
72
- * for JS/TS containers that can import the generated `api`.
73
- */
79
+ * Fully-typed call via a generated function reference. Pass a reference from
80
+ * the project's `_generated/api` (e.g. `api.messages.list`) and the args +
81
+ * result are inferred from it — the typed counterpart to {@link ContainerBridge.call}
82
+ * for JS/TS containers that can import the generated `api`.
83
+ */
74
84
  run: <Reference extends BridgeFunctionReference>(reference: Reference, args: ArgsOfReference<Reference>, shardKey?: string) => Promise<ResultOfReference<Reference>>;
75
85
  }
76
86
  /**
77
- * Build a container→Lunora bridge bound to a Worker URL + token.
78
- *
79
- * ```ts
80
- * const lunora = createContainerBridge({ baseUrl: process.env.LUNORA_URL!, token: process.env.LUNORA_TOKEN });
81
- * const messages = await lunora.query("messages:list", { limit: 20 });
82
- * await lunora.mutation("messages:markProcessed", { id });
83
- * ```
84
- *
85
- * `query`/`mutation`/`action` are intent-revealing aliases of one `call` — the
86
- * wire is identical and the server dispatches by the function's registered
87
- * kind, so a query path called via `.mutation(...)` still runs as a query.
88
- */
87
+ * Build a container→Lunora bridge bound to a Worker URL + token.
88
+ *
89
+ * ```ts
90
+ * const lunora = createContainerBridge({ baseUrl: process.env.LUNORA_URL!, token: process.env.LUNORA_TOKEN });
91
+ * const messages = await lunora.query("messages:list", { limit: 20 });
92
+ * await lunora.mutation("messages:markProcessed", { id });
93
+ * ```
94
+ *
95
+ * `query`/`mutation`/`action` are intent-revealing aliases of one `call` — the
96
+ * wire is identical and the server dispatches by the function's registered
97
+ * kind, so a query path called via `.mutation(...)` still runs as a query.
98
+ */
89
99
  declare const createContainerBridge: (options: ContainerBridgeOptions) => ContainerBridge;
90
100
  export { type BridgeFunctionReference, type ContainerBridge, ContainerBridgeError, type ContainerBridgeOptions, type FetchLike, createContainerBridge };
package/dist/bridge.mjs CHANGED
@@ -1,79 +1 @@
1
- import { LunoraError } from '@lunora/errors';
2
-
3
- const RPC_PATH = "/_lunora/rpc";
4
- class ContainerBridgeError extends LunoraError {
5
- constructor(code, message) {
6
- super(code, message, { name: "ContainerBridgeError" });
7
- }
8
- }
9
- const joinUrl = (baseUrl, path) => {
10
- let base = baseUrl;
11
- while (base.endsWith("/")) {
12
- base = base.slice(0, -1);
13
- }
14
- return `${base}${path}`;
15
- };
16
- const statusError = (functionPath, response) => new Error(
17
- `createContainerBridge: request to "${functionPath}" failed (status ${String(response.status)}${response.statusText ? ` ${response.statusText}` : ""})`
18
- );
19
- const parseResponseBody = async (response, functionPath) => {
20
- try {
21
- return await response.json();
22
- } catch {
23
- if (!response.ok) {
24
- throw statusError(functionPath, response);
25
- }
26
- throw new LunoraError(
27
- "INTERNAL",
28
- `createContainerBridge: request to "${functionPath}" returned a non-JSON response (status ${String(response.status)})`
29
- );
30
- }
31
- };
32
- const createContainerBridge = (options) => {
33
- if (typeof options.baseUrl !== "string" || options.baseUrl.length === 0) {
34
- throw new TypeError("createContainerBridge: `baseUrl` must be a non-empty Worker URL (e.g. https://my-app.workers.dev) — is the URL env var set?");
35
- }
36
- const fetchImpl = options.fetch ?? globalThis.fetch;
37
- const call = async (functionPath, args = {}, shardKey) => {
38
- if (typeof fetchImpl !== "function") {
39
- throw new TypeError("createContainerBridge: no `fetch` available — pass `fetch` in options for this runtime.");
40
- }
41
- const headers = { "content-type": "application/json" };
42
- if (options.token !== void 0) {
43
- headers.authorization = `Bearer ${options.token}`;
44
- }
45
- const response = await fetchImpl(joinUrl(options.baseUrl, RPC_PATH), {
46
- body: JSON.stringify({ args, functionPath, shardKey }),
47
- headers,
48
- method: "POST"
49
- });
50
- const body = await parseResponseBody(response, functionPath);
51
- if (typeof body === "object" && body !== null && "error" in body) {
52
- const { error } = body;
53
- if (typeof error === "object" && error !== null) {
54
- const { code, message } = error;
55
- if (typeof code === "string" && typeof message === "string") {
56
- throw new ContainerBridgeError(code, message);
57
- }
58
- }
59
- let detail;
60
- try {
61
- detail = JSON.stringify(error);
62
- } catch {
63
- detail = String(error);
64
- }
65
- throw new LunoraError(
66
- "INTERNAL",
67
- `createContainerBridge: request to "${functionPath}" returned a malformed error envelope (status ${String(response.status)}): ${detail}`
68
- );
69
- }
70
- if (!response.ok) {
71
- throw statusError(functionPath, response);
72
- }
73
- return body.result;
74
- };
75
- const run = async (reference, args, shardKey) => call(reference.__lunoraRef, args, shardKey);
76
- return { action: call, call, mutation: call, query: call, run };
77
- };
78
-
79
- export { ContainerBridgeError, createContainerBridge };
1
+ import{LunoraError as u}from"@lunora/errors";import{encodeWire as h,decodeWire as p}from"@lunora/wire";const w="/_lunora/rpc";class m extends u{constructor(r,t){super(r,t,{name:"ContainerBridgeError"})}}const b=(e,r)=>{let t=e;for(;t.endsWith("/");)t=t.slice(0,-1);return`${t}${r}`},y=(e,r)=>new Error(`createContainerBridge: request to "${e}" failed (status ${String(r.status)}${r.statusText?` ${r.statusText}`:""})`),$=async(e,r)=>{try{return await e.json()}catch{throw e.ok?new u("INTERNAL",`createContainerBridge: request to "${r}" returned a non-JSON response (status ${String(e.status)})`):y(r,e)}},T=e=>{if(typeof e.baseUrl!="string"||e.baseUrl.length===0)throw new TypeError("createContainerBridge: `baseUrl` must be a non-empty Worker URL (e.g. https://my-app.workers.dev) — is the URL env var set?");const r=e.fetch??globalThis.fetch,t=async(o,i={},c)=>{if(typeof r!="function")throw new TypeError("createContainerBridge: no `fetch` available — pass `fetch` in options for this runtime.");const d={"content-type":"application/json"};e.token!==void 0&&(d.authorization=`Bearer ${e.token}`);const s=await r(b(e.baseUrl,w),{body:JSON.stringify({args:h(i),functionPath:o,shardKey:c}),headers:d,method:"POST"}),n=await $(s,o);if(typeof n=="object"&&n!==null&&"error"in n){const{error:a}=n;if(typeof a=="object"&&a!==null){const{code:f,message:g}=a;if(typeof f=="string"&&typeof g=="string")throw new m(f,g)}let l;try{l=JSON.stringify(a)}catch{l=String(a)}throw new u("INTERNAL",`createContainerBridge: request to "${o}" returned a malformed error envelope (status ${String(s.status)}): ${l}`)}if(!s.ok)throw y(o,s);return p(n.result)};return{action:t,call:t,mutation:t,query:t,run:async(o,i,c)=>t(o.__lunoraRef,i,c)}};export{m as ContainerBridgeError,T as createContainerBridge};