@lunora/container 0.0.0 → 1.0.0-alpha.10

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.
@@ -0,0 +1,90 @@
1
+ import { LunoraError } from '@lunora/errors';
2
+ /** A `fetch` implementation — defaults to the runtime global. */
3
+ type FetchLike = (input: string, init: {
4
+ body: string;
5
+ headers: Record<string, string>;
6
+ method: string;
7
+ }) => Promise<{
8
+ json: () => Promise<unknown>;
9
+ ok: boolean;
10
+ status: number;
11
+ statusText?: string;
12
+ }>;
13
+ interface ContainerBridgeOptions {
14
+ /**
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
+ */
19
+ baseUrl: string;
20
+ /** Injectable `fetch` (tests / non-global runtimes). Defaults to `globalThis.fetch`. */
21
+ fetch?: FetchLike;
22
+ /**
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
+ */
27
+ token?: string;
28
+ }
29
+ /** Thrown when a Lunora function returns an error envelope. A `LunoraError` subclass carrying the wire `code`. */
30
+ declare class ContainerBridgeError extends LunoraError {
31
+ constructor(code: string, message: string);
32
+ }
33
+ /**
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
+ */
40
+ interface BridgeFunctionReference<Args = unknown, Result = unknown> {
41
+ readonly __lunoraPhantom?: {
42
+ args: Args;
43
+ returns: Result;
44
+ };
45
+ readonly __lunoraRef: string;
46
+ }
47
+ /** Infer the args type from a {@link BridgeFunctionReference} (or a `@lunora/client` reference). */
48
+ type ArgsOfReference<Reference> = Reference extends {
49
+ __lunoraPhantom?: {
50
+ args: infer Args;
51
+ };
52
+ } ? Args : never;
53
+ /** Infer the result type from a {@link BridgeFunctionReference} (or a `@lunora/client` reference). */
54
+ type ResultOfReference<Reference> = Reference extends {
55
+ __lunoraPhantom?: {
56
+ returns: infer Result;
57
+ };
58
+ } ? Result : never;
59
+ interface ContainerBridge {
60
+ /** Call an `action` by `namespace:fn` path. Alias of {@link ContainerBridge.call} for intent. */
61
+ action: <Result = unknown>(functionPath: string, args?: Record<string, unknown>, shardKey?: string) => Promise<Result>;
62
+ /** Call any Lunora function by `namespace:fn` path; the server resolves its kind. */
63
+ call: <Result = unknown>(functionPath: string, args?: Record<string, unknown>, shardKey?: string) => Promise<Result>;
64
+ /** Call a `mutation` by `namespace:fn` path. Alias of {@link ContainerBridge.call} for intent. */
65
+ mutation: <Result = unknown>(functionPath: string, args?: Record<string, unknown>, shardKey?: string) => Promise<Result>;
66
+ /** Call a `query` by `namespace:fn` path. Alias of {@link ContainerBridge.call} for intent. */
67
+ query: <Result = unknown>(functionPath: string, args?: Record<string, unknown>, shardKey?: string) => Promise<Result>;
68
+ /**
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
+ */
74
+ run: <Reference extends BridgeFunctionReference>(reference: Reference, args: ArgsOfReference<Reference>, shardKey?: string) => Promise<ResultOfReference<Reference>>;
75
+ }
76
+ /**
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
+ */
89
+ declare const createContainerBridge: (options: ContainerBridgeOptions) => ContainerBridge;
90
+ export { type BridgeFunctionReference, type ContainerBridge, ContainerBridgeError, type ContainerBridgeOptions, type FetchLike, createContainerBridge };
@@ -0,0 +1,90 @@
1
+ import { LunoraError } from '@lunora/errors';
2
+ /** A `fetch` implementation — defaults to the runtime global. */
3
+ type FetchLike = (input: string, init: {
4
+ body: string;
5
+ headers: Record<string, string>;
6
+ method: string;
7
+ }) => Promise<{
8
+ json: () => Promise<unknown>;
9
+ ok: boolean;
10
+ status: number;
11
+ statusText?: string;
12
+ }>;
13
+ interface ContainerBridgeOptions {
14
+ /**
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
+ */
19
+ baseUrl: string;
20
+ /** Injectable `fetch` (tests / non-global runtimes). Defaults to `globalThis.fetch`. */
21
+ fetch?: FetchLike;
22
+ /**
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
+ */
27
+ token?: string;
28
+ }
29
+ /** Thrown when a Lunora function returns an error envelope. A `LunoraError` subclass carrying the wire `code`. */
30
+ declare class ContainerBridgeError extends LunoraError {
31
+ constructor(code: string, message: string);
32
+ }
33
+ /**
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
+ */
40
+ interface BridgeFunctionReference<Args = unknown, Result = unknown> {
41
+ readonly __lunoraPhantom?: {
42
+ args: Args;
43
+ returns: Result;
44
+ };
45
+ readonly __lunoraRef: string;
46
+ }
47
+ /** Infer the args type from a {@link BridgeFunctionReference} (or a `@lunora/client` reference). */
48
+ type ArgsOfReference<Reference> = Reference extends {
49
+ __lunoraPhantom?: {
50
+ args: infer Args;
51
+ };
52
+ } ? Args : never;
53
+ /** Infer the result type from a {@link BridgeFunctionReference} (or a `@lunora/client` reference). */
54
+ type ResultOfReference<Reference> = Reference extends {
55
+ __lunoraPhantom?: {
56
+ returns: infer Result;
57
+ };
58
+ } ? Result : never;
59
+ interface ContainerBridge {
60
+ /** Call an `action` by `namespace:fn` path. Alias of {@link ContainerBridge.call} for intent. */
61
+ action: <Result = unknown>(functionPath: string, args?: Record<string, unknown>, shardKey?: string) => Promise<Result>;
62
+ /** Call any Lunora function by `namespace:fn` path; the server resolves its kind. */
63
+ call: <Result = unknown>(functionPath: string, args?: Record<string, unknown>, shardKey?: string) => Promise<Result>;
64
+ /** Call a `mutation` by `namespace:fn` path. Alias of {@link ContainerBridge.call} for intent. */
65
+ mutation: <Result = unknown>(functionPath: string, args?: Record<string, unknown>, shardKey?: string) => Promise<Result>;
66
+ /** Call a `query` by `namespace:fn` path. Alias of {@link ContainerBridge.call} for intent. */
67
+ query: <Result = unknown>(functionPath: string, args?: Record<string, unknown>, shardKey?: string) => Promise<Result>;
68
+ /**
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
+ */
74
+ run: <Reference extends BridgeFunctionReference>(reference: Reference, args: ArgsOfReference<Reference>, shardKey?: string) => Promise<ResultOfReference<Reference>>;
75
+ }
76
+ /**
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
+ */
89
+ declare const createContainerBridge: (options: ContainerBridgeOptions) => ContainerBridge;
90
+ export { type BridgeFunctionReference, type ContainerBridge, ContainerBridgeError, type ContainerBridgeOptions, type FetchLike, createContainerBridge };
@@ -0,0 +1,79 @@
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 };