@lunora/runtime 1.0.0-alpha.16 → 1.0.0-alpha.18

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
  /**
@@ -2467,18 +2450,30 @@ interface DynamicShardRegistry extends ShardRegistry {
2467
2450
  }
2468
2451
  declare const createDynamicShardRegistry: (options: DynamicShardRegistryOptions) => DynamicShardRegistry;
2469
2452
  interface LunoraErrorBody {
2470
- error: {
2471
- code: string;
2472
- message: string;
2473
- };
2453
+ error: ErrorBody;
2474
2454
  }
2475
2455
  /**
2476
- * Error type recognised by the runtime's error middleware. Anything thrown
2477
- * 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`.
2478
2475
  */
2479
- declare class LunoraError extends Error {
2480
- readonly code: string;
2481
- readonly status: number;
2476
+ declare class LunoraError extends LunoraError$1 {
2482
2477
  constructor(message: string, options?: {
2483
2478
  cause?: unknown;
2484
2479
  code?: string;
@@ -2486,10 +2481,6 @@ declare class LunoraError extends Error {
2486
2481
  });
2487
2482
  toResponse(): Response;
2488
2483
  }
2489
- /** Shape recognised by the runtime's structural error checks. */
2490
-
2491
- /** Convert any thrown value into a JSON error response. */
2492
- declare const toErrorResponse: (error: unknown) => Response;
2493
2484
  /** Shared shape for sinks that can be limited to error events only. */
2494
2485
  interface OnlyErrorsOption {
2495
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
  /**
@@ -2467,18 +2450,30 @@ interface DynamicShardRegistry extends ShardRegistry {
2467
2450
  }
2468
2451
  declare const createDynamicShardRegistry: (options: DynamicShardRegistryOptions) => DynamicShardRegistry;
2469
2452
  interface LunoraErrorBody {
2470
- error: {
2471
- code: string;
2472
- message: string;
2473
- };
2453
+ error: ErrorBody;
2474
2454
  }
2475
2455
  /**
2476
- * Error type recognised by the runtime's error middleware. Anything thrown
2477
- * 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`.
2478
2475
  */
2479
- declare class LunoraError extends Error {
2480
- readonly code: string;
2481
- readonly status: number;
2476
+ declare class LunoraError extends LunoraError$1 {
2482
2477
  constructor(message: string, options?: {
2483
2478
  cause?: unknown;
2484
2479
  code?: string;
@@ -2486,10 +2481,6 @@ declare class LunoraError extends Error {
2486
2481
  });
2487
2482
  toResponse(): Response;
2488
2483
  }
2489
- /** Shape recognised by the runtime's structural error checks. */
2490
-
2491
- /** Convert any thrown value into a JSON error response. */
2492
- declare const toErrorResponse: (error: unknown) => Response;
2493
2484
  /** Shared shape for sinks that can be limited to error events only. */
2494
2485
  interface OnlyErrorsOption {
2495
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-BnZf6iRH.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-CR1Z2s7k.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-igd8FJqk.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-igd8FJqk.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)}`;
@@ -1727,6 +1728,7 @@ const SCHEDULER_DISPATCH_PATH = "/_lunora/scheduler/dispatch";
1727
1728
  const CRON_JOBS_RUN_PATH = "/_lunora/admin/cron-jobs/run";
1728
1729
  const ADMIN_PATH_PREFIX = "/_lunora/admin/";
1729
1730
  const MIGRATE_PATH = "/_lunora/migrate";
1731
+ const STATUS_PATH = "/_lunora/status";
1730
1732
  const isAdminPath = (pathname) => pathname.startsWith(ADMIN_PATH_PREFIX) || pathname === MIGRATE_PATH;
1731
1733
  const DEFAULT_AUTH_BASE_PATH = "/api/auth";
1732
1734
  const RECORD_AUTH_EVENT_OP = "__lunora_admin__:recordAuthEvent";
@@ -1740,7 +1742,7 @@ const isAuthAttemptPath = (pathname, basePath) => {
1740
1742
  return AUTH_ATTEMPT_SEGMENTS.some((segment) => suffix === segment || suffix.startsWith(`${segment}/`));
1741
1743
  };
1742
1744
  const buildErrorEvent = (functionPath, durationMs, error, extra) => {
1743
- const mappable = error instanceof LunoraError || isStructuralLunoraError(error) || isStructuralConflictError(error);
1745
+ const mappable = isLunoraError(error);
1744
1746
  const code = mappable ? error.code : "INTERNAL_SERVER_ERROR";
1745
1747
  const status = mappable ? error.status : 500;
1746
1748
  const message = error instanceof Error ? error.message : String(error);
@@ -2767,6 +2769,12 @@ const createWorker = (options) => {
2767
2769
  };
2768
2770
  const customRoutes = options.routes !== void 0 && Object.keys(options.routes).length > 0 ? options.routes : void 0;
2769
2771
  const internalRoutes = {
2772
+ [STATUS_PATH]: (request) => {
2773
+ if (request.method !== "GET" && request.method !== "HEAD") {
2774
+ return new Response(void 0, { headers: { allow: "GET, HEAD" }, status: 405 });
2775
+ }
2776
+ return Response.json({ ok: true }, { headers: { "cache-control": "no-store" } });
2777
+ },
2770
2778
  [WS_PATH]: (request, env, url) => handleWebSocketUpgrade(request, env, url),
2771
2779
  [RPC_PATH]: (request, env, _url, context) => handleRpc(request, env, context),
2772
2780
  [RPC_BATCH_PATH]: (request, env, _url, context) => handleBatchRpc(request, env, context),
@@ -2911,7 +2919,7 @@ const resolveLunoraOptions = (options, env) => {
2911
2919
  }
2912
2920
  const shardDO = options.shardDO ?? env?.SHARD;
2913
2921
  if (!shardDO) {
2914
- throw new Error(
2922
+ throw new LunoraError(
2915
2923
  "@lunora/runtime: no shard Durable Object namespace found. Bind `SHARD` in wrangler.jsonc, or pass `createLunoraHandler({ shardDO: env.MY_SHARD })`."
2916
2924
  );
2917
2925
  }
@@ -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'"];
@@ -82,7 +84,7 @@ const resolveCors = (input) => {
82
84
  } else {
83
85
  const originsList = origins;
84
86
  if (originsList.includes("*") && allowCredentials) {
85
- throw new Error(
87
+ throw new LunoraError(
86
88
  '@lunora/runtime: security.cors cannot combine a wildcard origin ("*") with allowCredentials: true — browsers reject it and it defeats the allowlist.'
87
89
  );
88
90
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/runtime",
3
- "version": "1.0.0-alpha.16",
3
+ "version": "1.0.0-alpha.18",
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 };