@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.
- package/README.md +24 -5
- package/dist/bridge.d.mts +43 -33
- package/dist/bridge.d.ts +43 -33
- package/dist/bridge.mjs +1 -79
- package/dist/do/index.d.mts +353 -91
- package/dist/do/index.d.ts +353 -91
- package/dist/do/index.mjs +1 -328
- package/dist/index.d.mts +584 -153
- package/dist/index.d.ts +584 -153
- package/dist/index.mjs +1 -2
- package/dist/otel.d.mts +217 -0
- package/dist/otel.d.ts +217 -0
- package/dist/otel.mjs +1 -0
- package/dist/packem_shared/CONTAINER_EXEC_PATH-7Hcd6-zs.mjs +1 -0
- package/dist/packem_shared/CUSTOM_INSTANCE_MIN_MEMORY_MIB_PER_VCPU-D8Hy9Rxv.mjs +1 -0
- package/dist/packem_shared/ContainerProxy-CfXjS6ZI.mjs +27 -0
- package/dist/packem_shared/DURABLE_OBJECT_POLICY_FORBIDDEN_KEYS-gYi8RBUO.mjs +1 -0
- package/dist/packem_shared/LunoraSandboxContainer-DPnhJUu1.mjs +1 -0
- package/dist/packem_shared/abort-deadline-jwbW9Ala.mjs +1 -0
- package/dist/packem_shared/createContainerContext-p6QxWjsm.mjs +1 -0
- package/dist/packem_shared/exec-BYLW70sR.mjs +1 -0
- package/dist/packem_shared/jurisdiction-DtE9s70w.mjs +1 -0
- package/dist/packem_shared/jurisdiction.d-BwpauCoI.d.mts +471 -0
- package/dist/packem_shared/jurisdiction.d-BwpauCoI.d.ts +471 -0
- package/dist/packem_shared/sampling-D21rpu_s.mjs +1 -0
- package/dist/packem_shared/sandbox-types.d-DXlHnsEg.d.mts +110 -0
- package/dist/packem_shared/sandbox-types.d-DXlHnsEg.d.ts +110 -0
- package/dist/sandbox/index.d.mts +66 -0
- package/dist/sandbox/index.d.ts +66 -0
- package/dist/sandbox/index.mjs +1 -0
- package/package.json +12 -2
- package/dist/packem_shared/ContainerProxy-DWqUX_re.mjs +0 -1474
- package/dist/packem_shared/containerBindingName-D5KTrblH.mjs +0 -218
- package/dist/packem_shared/createContainerContext-Cibbh7AE.mjs +0 -228
- package/dist/packem_shared/types.d-BlNwNY44.d.mts +0 -250
- 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`.
|
|
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", //
|
|
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/
|
|
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/
|
|
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
|
-
/**
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
/**
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
/**
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
/**
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
|
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};
|