@lunora/container 1.0.0-alpha.5 → 1.0.0-alpha.51
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/LICENSE.md +26 -0
- package/README.md +6 -4
- package/dist/bridge.d.mts +45 -35
- package/dist/bridge.d.ts +45 -35
- package/dist/bridge.mjs +1 -76
- package/dist/do/index.d.mts +172 -84
- package/dist/do/index.d.ts +172 -84
- package/dist/do/index.mjs +1 -324
- package/dist/index.d.mts +388 -160
- package/dist/index.d.ts +388 -160
- package/dist/index.mjs +1 -2
- package/dist/otel.d.mts +169 -0
- package/dist/otel.d.ts +169 -0
- package/dist/otel.mjs +1 -0
- package/dist/packem_shared/CONTAINER_EXEC_PATH-CAqlIhLq.mjs +1 -0
- package/dist/packem_shared/ContainerProxy-fTW_pXWY.mjs +27 -0
- package/dist/packem_shared/abort-deadline-jwbW9Ala.mjs +1 -0
- package/dist/packem_shared/containerBindingName-C7SonAIK.mjs +1 -0
- package/dist/packem_shared/createContainerContext-DApPtouZ.mjs +1 -0
- package/dist/packem_shared/exec-KlR4eMip.mjs +1 -0
- package/dist/packem_shared/jurisdiction-DtE9s70w.mjs +1 -0
- package/dist/packem_shared/jurisdiction.d-rC2ejtvT.d.mts +271 -0
- package/dist/packem_shared/jurisdiction.d-rC2ejtvT.d.ts +271 -0
- package/package.json +8 -1
- package/dist/packem_shared/ContainerProxy-DWqUX_re.mjs +0 -1474
- package/dist/packem_shared/containerBindingName-CWmEE_3Y.mjs +0 -215
- package/dist/packem_shared/createContainerContext-BtzK5gJL.mjs +0 -225
- package/dist/packem_shared/types.d-BlNwNY44.d.mts +0 -250
- package/dist/packem_shared/types.d-BlNwNY44.d.ts +0 -250
package/LICENSE.md
CHANGED
|
@@ -103,3 +103,29 @@ Unless required by applicable law or agreed to in writing, software distributed
|
|
|
103
103
|
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
104
104
|
CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
105
105
|
specific language governing permissions and limitations under the License.
|
|
106
|
+
|
|
107
|
+
<!-- DEPENDENCIES -->
|
|
108
|
+
|
|
109
|
+
# Licenses of bundled dependencies
|
|
110
|
+
The published @lunora/container artifact additionally contains code with the following licenses:
|
|
111
|
+
MIT OR Apache-2.0
|
|
112
|
+
|
|
113
|
+
# Bundled dependencies:
|
|
114
|
+
## @cloudflare/containers
|
|
115
|
+
License: MIT OR Apache-2.0
|
|
116
|
+
Repository: git+https://github.com/cloudflare/containers.git
|
|
117
|
+
|
|
118
|
+
<!-- /DEPENDENCIES -->
|
|
119
|
+
|
|
120
|
+
<!-- TYPE_DEPENDENCIES -->
|
|
121
|
+
|
|
122
|
+
# Licenses of bundled types
|
|
123
|
+
The published @lunora/container artifact additionally contains code with the following licenses:
|
|
124
|
+
MIT OR Apache-2.0
|
|
125
|
+
|
|
126
|
+
# Bundled types:
|
|
127
|
+
## @cloudflare/containers
|
|
128
|
+
License: MIT OR Apache-2.0
|
|
129
|
+
Repository: git+https://github.com/cloudflare/containers.git
|
|
130
|
+
|
|
131
|
+
<!-- /TYPE_DEPENDENCIES -->
|
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">
|
|
@@ -170,12 +172,12 @@ 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
|
|
179
181
|
});
|
|
180
182
|
```
|
|
181
183
|
|
|
@@ -194,7 +196,7 @@ await lunora.mutation("jobs:markDone", { id: pending[0].id });
|
|
|
194
196
|
|
|
195
197
|
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
198
|
|
|
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/
|
|
199
|
+
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
200
|
|
|
199
201
|
### Entry points
|
|
200
202
|
|
|
@@ -202,7 +204,7 @@ Secure the bridge in `resolveIdentity`: read `request.headers.get("authorization
|
|
|
202
204
|
- `@lunora/container/do` — workerd-only: the `LunoraContainer` base class the generated DO classes extend (pulls in `@cloudflare/containers`).
|
|
203
205
|
- `@lunora/container/bridge` — runtime-agnostic: `createContainerBridge` for calling Lunora functions from inside a container.
|
|
204
206
|
|
|
205
|
-
> This README covers the basics. For the full API, options, and guides, see the **[documentation](https://lunora.sh/docs/
|
|
207
|
+
> This README covers the basics. For the full API, options, and guides, see the **[documentation](https://lunora.sh/docs/packages/container)**.
|
|
206
208
|
|
|
207
209
|
### Known platform limitations
|
|
208
210
|
|
package/dist/bridge.d.mts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
import { LunoraError } from '@lunora/errors';
|
|
2
|
+
/**
|
|
3
|
+
* A `fetch` implementation — defaults to the runtime global.
|
|
4
|
+
*/
|
|
2
5
|
type FetchLike = (input: string, init: {
|
|
3
6
|
body: string;
|
|
4
7
|
headers: Record<string, string>;
|
|
@@ -9,34 +12,38 @@ type FetchLike = (input: string, init: {
|
|
|
9
12
|
status: number;
|
|
10
13
|
statusText?: string;
|
|
11
14
|
}>;
|
|
15
|
+
/**
|
|
16
|
+
* `ContainerBridgeOptions` is part of the experimental `@lunora/container` API and may change without a major version bump.
|
|
17
|
+
*/
|
|
12
18
|
interface ContainerBridgeOptions {
|
|
13
19
|
/**
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
+
*/
|
|
18
24
|
baseUrl: string;
|
|
19
25
|
/** Injectable `fetch` (tests / non-global runtimes). Defaults to `globalThis.fetch`. */
|
|
20
26
|
fetch?: FetchLike;
|
|
21
27
|
/**
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
+
*/
|
|
26
32
|
token?: string;
|
|
27
33
|
}
|
|
28
|
-
/**
|
|
29
|
-
|
|
30
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Thrown when a Lunora function returns an error envelope. A `LunoraError` subclass carrying the wire `code`.
|
|
36
|
+
*/
|
|
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,4 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
import { LunoraError } from '@lunora/errors';
|
|
2
|
+
/**
|
|
3
|
+
* A `fetch` implementation — defaults to the runtime global.
|
|
4
|
+
*/
|
|
2
5
|
type FetchLike = (input: string, init: {
|
|
3
6
|
body: string;
|
|
4
7
|
headers: Record<string, string>;
|
|
@@ -9,34 +12,38 @@ type FetchLike = (input: string, init: {
|
|
|
9
12
|
status: number;
|
|
10
13
|
statusText?: string;
|
|
11
14
|
}>;
|
|
15
|
+
/**
|
|
16
|
+
* `ContainerBridgeOptions` is part of the experimental `@lunora/container` API and may change without a major version bump.
|
|
17
|
+
*/
|
|
12
18
|
interface ContainerBridgeOptions {
|
|
13
19
|
/**
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
+
*/
|
|
18
24
|
baseUrl: string;
|
|
19
25
|
/** Injectable `fetch` (tests / non-global runtimes). Defaults to `globalThis.fetch`. */
|
|
20
26
|
fetch?: FetchLike;
|
|
21
27
|
/**
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
+
*/
|
|
26
32
|
token?: string;
|
|
27
33
|
}
|
|
28
|
-
/**
|
|
29
|
-
|
|
30
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Thrown when a Lunora function returns an error envelope. A `LunoraError` subclass carrying the wire `code`.
|
|
36
|
+
*/
|
|
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,76 +1 @@
|
|
|
1
|
-
const
|
|
2
|
-
class ContainerBridgeError extends Error {
|
|
3
|
-
code;
|
|
4
|
-
constructor(code, message) {
|
|
5
|
-
super(message);
|
|
6
|
-
this.name = "ContainerBridgeError";
|
|
7
|
-
this.code = code;
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
const joinUrl = (baseUrl, path) => {
|
|
11
|
-
let base = baseUrl;
|
|
12
|
-
while (base.endsWith("/")) {
|
|
13
|
-
base = base.slice(0, -1);
|
|
14
|
-
}
|
|
15
|
-
return `${base}${path}`;
|
|
16
|
-
};
|
|
17
|
-
const statusError = (functionPath, response) => new Error(
|
|
18
|
-
`createContainerBridge: request to "${functionPath}" failed (status ${String(response.status)}${response.statusText ? ` ${response.statusText}` : ""})`
|
|
19
|
-
);
|
|
20
|
-
const parseResponseBody = async (response, functionPath) => {
|
|
21
|
-
try {
|
|
22
|
-
return await response.json();
|
|
23
|
-
} catch {
|
|
24
|
-
if (!response.ok) {
|
|
25
|
-
throw statusError(functionPath, response);
|
|
26
|
-
}
|
|
27
|
-
throw new Error(`createContainerBridge: request to "${functionPath}" returned a non-JSON response (status ${String(response.status)})`);
|
|
28
|
-
}
|
|
29
|
-
};
|
|
30
|
-
const createContainerBridge = (options) => {
|
|
31
|
-
if (typeof options.baseUrl !== "string" || options.baseUrl.length === 0) {
|
|
32
|
-
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?");
|
|
33
|
-
}
|
|
34
|
-
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
35
|
-
const call = async (functionPath, args = {}, shardKey) => {
|
|
36
|
-
if (typeof fetchImpl !== "function") {
|
|
37
|
-
throw new TypeError("createContainerBridge: no `fetch` available — pass `fetch` in options for this runtime.");
|
|
38
|
-
}
|
|
39
|
-
const headers = { "content-type": "application/json" };
|
|
40
|
-
if (options.token !== void 0) {
|
|
41
|
-
headers.authorization = `Bearer ${options.token}`;
|
|
42
|
-
}
|
|
43
|
-
const response = await fetchImpl(joinUrl(options.baseUrl, RPC_PATH), {
|
|
44
|
-
body: JSON.stringify({ args, functionPath, shardKey }),
|
|
45
|
-
headers,
|
|
46
|
-
method: "POST"
|
|
47
|
-
});
|
|
48
|
-
const body = await parseResponseBody(response, functionPath);
|
|
49
|
-
if (typeof body === "object" && body !== null && "error" in body) {
|
|
50
|
-
const { error } = body;
|
|
51
|
-
if (typeof error === "object" && error !== null) {
|
|
52
|
-
const { code, message } = error;
|
|
53
|
-
if (typeof code === "string" && typeof message === "string") {
|
|
54
|
-
throw new ContainerBridgeError(code, message);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
let detail;
|
|
58
|
-
try {
|
|
59
|
-
detail = JSON.stringify(error);
|
|
60
|
-
} catch {
|
|
61
|
-
detail = String(error);
|
|
62
|
-
}
|
|
63
|
-
throw new Error(
|
|
64
|
-
`createContainerBridge: request to "${functionPath}" returned a malformed error envelope (status ${String(response.status)}): ${detail}`
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
if (!response.ok) {
|
|
68
|
-
throw statusError(functionPath, response);
|
|
69
|
-
}
|
|
70
|
-
return body.result;
|
|
71
|
-
};
|
|
72
|
-
const run = async (reference, args, shardKey) => call(reference.__lunoraRef, args, shardKey);
|
|
73
|
-
return { action: call, call, mutation: call, query: call, run };
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
export { ContainerBridgeError, createContainerBridge };
|
|
1
|
+
import{LunoraError as p}from"@lunora/errors";const m=r=>{let t="";for(let c=0;c<r.length;c+=32768)t+=String.fromCharCode(...r.subarray(c,c+32768));return btoa(t)},R=r=>{const t=atob(r),o=new Uint8Array(t.length);for(let c=0;c<t.length;c+=1)o[c]=t.codePointAt(c)??0;return o},f="$lunora.wire$",w=64,E=1024,g="__proto__",j={BigInt64Array,BigUint64Array,Float32Array,Float64Array,Int8Array,Int16Array,Int32Array,Uint8Array,Uint8ClampedArray,Uint16Array,Uint32Array},O={Error,EvalError,RangeError,ReferenceError,SyntaxError,TypeError,URIError},U=r=>{if(r===null||typeof r!="object")return!1;const t=Object.getPrototypeOf(r);return t===null||t===Object.prototype},d=(r,t=0)=>{if(t>w)throw new RangeError(`wire-codec: value nesting exceeds the ${w}-level limit`);if(r===void 0)return[f,"undefined"];if(r===null)return null;const o=typeof r;if(o==="bigint")return[f,"bigint",r.toString()];if(o==="number"){const e=r;return Number.isNaN(e)?[f,"nan"]:e===1/0?[f,"inf"]:e===-1/0?[f,"-inf"]:e}if(o!=="object")return r;if(r instanceof Date)return[f,"date",d(r.getTime(),t+1)];if(r instanceof Error){const e=r,n={};for(const i of Object.keys(e)){if(e[i]===void 0)continue;const u=d(e[i],t+1);i===g?Object.defineProperty(n,i,{configurable:!0,enumerable:!0,value:u,writable:!0}):n[i]=u}const a=[f,"error",String(e.name),String(e.message),n];return e.cause!==void 0&&a.push(d(e.cause,t+1)),a}if(r instanceof URL)return[f,"url",r.href];if(r instanceof Map)return[f,"map",[...r.entries()].map(([e,n])=>[d(e,t+1),d(n,t+1)])];if(r instanceof Set)return[f,"set",[...r].map(e=>d(e,t+1))];if(r instanceof ArrayBuffer)return[f,"bytes",m(new Uint8Array(r)),"ArrayBuffer"];if(ArrayBuffer.isView(r)){const e=r,n=e.constructor.name,a=new Uint8Array(e.buffer,e.byteOffset,e.byteLength);return n==="Uint8Array"?[f,"bytes",m(a)]:[f,"bytes",m(a),n]}if(Array.isArray(r)){const e=r.map(n=>d(n,t+1));return e.length>0&&e[0]===f?[f,"arr",e]:e}if(!U(r)){const e=r.constructor?.name??"value";throw new TypeError(`wire-codec: cannot encode a ${e} over the Lunora wire — only plain objects, arrays, and the supported built-ins (Date, Error, URL, Map, Set, ArrayBuffer/typed arrays, bigint) round-trip`)}const c=r,s={};for(const e of Object.keys(c)){const n=c[e];if(n===void 0)continue;const a=d(n,t+1);e===g?Object.defineProperty(s,e,{configurable:!0,enumerable:!0,value:a,writable:!0}):s[e]=a}return s},b=(r,t=0)=>{if(t>w)throw new RangeError(`wire-codec: value nesting exceeds the ${w}-level limit`);if(r===null||typeof r!="object")return r;if(Array.isArray(r)){if(r[0]===f)switch(r[1]){case"-inf":return-1/0;case"arr":return r[2].map(e=>b(e,t+1));case"bigint":{const e=r[2];if(typeof e!="string"||e.length>E||!/^-?\d+$/.test(e))throw new RangeError(`wire-codec: invalid or over-long bigint (max ${E} digits)`);return BigInt(e)}case"date":{const e=b(r[2],t+1);if(typeof e!="number")throw new TypeError("wire-codec: malformed date — epoch must be a number");return new Date(e)}case"map":{const e=r[2];return new Map(e.map(n=>{if(!Array.isArray(n)||n.length!==2)throw new TypeError("wire-codec: malformed map entry — expected a [key, value] pair");return[b(n[0],t+1),b(n[1],t+1)]}))}case"set":return new Set(r[2].map(e=>b(e,t+1)));case"url":{const e=r[2];if(typeof e!="string")throw new TypeError("wire-codec: malformed url — href must be a string");return new URL(e)}case"error":{const e=r[2],n=r[3];if(typeof e!="string"||typeof n!="string")throw new TypeError("wire-codec: malformed error — name and message must be strings");const a=(Object.hasOwn(O,e)?O[e]:void 0)??Error,i=new a(n);i.name!==e&&Object.defineProperty(i,"name",{configurable:!0,value:e,writable:!0});const u=b(r[4],t+1);if(u===null||typeof u!="object"||Array.isArray(u))throw new TypeError("wire-codec: malformed error — props must be an object");for(const y of Object.keys(u))y===g?Object.defineProperty(i,y,{configurable:!0,enumerable:!0,value:u[y],writable:!0}):i[y]=u[y];return r.length>5&&Object.defineProperty(i,"cause",{configurable:!0,value:b(r[5],t+1),writable:!0}),i}case"bytes":{const e=r[2];if(typeof e!="string")throw new TypeError("wire-codec: malformed bytes — payload must be a base64 string");const n=R(e);if(m(n)!==e)throw new TypeError("wire-codec: malformed bytes — payload must be canonical padded base64");const a=r[3]??"Uint8Array";if(a==="ArrayBuffer")return n.buffer.byteLength===n.byteLength?n.buffer:n.slice().buffer;const i=Object.hasOwn(j,a)?j[a]:void 0;return i?new i(n.slice().buffer):n}case"inf":return 1/0;case"nan":return Number.NaN;case"undefined":return;default:return r.map(e=>b(e,t+1))}return r.map(s=>b(s,t+1))}const o=r,c={};for(const s of Object.keys(o)){const e=b(o[s],t+1);s===g?Object.defineProperty(c,s,{configurable:!0,enumerable:!0,value:e,writable:!0}):c[s]=e}return c},B="/_lunora/rpc";class S extends p{constructor(t,o){super(t,o,{name:"ContainerBridgeError"})}}const k=(r,t)=>{let o=r;for(;o.endsWith("/");)o=o.slice(0,-1);return`${o}${t}`},T=(r,t)=>new Error(`createContainerBridge: request to "${r}" failed (status ${String(t.status)}${t.statusText?` ${t.statusText}`:""})`),$=async(r,t)=>{try{return await r.json()}catch{throw r.ok?new p("INTERNAL",`createContainerBridge: request to "${t}" returned a non-JSON response (status ${String(r.status)})`):T(t,r)}},N=r=>{if(typeof r.baseUrl!="string"||r.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 t=r.fetch??globalThis.fetch,o=async(s,e={},n)=>{if(typeof t!="function")throw new TypeError("createContainerBridge: no `fetch` available — pass `fetch` in options for this runtime.");const a={"content-type":"application/json"};r.token!==void 0&&(a.authorization=`Bearer ${r.token}`);const i=await t(k(r.baseUrl,B),{body:JSON.stringify({args:d(e),functionPath:s,shardKey:n}),headers:a,method:"POST"}),u=await $(i,s);if(typeof u=="object"&&u!==null&&"error"in u){const{error:y}=u;if(typeof y=="object"&&y!==null){const{code:h,message:A}=y;if(typeof h=="string"&&typeof A=="string")throw new S(h,A)}let l;try{l=JSON.stringify(y)}catch{l=String(y)}throw new p("INTERNAL",`createContainerBridge: request to "${s}" returned a malformed error envelope (status ${String(i.status)}): ${l}`)}if(!i.ok)throw T(s,i);return b(u.result)};return{action:o,call:o,mutation:o,query:o,run:async(s,e,n)=>o(s.__lunoraRef,e,n)}};export{S as ContainerBridgeError,N as createContainerBridge};
|