@lunora/runtime 1.0.0-alpha.15 → 1.0.0-alpha.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { RankDirection, RankPageRow, DatabaseWriterLike } from '@lunora/do';
2
2
  export type { RankDirection as RankPageDirection, RankPageRowKey as RankPageKey, RankPageRow, ShardRankPageResult } from '@lunora/do';
3
3
  import { WorkflowsRestClient } from '@lunora/workflow';
4
+ import { LunoraError as LunoraError$1, ErrorBody } from '@lunora/errors';
4
5
  /**
5
6
  * Turn-key incremental-sync source helpers for warehouse connectors
6
7
  * (Fivetran custom functions, Airbyte incremental sources).
@@ -1194,24 +1195,6 @@ interface ShardTrafficFanOutResult {
1194
1195
  shards: ReadonlyArray<ShardTrafficEntry>;
1195
1196
  }
1196
1197
  declare const createQueryCoordinator: (options: QueryCoordinatorOptions) => QueryCoordinator;
1197
- /**
1198
- * Secure-by-default HTTP edge for the Lunora worker.
1199
- *
1200
- * The worker's top-level `fetch` (see `./create-worker`) is the single choke
1201
- * point every response passes through — RPC, auth, admin, `httpRoute` handlers,
1202
- * and the SSR fallback alike. This module supplies what is applied there:
1203
- * `decorateResponse` adds baseline security headers plus, for allowed
1204
- * cross-origin requests, the matching `Access-Control-Allow-*` headers (never
1205
- * overwriting a header the inner handler set); `handleCorsPreflight` answers
1206
- * `OPTIONS` preflights for allowlisted origins; `enforceOrigin` is a CSRF guard
1207
- * that rejects state-changing, cookie-authenticated requests from untrusted
1208
- * origins.
1209
- *
1210
- * Every layer is on by default and individually disable-able through the
1211
- * `SecurityOptions` passed to `createWorker`. Resolution (`resolveSecurity`) is
1212
- * pure and platform-agnostic — it touches only the global `Request`/`Response`/
1213
- * `Headers`/`URL`, so it unit-tests under plain Node without workerd.
1214
- */
1215
1198
  /** Per-header overrides for {@link SecurityHeadersOptions}. `false` omits the header. */
1216
1199
  interface SecurityHeadersOptions {
1217
1200
  /**
@@ -1808,15 +1791,19 @@ interface WorkerOptions {
1808
1791
  /**
1809
1792
  * Opt into an authorization-open posture for sharded and fan-out access.
1810
1793
  *
1811
- * By default (this flag unset/`false`) the runtime FAILS CLOSED: when
1812
- * neither {@link WorkerOptions.authorizeShard} nor {@link WorkerOptions.authorizeFanOut}
1813
- * is configured, naming a non-default shard (a potential cross-tenant hop)
1814
- * or sending a fan-out envelope is rejected with a `403`
1815
- * (`FORBIDDEN_SHARD`/`FORBIDDEN_FANOUT`). Set this to `true` to allow such
1816
- * requests from any caller (including unauthenticated ones) appropriate
1817
- * only when every table is protected by per-row RLS. The runtime then emits
1818
- * a single `console.warn` so the open posture stays visible in logs. Has no
1819
- * effect once an `authorize*` callback is configured (those gate directly).
1794
+ * By default (this flag unset/`false`) the runtime FAILS CLOSED per
1795
+ * operation: naming a non-default shard (a potential cross-tenant hop) is
1796
+ * rejected with a `403` (`FORBIDDEN_SHARD`) unless
1797
+ * {@link WorkerOptions.authorizeShard} is configured, and a fan-out
1798
+ * envelope is rejected (`FORBIDDEN_FANOUT`) unless
1799
+ * {@link WorkerOptions.authorizeFanOut} is. Set this to `true` to allow
1800
+ * such requests from any caller (including unauthenticated ones)
1801
+ * appropriate only when every table is protected by per-row RLS. The
1802
+ * runtime then emits a single `console.warn` so the open posture stays
1803
+ * visible in logs. The flag is consulted per operation: it has no effect
1804
+ * on an operation whose own `authorize*` callback is configured (that
1805
+ * callback gates directly), but configuring only one of the two callbacks
1806
+ * does NOT cover the other operation.
1820
1807
  *
1821
1808
  * NOTE: this is a behaviour change from earlier alphas, where the same
1822
1809
  * situation was warn-once-then-allow. Apps that relied on client-chosen
@@ -2463,18 +2450,30 @@ interface DynamicShardRegistry extends ShardRegistry {
2463
2450
  }
2464
2451
  declare const createDynamicShardRegistry: (options: DynamicShardRegistryOptions) => DynamicShardRegistry;
2465
2452
  interface LunoraErrorBody {
2466
- error: {
2467
- code: string;
2468
- message: string;
2469
- };
2453
+ error: ErrorBody;
2470
2454
  }
2471
2455
  /**
2472
- * Error type recognised by the runtime's error middleware. Anything thrown
2473
- * that isn't a `LunoraError` is mapped to a generic 500 with code `INTERNAL`.
2456
+ * Convert any thrown value into a JSON error response.
2457
+ *
2458
+ * Delegates the envelope + redaction to `@lunora/errors`' {@link toErrorBody} —
2459
+ * a non-internal `LunoraError` (from `@lunora/server`, this runtime, or the
2460
+ * `@lunora/do` data layer) is echoed with its `code`/`message`/`hint`/`docsUrl`;
2461
+ * an internal-coded error keeps its status but its message is redacted; anything
2462
+ * else becomes a generic `INTERNAL` 500. All of these share the unified shape
2463
+ * recognized by `isLunoraError`.
2464
+ */
2465
+ declare const toErrorResponse: (error: unknown) => Response;
2466
+ /**
2467
+ * Transport-level error for the worker entry. A thin ergonomic wrapper over the
2468
+ * shared `@lunora/errors` `LunoraError` that keeps the runtime's historical
2469
+ * `(message, { code, status })` signature — the runtime mints these with
2470
+ * dispatch-specific codes (`METHOD_NOT_ALLOWED`, `*_NOT_CONFIGURED`, …) and an
2471
+ * explicit status, so they don't need a central catalog entry. Because it is a
2472
+ * real `LunoraError`, it carries the unified wire shape and is recognized by
2473
+ * `isLunoraError` everywhere. Anything thrown that isn't a `LunoraError`
2474
+ * is mapped to a generic 500 with code `INTERNAL`.
2474
2475
  */
2475
- declare class LunoraError extends Error {
2476
- readonly code: string;
2477
- readonly status: number;
2476
+ declare class LunoraError extends LunoraError$1 {
2478
2477
  constructor(message: string, options?: {
2479
2478
  cause?: unknown;
2480
2479
  code?: string;
@@ -2482,10 +2481,6 @@ declare class LunoraError extends Error {
2482
2481
  });
2483
2482
  toResponse(): Response;
2484
2483
  }
2485
- /** Shape recognised by the runtime's structural error checks. */
2486
-
2487
- /** Convert any thrown value into a JSON error response. */
2488
- declare const toErrorResponse: (error: unknown) => Response;
2489
2484
  /** Shared shape for sinks that can be limited to error events only. */
2490
2485
  interface OnlyErrorsOption {
2491
2486
  /** When true, only events with `ok === false` are forwarded. */
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { RankDirection, RankPageRow, DatabaseWriterLike } from '@lunora/do';
2
2
  export type { RankDirection as RankPageDirection, RankPageRowKey as RankPageKey, RankPageRow, ShardRankPageResult } from '@lunora/do';
3
3
  import { WorkflowsRestClient } from '@lunora/workflow';
4
+ import { LunoraError as LunoraError$1, ErrorBody } from '@lunora/errors';
4
5
  /**
5
6
  * Turn-key incremental-sync source helpers for warehouse connectors
6
7
  * (Fivetran custom functions, Airbyte incremental sources).
@@ -1194,24 +1195,6 @@ interface ShardTrafficFanOutResult {
1194
1195
  shards: ReadonlyArray<ShardTrafficEntry>;
1195
1196
  }
1196
1197
  declare const createQueryCoordinator: (options: QueryCoordinatorOptions) => QueryCoordinator;
1197
- /**
1198
- * Secure-by-default HTTP edge for the Lunora worker.
1199
- *
1200
- * The worker's top-level `fetch` (see `./create-worker`) is the single choke
1201
- * point every response passes through — RPC, auth, admin, `httpRoute` handlers,
1202
- * and the SSR fallback alike. This module supplies what is applied there:
1203
- * `decorateResponse` adds baseline security headers plus, for allowed
1204
- * cross-origin requests, the matching `Access-Control-Allow-*` headers (never
1205
- * overwriting a header the inner handler set); `handleCorsPreflight` answers
1206
- * `OPTIONS` preflights for allowlisted origins; `enforceOrigin` is a CSRF guard
1207
- * that rejects state-changing, cookie-authenticated requests from untrusted
1208
- * origins.
1209
- *
1210
- * Every layer is on by default and individually disable-able through the
1211
- * `SecurityOptions` passed to `createWorker`. Resolution (`resolveSecurity`) is
1212
- * pure and platform-agnostic — it touches only the global `Request`/`Response`/
1213
- * `Headers`/`URL`, so it unit-tests under plain Node without workerd.
1214
- */
1215
1198
  /** Per-header overrides for {@link SecurityHeadersOptions}. `false` omits the header. */
1216
1199
  interface SecurityHeadersOptions {
1217
1200
  /**
@@ -1808,15 +1791,19 @@ interface WorkerOptions {
1808
1791
  /**
1809
1792
  * Opt into an authorization-open posture for sharded and fan-out access.
1810
1793
  *
1811
- * By default (this flag unset/`false`) the runtime FAILS CLOSED: when
1812
- * neither {@link WorkerOptions.authorizeShard} nor {@link WorkerOptions.authorizeFanOut}
1813
- * is configured, naming a non-default shard (a potential cross-tenant hop)
1814
- * or sending a fan-out envelope is rejected with a `403`
1815
- * (`FORBIDDEN_SHARD`/`FORBIDDEN_FANOUT`). Set this to `true` to allow such
1816
- * requests from any caller (including unauthenticated ones) appropriate
1817
- * only when every table is protected by per-row RLS. The runtime then emits
1818
- * a single `console.warn` so the open posture stays visible in logs. Has no
1819
- * effect once an `authorize*` callback is configured (those gate directly).
1794
+ * By default (this flag unset/`false`) the runtime FAILS CLOSED per
1795
+ * operation: naming a non-default shard (a potential cross-tenant hop) is
1796
+ * rejected with a `403` (`FORBIDDEN_SHARD`) unless
1797
+ * {@link WorkerOptions.authorizeShard} is configured, and a fan-out
1798
+ * envelope is rejected (`FORBIDDEN_FANOUT`) unless
1799
+ * {@link WorkerOptions.authorizeFanOut} is. Set this to `true` to allow
1800
+ * such requests from any caller (including unauthenticated ones)
1801
+ * appropriate only when every table is protected by per-row RLS. The
1802
+ * runtime then emits a single `console.warn` so the open posture stays
1803
+ * visible in logs. The flag is consulted per operation: it has no effect
1804
+ * on an operation whose own `authorize*` callback is configured (that
1805
+ * callback gates directly), but configuring only one of the two callbacks
1806
+ * does NOT cover the other operation.
1820
1807
  *
1821
1808
  * NOTE: this is a behaviour change from earlier alphas, where the same
1822
1809
  * situation was warn-once-then-allow. Apps that relied on client-chosen
@@ -2463,18 +2450,30 @@ interface DynamicShardRegistry extends ShardRegistry {
2463
2450
  }
2464
2451
  declare const createDynamicShardRegistry: (options: DynamicShardRegistryOptions) => DynamicShardRegistry;
2465
2452
  interface LunoraErrorBody {
2466
- error: {
2467
- code: string;
2468
- message: string;
2469
- };
2453
+ error: ErrorBody;
2470
2454
  }
2471
2455
  /**
2472
- * Error type recognised by the runtime's error middleware. Anything thrown
2473
- * that isn't a `LunoraError` is mapped to a generic 500 with code `INTERNAL`.
2456
+ * Convert any thrown value into a JSON error response.
2457
+ *
2458
+ * Delegates the envelope + redaction to `@lunora/errors`' {@link toErrorBody} —
2459
+ * a non-internal `LunoraError` (from `@lunora/server`, this runtime, or the
2460
+ * `@lunora/do` data layer) is echoed with its `code`/`message`/`hint`/`docsUrl`;
2461
+ * an internal-coded error keeps its status but its message is redacted; anything
2462
+ * else becomes a generic `INTERNAL` 500. All of these share the unified shape
2463
+ * recognized by `isLunoraError`.
2464
+ */
2465
+ declare const toErrorResponse: (error: unknown) => Response;
2466
+ /**
2467
+ * Transport-level error for the worker entry. A thin ergonomic wrapper over the
2468
+ * shared `@lunora/errors` `LunoraError` that keeps the runtime's historical
2469
+ * `(message, { code, status })` signature — the runtime mints these with
2470
+ * dispatch-specific codes (`METHOD_NOT_ALLOWED`, `*_NOT_CONFIGURED`, …) and an
2471
+ * explicit status, so they don't need a central catalog entry. Because it is a
2472
+ * real `LunoraError`, it carries the unified wire shape and is recognized by
2473
+ * `isLunoraError` everywhere. Anything thrown that isn't a `LunoraError`
2474
+ * is mapped to a generic 500 with code `INTERNAL`.
2474
2475
  */
2475
- declare class LunoraError extends Error {
2476
- readonly code: string;
2477
- readonly status: number;
2476
+ declare class LunoraError extends LunoraError$1 {
2478
2477
  constructor(message: string, options?: {
2479
2478
  cause?: unknown;
2480
2479
  code?: string;
@@ -2482,10 +2481,6 @@ declare class LunoraError extends Error {
2482
2481
  });
2483
2482
  toResponse(): Response;
2484
2483
  }
2485
- /** Shape recognised by the runtime's structural error checks. */
2486
-
2487
- /** Convert any thrown value into a JSON error response. */
2488
- declare const toErrorResponse: (error: unknown) => Response;
2489
2484
  /** Shared shape for sinks that can be limited to error events only. */
2490
2485
  interface OnlyErrorsOption {
2491
2486
  /** When true, only events with `ok === false` are forwarded. */
package/dist/index.mjs CHANGED
@@ -1,15 +1,15 @@
1
1
  export { toAirbyteMessages, toFivetranResponse } from './packem_shared/toAirbyteMessages-DrHdplb4.mjs';
2
- export { composeWorker, createLunoraHandler, createWorker, defineRpcEnvelope, resolveLunoraOptions, withFrameworkWorker } from './packem_shared/composeWorker-BUf56-tZ.mjs';
3
- export { createCrossShardRelationCapabilities } from './packem_shared/createCrossShardRelationCapabilities-C0KOf7er.mjs';
4
- export { DEFAULT_REGISTRY_CACHE_TTL_MS, SHARD_REGISTRY_DO_NAME, createDynamicShardRegistry } from './packem_shared/DEFAULT_REGISTRY_CACHE_TTL_MS-ocax8v0n.mjs';
5
- export { LunoraError, toErrorResponse } from './packem_shared/LunoraError-CL0aOtpo.mjs';
2
+ export { composeWorker, createLunoraHandler, createWorker, defineRpcEnvelope, resolveLunoraOptions, withFrameworkWorker } from './packem_shared/composeWorker-DU8vDrgA.mjs';
3
+ export { createCrossShardRelationCapabilities } from './packem_shared/createCrossShardRelationCapabilities-CbcWjkAn.mjs';
4
+ export { DEFAULT_REGISTRY_CACHE_TTL_MS, SHARD_REGISTRY_DO_NAME, createDynamicShardRegistry } from './packem_shared/DEFAULT_REGISTRY_CACHE_TTL_MS-B3pA7aXp.mjs';
5
+ export { LunoraError, toErrorResponse } from './packem_shared/LunoraError-Bpb9EFJ3.mjs';
6
6
  export { emitLogEvent, emitRpcEvent } from './packem_shared/emitLogEvent-pEdtqAK8.mjs';
7
7
  export { analyticsEngineSink, combineSinks, consoleSink, sentrySink, webhookSink } from './packem_shared/analyticsEngineSink-DqEvrQs0.mjs';
8
- export { createQueryCoordinator, createStaticShardRegistry, mergeStrategyForAggregate } from './packem_shared/createQueryCoordinator-ZeZYUPNu.mjs';
8
+ export { createQueryCoordinator, createStaticShardRegistry, mergeStrategyForAggregate } from './packem_shared/createQueryCoordinator-DNCJzOZE.mjs';
9
9
  export { applyJurisdiction, resolveShard } from './packem_shared/applyJurisdiction-BkZtTkct.mjs';
10
- export { decorateResponse, enforceOrigin, handleCorsPreflight, resolveSecurity } from './packem_shared/decorateResponse-HRXo-oOD.mjs';
10
+ export { decorateResponse, enforceOrigin, handleCorsPreflight, resolveSecurity } from './packem_shared/decorateResponse-CsZc49QC.mjs';
11
11
  export { NOOP_EXECUTION_CONTEXT } from './packem_shared/NOOP_EXECUTION_CONTEXT-CCTu0Bf1.mjs';
12
- export { composeIdentityResolvers, routeIdentityResolvers } from './packem_shared/composeIdentityResolvers-YjvUKisc.mjs';
12
+ export { composeIdentityResolvers, routeIdentityResolvers } from './packem_shared/composeIdentityResolvers-XGjO7V1J.mjs';
13
13
 
14
14
  const VERSION = "0.0.0";
15
15
 
@@ -1,3 +1,4 @@
1
+ import { LunoraError } from './LunoraError-Bpb9EFJ3.mjs';
1
2
  import { applyJurisdiction } from './applyJurisdiction-BkZtTkct.mjs';
2
3
 
3
4
  const SHARD_REGISTRY_DO_NAME = "__lunora_shard_registry__";
@@ -42,7 +43,7 @@ const createDynamicShardRegistry = (options) => {
42
43
  }
43
44
  const response = await get(`/list?table=${encodeURIComponent(table)}`);
44
45
  if (!response.ok) {
45
- throw new Error(`shard registry /list returned ${String(response.status)}`);
46
+ throw new LunoraError(`shard registry /list returned ${String(response.status)}`);
46
47
  }
47
48
  const { shardKeys } = await decodeJson(response);
48
49
  if (cacheTtlMs > 0) {
@@ -53,14 +54,14 @@ const createDynamicShardRegistry = (options) => {
53
54
  async register(table, shardKey) {
54
55
  const response = await post("/register", { shardKey, table });
55
56
  if (!response.ok) {
56
- throw new Error(`shard registry /register returned ${String(response.status)}`);
57
+ throw new LunoraError(`shard registry /register returned ${String(response.status)}`);
57
58
  }
58
59
  cache.delete(table);
59
60
  },
60
61
  async snapshot() {
61
62
  const response = await get("/snapshot");
62
63
  if (!response.ok) {
63
- throw new Error(`shard registry /snapshot returned ${String(response.status)}`);
64
+ throw new LunoraError(`shard registry /snapshot returned ${String(response.status)}`);
64
65
  }
65
66
  const { tables } = await decodeJson(response);
66
67
  return tables;
@@ -68,7 +69,7 @@ const createDynamicShardRegistry = (options) => {
68
69
  async unregister(table, shardKey) {
69
70
  const response = await post("/unregister", { shardKey, table });
70
71
  if (!response.ok) {
71
- throw new Error(`shard registry /unregister returned ${String(response.status)}`);
72
+ throw new LunoraError(`shard registry /unregister returned ${String(response.status)}`);
72
73
  }
73
74
  cache.delete(table);
74
75
  }
@@ -0,0 +1,22 @@
1
+ import { LunoraError as LunoraError$1, toErrorBody } from '@lunora/errors';
2
+
3
+ const toErrorResponse = (error) => {
4
+ const { body, redacted, status } = toErrorBody(error, { fallbackCode: "INTERNAL", redactedMessage: "Internal error" });
5
+ if (redacted) {
6
+ console.error("[lunora] internal error:", error);
7
+ }
8
+ return Response.json({ error: body }, {
9
+ headers: { "content-type": "application/json" },
10
+ status
11
+ });
12
+ };
13
+ class LunoraError extends LunoraError$1 {
14
+ constructor(message, options) {
15
+ super(options?.code ?? "INTERNAL", message, { cause: options?.cause, status: options?.status });
16
+ }
17
+ toResponse() {
18
+ return toErrorResponse(this);
19
+ }
20
+ }
21
+
22
+ export { LunoraError, toErrorResponse };
@@ -1,4 +1,4 @@
1
- import { LunoraError } from './LunoraError-CL0aOtpo.mjs';
1
+ import { LunoraError } from './LunoraError-Bpb9EFJ3.mjs';
2
2
 
3
3
  const composeIdentityResolvers = (resolvers, options = {}) => {
4
4
  const onError = options.onError ?? "fail-closed";
@@ -1,10 +1,11 @@
1
+ import { isLunoraError } from '@lunora/errors';
1
2
  import { NOOP_EXECUTION_CONTEXT } from './NOOP_EXECUTION_CONTEXT-CCTu0Bf1.mjs';
2
- import { LunoraError, toErrorResponse, isStructuralLunoraError, isStructuralConflictError } from './LunoraError-CL0aOtpo.mjs';
3
- import { wrapResolverWithContract } from './composeIdentityResolvers-YjvUKisc.mjs';
4
- export { composeIdentityResolvers, routeIdentityResolvers } from './composeIdentityResolvers-YjvUKisc.mjs';
3
+ import { LunoraError, toErrorResponse } from './LunoraError-Bpb9EFJ3.mjs';
4
+ import { wrapResolverWithContract } from './composeIdentityResolvers-XGjO7V1J.mjs';
5
+ export { composeIdentityResolvers, routeIdentityResolvers } from './composeIdentityResolvers-XGjO7V1J.mjs';
5
6
  import { emitRpcEvent } from './emitLogEvent-pEdtqAK8.mjs';
6
7
  import { resolveShard, applyJurisdiction } from './applyJurisdiction-BkZtTkct.mjs';
7
- import { resolveSecurity, handleCorsPreflight, enforceOrigin, decorateResponse, enforceWebSocketOrigin } from './decorateResponse-HRXo-oOD.mjs';
8
+ import { resolveSecurity, handleCorsPreflight, enforceOrigin, decorateResponse, enforceWebSocketOrigin } from './decorateResponse-CsZc49QC.mjs';
8
9
 
9
10
  const RELAY_NAME_INFIX = "::relay::";
10
11
  const relayName = (ownerKey, index) => `${ownerKey}${RELAY_NAME_INFIX}${String(index)}`;
@@ -251,7 +252,7 @@ const buildAuthAdminRoutes = (deps) => {
251
252
  const candidate = error;
252
253
  const code = typeof candidate.code === "string" ? candidate.code : "AUTH_ADMIN_ERROR";
253
254
  console.error("[lunora] auth admin operation failed:", error);
254
- throw new LunoraError("auth admin operation failed", { code, status: AUTH_ADMIN_ERROR_STATUS[code] ?? 400 });
255
+ throw new LunoraError("auth admin operation failed", { code, status: AUTH_ADMIN_ERROR_STATUS[code] ?? 500 });
255
256
  }
256
257
  };
257
258
  const handle = async (request, descriptor) => {
@@ -1740,7 +1741,7 @@ const isAuthAttemptPath = (pathname, basePath) => {
1740
1741
  return AUTH_ATTEMPT_SEGMENTS.some((segment) => suffix === segment || suffix.startsWith(`${segment}/`));
1741
1742
  };
1742
1743
  const buildErrorEvent = (functionPath, durationMs, error, extra) => {
1743
- const mappable = error instanceof LunoraError || isStructuralLunoraError(error) || isStructuralConflictError(error);
1744
+ const mappable = isLunoraError(error);
1744
1745
  const code = mappable ? error.code : "INTERNAL_SERVER_ERROR";
1745
1746
  const status = mappable ? error.status : 500;
1746
1747
  const message = error instanceof Error ? error.message : String(error);
@@ -2911,7 +2912,7 @@ const resolveLunoraOptions = (options, env) => {
2911
2912
  }
2912
2913
  const shardDO = options.shardDO ?? env?.SHARD;
2913
2914
  if (!shardDO) {
2914
- throw new Error(
2915
+ throw new LunoraError(
2915
2916
  "@lunora/runtime: no shard Durable Object namespace found. Bind `SHARD` in wrangler.jsonc, or pass `createLunoraHandler({ shardDO: env.MY_SHARD })`."
2916
2917
  );
2917
2918
  }
@@ -1,3 +1,5 @@
1
+ import { LunoraError } from './LunoraError-Bpb9EFJ3.mjs';
2
+
1
3
  const RPC_ENDPOINT = "/_lunora/rpc";
2
4
  const buildIdentityHeaders = (options) => {
3
5
  const headers = { "content-type": "application/json" };
@@ -19,12 +21,12 @@ const fanOutRelation = async (options, body, label) => {
19
21
  })
20
22
  );
21
23
  if (!response.ok) {
22
- throw new Error(`cross-shard relation ${label} failed: worker returned ${String(response.status)}`);
24
+ throw new LunoraError(`cross-shard relation ${label} failed: worker returned ${String(response.status)}`);
23
25
  }
24
26
  const result = await response.json();
25
27
  if (typeof result.failed === "number" && result.failed > 0) {
26
28
  const reached = (typeof result.ok === "number" ? result.ok : 0) + result.failed;
27
- throw new Error(
29
+ throw new LunoraError(
28
30
  `cross-shard relation ${label} failed on ${String(result.failed)} of ${String(reached)} shard(s) — refusing to return a partial result`
29
31
  );
30
32
  }
@@ -1,4 +1,4 @@
1
- import { LunoraError } from './LunoraError-CL0aOtpo.mjs';
1
+ import { LunoraError } from './LunoraError-Bpb9EFJ3.mjs';
2
2
  import { resolveShard } from './applyJurisdiction-BkZtTkct.mjs';
3
3
 
4
4
  const createStaticShardRegistry = (table_to_keys) => {
@@ -1,3 +1,5 @@
1
+ import { LunoraError } from './LunoraError-Bpb9EFJ3.mjs';
2
+
1
3
  const DEFAULT_CSP = "default-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none'";
2
4
  const htmlCspFor = (frameOptions) => {
3
5
  const parts = ["base-uri 'none'", "object-src 'none'"];
@@ -75,15 +77,14 @@ const resolveCors = (input) => {
75
77
  if (typeof origins === "function") {
76
78
  isAllowed = origins;
77
79
  isExplicitlyAllowed = origins;
78
- if (allowCredentials) {
79
- console.warn(
80
- "@lunora/runtime: security.cors combines a custom `allowedOrigins` predicate with `allowCredentials: true`. Ensure the predicate matches ONLY trusted origins by exact equality an over-broad predicate (e.g. `() => true`, or `endsWith`/`includes` checks) reflects any origin with credentials, defeating the allowlist and the CSRF guard."
81
- );
82
- }
80
+ const credentialsNote = allowCredentials ? " AND reflects matching origins with credentials (`allowCredentials: true`)" : "";
81
+ console.warn(
82
+ `@lunora/runtime: security.cors uses a custom \`allowedOrigins\` predicate. It is trusted by the CSRF and WebSocket origin checks${credentialsNote} — ensure it matches ONLY trusted origins by exact equality; an over-broad predicate (e.g. \`() => true\`, or \`endsWith\`/\`includes\` checks) defeats the allowlist.`
83
+ );
83
84
  } else {
84
85
  const originsList = origins;
85
86
  if (originsList.includes("*") && allowCredentials) {
86
- throw new Error(
87
+ throw new LunoraError(
87
88
  '@lunora/runtime: security.cors cannot combine a wildcard origin ("*") with allowCredentials: true — browsers reject it and it defeats the allowlist.'
88
89
  );
89
90
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/runtime",
3
- "version": "1.0.0-alpha.15",
3
+ "version": "1.0.0-alpha.17",
4
4
  "description": "Lunora Worker runtime: the RPC router, shard resolver, and query coordinator",
5
5
  "keywords": [
6
6
  "cloudflare",
@@ -45,6 +45,9 @@
45
45
  "publishConfig": {
46
46
  "access": "public"
47
47
  },
48
+ "dependencies": {
49
+ "@lunora/errors": "1.0.0-alpha.1"
50
+ },
48
51
  "engines": {
49
52
  "node": "^22.15.0 || >=24.11.0"
50
53
  }
@@ -1,46 +0,0 @@
1
- class LunoraError extends Error {
2
- code;
3
- status;
4
- constructor(message, options) {
5
- super(message, { cause: options?.cause });
6
- this.name = "LunoraError";
7
- this.code = options?.code ?? "INTERNAL";
8
- this.status = options?.status ?? 500;
9
- }
10
- toResponse() {
11
- const body = { error: { code: this.code, message: this.message } };
12
- return Response.json(body, {
13
- headers: { "content-type": "application/json" },
14
- status: this.status
15
- });
16
- }
17
- }
18
- const hasErrorShape = (error, name) => {
19
- if (!error || typeof error !== "object") {
20
- return false;
21
- }
22
- const candidate = error;
23
- return candidate.name === name && typeof candidate.code === "string" && typeof candidate.status === "number" && typeof candidate.message === "string";
24
- };
25
- const isStructuralConflictError = (error) => hasErrorShape(error, "ConflictError");
26
- const isStructuralLunoraError = (error) => hasErrorShape(error, "LunoraError");
27
- const toErrorResponse = (error) => {
28
- if (error instanceof LunoraError) {
29
- return error.toResponse();
30
- }
31
- if (isStructuralLunoraError(error) || isStructuralConflictError(error)) {
32
- const body2 = { error: { code: error.code, message: error.message } };
33
- return Response.json(body2, {
34
- headers: { "content-type": "application/json" },
35
- status: error.status
36
- });
37
- }
38
- console.error("[lunora] unhandled error:", error);
39
- const body = { error: { code: "INTERNAL", message: "Internal error" } };
40
- return Response.json(body, {
41
- headers: { "content-type": "application/json" },
42
- status: 500
43
- });
44
- };
45
-
46
- export { LunoraError, isStructuralConflictError, isStructuralLunoraError, toErrorResponse };