@lunora/errors 0.0.1 → 1.0.0-alpha.2

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,340 @@
1
+ /**
2
+ * The central Lunora error catalog — the single source of truth mapping a
3
+ * machine-readable `code` to its transport `status`, a short human `title`, and
4
+ * (where useful) an actionable Markdown `hint` plus a `docsUrl`.
5
+ *
6
+ * This table is consumed everywhere an error is surfaced: the runtime/DO wire
7
+ * mappers (status), the client SDK (code discrimination), the CLI renderer and
8
+ * the Vite overlay (hint), and the Studio UI (title + hint + docs link). It also
9
+ * absorbs the former `@lunora/codegen` "solutions" table (see {@link MESSAGE_SOLUTIONS})
10
+ * so codegen build-time errors — which are thrown as plain messages into
11
+ * generated code and lose their class identity before a consumer sees them —
12
+ * keep their message-matched hints.
13
+ */
14
+ /** Markdown hint: a single string or an array of lines. Shape matches `@visulima/error`'s `hint`. */
15
+ type ErrorHint = string | string[];
16
+ /** A catalog entry: the fixed metadata for one error `code`. */
17
+ interface ErrorCatalogEntry {
18
+ /** Optional URL to deeper docs for this error. */
19
+ docsUrl?: string;
20
+ /** Optional actionable fix, authored as Markdown (rendered by CLI/overlay/Studio). */
21
+ hint?: ErrorHint;
22
+ /**
23
+ * When `true`, this code's `message` must NOT cross the wire — an internal
24
+ * failure or unhandled invariant may carry SQL fragments, file paths, or
25
+ * internal identifiers. The transport mappers emit a generic message for
26
+ * these (and log the real one server-side). See {@link isInternalCode}.
27
+ */
28
+ internal?: boolean;
29
+ /** HTTP/RPC status this code maps to on the wire. */
30
+ status: number;
31
+ /** Short, human-readable summary. */
32
+ title: string;
33
+ }
34
+ /**
35
+ * Every well-known Lunora error code. Domain packages may throw additional
36
+ * codes (passing an explicit `status`); those are added here as their package is
37
+ * migrated. The keys of this object form the {@link LunoraErrorCode} union.
38
+ */
39
+ declare const ERROR_CATALOG: {
40
+ readonly BAD_REQUEST: {
41
+ readonly status: 400;
42
+ readonly title: "Bad request";
43
+ };
44
+ readonly UNAUTHORIZED: {
45
+ readonly status: 401;
46
+ readonly title: "Unauthorized";
47
+ };
48
+ readonly FORBIDDEN: {
49
+ readonly status: 403;
50
+ readonly title: "Forbidden";
51
+ };
52
+ readonly NOT_FOUND: {
53
+ readonly status: 404;
54
+ readonly title: "Not found";
55
+ };
56
+ readonly CONFLICT: {
57
+ readonly hint: readonly ["Another write changed this row while your mutation was running (optimistic concurrency conflict).", "", "Re-read the row and retry the mutation with the fresh value. Lunora serializes a DO's mutations, so a persistent conflict usually means the handler conflicts **with itself** (e.g. a trigger or cascade touching the same row) — split that work rather than adding a retry loop."];
58
+ readonly status: 409;
59
+ readonly title: "Conflict";
60
+ };
61
+ readonly NOT_UNIQUE: {
62
+ readonly hint: readonly ["`.unique()` matched more than one document — it expects the query to identify at most one row.", "", "- If several matches are legitimate, use `.first()` (take one) or `.collect()` (take all) instead.", "- Otherwise tighten the query (e.g. filter on a unique/indexed field) so it can only match one row."];
63
+ readonly status: 400;
64
+ readonly title: "Query matched more than one document";
65
+ };
66
+ readonly VALIDATION_ERROR: {
67
+ readonly status: 400;
68
+ readonly title: "Validation failed";
69
+ };
70
+ readonly TOO_MANY_REQUESTS: {
71
+ readonly status: 429;
72
+ readonly title: "Too many requests";
73
+ };
74
+ readonly UNPROCESSABLE: {
75
+ readonly status: 422;
76
+ readonly title: "Unprocessable";
77
+ };
78
+ readonly NOT_IMPLEMENTED: {
79
+ readonly status: 501;
80
+ readonly title: "Not implemented";
81
+ }; /** RPC/REST dispatch codes emitted by the runtime + Durable Object router. */
82
+ readonly FUNCTION_NOT_FOUND: {
83
+ readonly status: 404;
84
+ readonly title: "Function not found";
85
+ };
86
+ readonly METHOD_NOT_ALLOWED: {
87
+ readonly status: 405;
88
+ readonly title: "Method not allowed";
89
+ };
90
+ readonly PAYLOAD_TOO_LARGE: {
91
+ readonly status: 413;
92
+ readonly title: "Payload too large";
93
+ }; /** Free-form internal failure — redacted to a generic message on the wire. */
94
+ readonly INTERNAL: {
95
+ readonly internal: true;
96
+ readonly status: 500;
97
+ readonly title: "Internal error";
98
+ };
99
+ /** Alias of {@link ERROR_CATALOG.INTERNAL} kept for `@lunora/server`'s historical code name. */
100
+ readonly INTERNAL_SERVER_ERROR: {
101
+ readonly internal: true;
102
+ readonly status: 500;
103
+ readonly title: "Internal error";
104
+ };
105
+ /** Non-mappable throw crossed the RPC boundary. */
106
+ readonly RPC_FAILED: {
107
+ readonly internal: true;
108
+ readonly status: 500;
109
+ readonly title: "Internal error";
110
+ };
111
+ readonly COUNT_RLS_UNSUPPORTED: {
112
+ readonly status: 422;
113
+ readonly title: "count() is unsupported under an RLS policy";
114
+ };
115
+ readonly MASK_UNSUPPORTED: {
116
+ readonly status: 422;
117
+ readonly title: "Aggregation over a masked column is unsupported";
118
+ };
119
+ readonly RELATION_PREDICATE_UNSUPPORTED: {
120
+ readonly status: 422;
121
+ readonly title: "Relation predicate is unsupported in a write policy";
122
+ };
123
+ readonly RLS_REQUIRED: {
124
+ readonly hint: readonly ["This table is secure-by-default: it has no `.public()` marker and no RLS policy resolved for the caller, so the read fails closed.", "", "Add a read policy with `.rls(...)`, or mark the table `.public()` if it is intentionally world-readable."];
125
+ readonly status: 403;
126
+ readonly title: "RLS policy required";
127
+ };
128
+ readonly SHARD_ERROR: {
129
+ readonly status: 503;
130
+ readonly title: "Shard error";
131
+ };
132
+ readonly SHARD_UNAVAILABLE: {
133
+ readonly status: 503;
134
+ readonly title: "Shard unavailable";
135
+ };
136
+ readonly OFFLINE_IDENTITY_CHANGED: {
137
+ readonly status: 409;
138
+ readonly title: "Offline identity changed";
139
+ }; /** Package-specific codes. Build-time-only — never cross the RPC wire, so deliberately not `internal`. */
140
+ readonly CODEGEN_DIAGNOSTIC: {
141
+ readonly status: 500;
142
+ readonly title: "Codegen diagnostic";
143
+ };
144
+ /** Build-time-only — never crosses the RPC wire, so deliberately not `internal`. */
145
+ readonly SCHEMA_SNAPSHOT_PARSE: {
146
+ readonly status: 500;
147
+ readonly title: "Schema snapshot parse error";
148
+ };
149
+ /** Runtime-reachable (env.ts): message enumerates failing env key names — redact on the wire. */
150
+ readonly ENV_INVALID: {
151
+ readonly internal: true;
152
+ readonly status: 500;
153
+ readonly title: "Invalid environment";
154
+ };
155
+ /** Runtime-reachable (auth/middleware.ts): message carries auth-wiring guidance — redact on the wire. */
156
+ readonly AUTH_HEADERS_MISSING: {
157
+ readonly internal: true;
158
+ readonly status: 500;
159
+ readonly title: "Auth headers missing";
160
+ };
161
+ /**
162
+ * Upstream Cloudflare API failures surfaced from an action. The message
163
+ * carries the upstream response body (Cloudflare's own error text — trusted
164
+ * infra, not user input), so it is echoed rather than redacted. `status`
165
+ * here is a fallback; each throw passes the actual upstream HTTP status.
166
+ */
167
+ readonly ANALYTICS_SQL_ERROR: {
168
+ readonly status: 502;
169
+ readonly title: "Analytics Engine SQL API error";
170
+ };
171
+ readonly R2_SQL_ERROR: {
172
+ readonly status: 502;
173
+ readonly title: "R2 SQL API error";
174
+ };
175
+ readonly WORKFLOWS_REST_ERROR: {
176
+ readonly status: 502;
177
+ readonly title: "Cloudflare Workflows REST API error";
178
+ };
179
+ };
180
+ /** A well-known Lunora error code (a key of {@link ERROR_CATALOG}). */
181
+ type LunoraErrorCode = keyof typeof ERROR_CATALOG;
182
+ /**
183
+ * True when `code` is an internal/redacted code — an internal failure or
184
+ * unhandled invariant whose `message` must NOT cross the wire (it may carry SQL
185
+ * fragments, file paths, or internal identifiers). Derived from the catalog's
186
+ * `internal` flag so the redaction posture stays in one place (the table).
187
+ * Throwing a `LunoraError` with any non-internal code is the author's vouch that
188
+ * its message is client-safe; an unknown/unregistered code is treated as safe.
189
+ */
190
+ declare const isInternalCode: (code: string) => boolean;
191
+ /**
192
+ * A message-matched solution for errors that reach a consumer without a `code`
193
+ * — chiefly `@lunora/codegen` build errors, which are thrown as plain messages
194
+ * into generated code (and flattened to `{ message }` by the Vite overlay), so
195
+ * the message text is the only stable join key. Ordered most- to least-specific;
196
+ * the first matching rule wins.
197
+ */
198
+ interface Solution {
199
+ /** Markdown body shown under the header. */
200
+ body: string;
201
+ /** Short header for the solution. */
202
+ header: string;
203
+ /** Stable id (used in DEBUG logs and tests). */
204
+ id: string;
205
+ }
206
+ /** A {@link Solution} plus its message matcher. */
207
+ interface SolutionRule extends Solution {
208
+ /** True when this rule recognizes the error message. */
209
+ test: (message: string) => boolean;
210
+ }
211
+ /**
212
+ * Message-matched solutions (migrated verbatim from the former
213
+ * `@lunora/codegen` solutions table). Re-exported by `@lunora/codegen` as
214
+ * `LUNORA_SOLUTION_RULES` for backward compatibility.
215
+ */
216
+ declare const MESSAGE_SOLUTIONS: ReadonlyArray<SolutionRule>;
217
+ /**
218
+ * Flatten a Markdown hint to plain text for a terminal / non-Markdown surface:
219
+ * drop code-fence markers and strip inline `**bold**` / `` `code` `` emphasis.
220
+ * Shared by the CLI renderer and the Studio `ErrorAlert` so the two can't drift.
221
+ */
222
+ declare const flattenHint: (hint: ErrorHint) => string;
223
+ /**
224
+ * Find the first message-matched {@link Solution} for `message`, or `undefined`
225
+ * if none recognize it. Re-exported by `@lunora/codegen` as `findLunoraSolution`.
226
+ */
227
+ declare const findSolutionByMessage: (message: string) => Solution | undefined;
228
+ /**
229
+ * Resolve an actionable hint for an error: prefer a hint carried on the error
230
+ * (or its `code`'s catalog entry), then fall back to a message match. Returns
231
+ * `undefined` when nothing recognizes it.
232
+ */
233
+ declare const resolveHint: (input: {
234
+ code?: string;
235
+ hint?: ErrorHint;
236
+ message?: string;
237
+ } | string) => ErrorHint | undefined;
238
+ /** Source location for an error (mirrors `@visulima/error`'s `ErrorLocation`). */
239
+ interface ErrorLocation {
240
+ column?: number;
241
+ file?: string;
242
+ line?: number;
243
+ }
244
+ /** Options for {@link LunoraError}. Explicit values override the catalog defaults. */
245
+ interface LunoraErrorOptions {
246
+ /** Underlying error/value that triggered this one. */
247
+ cause?: unknown;
248
+ /** Structured, JSON+wire-encodable payload surfaced to the client alongside `code`. */
249
+ data?: unknown;
250
+ /** Link to deeper docs for this error. */
251
+ docsUrl?: string;
252
+ /** Actionable fix (Markdown). Defaults to the catalog entry's hint. */
253
+ hint?: ErrorHint;
254
+ /** Source location, when known (e.g. a codegen/schema diagnostic). */
255
+ location?: ErrorLocation;
256
+ /** Override the error `name` (e.g. a subclass like `"ConflictError"`). */
257
+ name?: string;
258
+ /** Override the transport status. Defaults to the catalog entry's status, else 500. */
259
+ status?: number;
260
+ /** Override the short title. Defaults to the catalog entry's title. */
261
+ title?: string;
262
+ }
263
+ /**
264
+ * A code string: a well-known {@link LunoraErrorCode} (with autocomplete) or any
265
+ * package-specific code not yet in the catalog.
266
+ */
267
+ type LunoraErrorCodeInput = LunoraErrorCode | (string & {});
268
+ declare class LunoraError extends Error {
269
+ /**
270
+ * Discriminator recognised by `@visulima/error`'s `renderError`/`isVisulimaError`
271
+ * (`error.type === "VisulimaError"`), so a `LunoraError` renders like a native
272
+ * `VisulimaError` — hint and all.
273
+ */
274
+ readonly type = "VisulimaError";
275
+ /** Actionable fix (Markdown), rendered by the CLI/overlay/Studio. */
276
+ readonly hint: ErrorHint | undefined;
277
+ /** Short, human-readable summary (separate from `message`). */
278
+ readonly title: string | undefined;
279
+ /** Source location, when known (mirrors `VisulimaError.loc`). */
280
+ readonly loc: ErrorLocation | undefined;
281
+ /** Machine-readable reason, keyed into {@link ERROR_CATALOG}. */
282
+ readonly code: string;
283
+ /** HTTP/RPC status for the transport mappers. */
284
+ readonly status: number;
285
+ /** Optional link to deeper docs. */
286
+ readonly docsUrl: string | undefined;
287
+ /** Optional structured payload propagated verbatim to the client. */
288
+ readonly data: unknown;
289
+ constructor(code: LunoraErrorCodeInput, message?: string, options?: LunoraErrorOptions);
290
+ }
291
+ /** The wire-relevant shape of a Lunora error (a real `LunoraError` or a wire-decoded twin). */
292
+ interface LunoraErrorLike extends Error {
293
+ code: string;
294
+ data?: unknown;
295
+ docsUrl?: string;
296
+ hint?: ErrorHint;
297
+ status: number;
298
+ }
299
+ /** True when `error` carries the Lunora transport shape (string `code` + numeric `status`). */
300
+ declare const isLunoraError: (error: unknown) => error is LunoraErrorLike;
301
+ /** Throw an `INTERNAL` {@link LunoraError} when `condition` is falsy. */
302
+ declare const invariant: (condition: unknown, message: string) => asserts condition;
303
+ /** Throw an `INTERNAL` {@link LunoraError} for an unreachable branch. */
304
+ declare const unreachable: (message: string) => never;
305
+ /** The client-facing error envelope body. */
306
+ interface ErrorBody {
307
+ code: string;
308
+ data?: unknown;
309
+ docsUrl?: string;
310
+ hint?: ErrorHint;
311
+ message: string;
312
+ }
313
+ interface ToErrorBodyOptions {
314
+ /**
315
+ * Wire-encode a `LunoraError`'s structured `data` for the client (so a
316
+ * `bigint`/`bytes` inside it survives). Omit to drop `data` from the body.
317
+ */
318
+ encodeData?: (data: unknown) => unknown;
319
+ /** Code for an unrecognized (non-`LunoraError`) throw. Default `"INTERNAL"`. */
320
+ fallbackCode?: string;
321
+ /** Message sent when the error is redacted (internal-coded or unrecognized). Default `"Internal error"`. */
322
+ redactedMessage?: string;
323
+ }
324
+ interface ToErrorBodyResult {
325
+ body: ErrorBody;
326
+ /** True when the message was redacted — the caller should log the raw error server-side. */
327
+ redacted: boolean;
328
+ /** HTTP/RPC status. */
329
+ status: number;
330
+ }
331
+ /**
332
+ * Turn a thrown value into an {@link ErrorBody} + status, applying the redaction
333
+ * invariant. A `LunoraError` with a non-internal code is echoed with its
334
+ * `message`, resolved `hint`, `docsUrl`, and (when `encodeData` is given) its
335
+ * `data`. An internal-coded `LunoraError` keeps its `code`/`status` but its
336
+ * message is replaced with `redactedMessage`. Anything else becomes a generic
337
+ * `fallbackCode`/500. When `redacted` is `true`, log the raw error server-side.
338
+ */
339
+ declare const toErrorBody: (error: unknown, options?: ToErrorBodyOptions) => ToErrorBodyResult;
340
+ export { ERROR_CATALOG, type ErrorBody, type ErrorCatalogEntry, type ErrorHint, type ErrorLocation, LunoraError, type LunoraErrorCode, type LunoraErrorCodeInput, type LunoraErrorLike, type LunoraErrorOptions, MESSAGE_SOLUTIONS, type Solution, type SolutionRule, type ToErrorBodyOptions, type ToErrorBodyResult, findSolutionByMessage, flattenHint, invariant, isInternalCode, isLunoraError, resolveHint, toErrorBody, unreachable };
@@ -0,0 +1,340 @@
1
+ /**
2
+ * The central Lunora error catalog — the single source of truth mapping a
3
+ * machine-readable `code` to its transport `status`, a short human `title`, and
4
+ * (where useful) an actionable Markdown `hint` plus a `docsUrl`.
5
+ *
6
+ * This table is consumed everywhere an error is surfaced: the runtime/DO wire
7
+ * mappers (status), the client SDK (code discrimination), the CLI renderer and
8
+ * the Vite overlay (hint), and the Studio UI (title + hint + docs link). It also
9
+ * absorbs the former `@lunora/codegen` "solutions" table (see {@link MESSAGE_SOLUTIONS})
10
+ * so codegen build-time errors — which are thrown as plain messages into
11
+ * generated code and lose their class identity before a consumer sees them —
12
+ * keep their message-matched hints.
13
+ */
14
+ /** Markdown hint: a single string or an array of lines. Shape matches `@visulima/error`'s `hint`. */
15
+ type ErrorHint = string | string[];
16
+ /** A catalog entry: the fixed metadata for one error `code`. */
17
+ interface ErrorCatalogEntry {
18
+ /** Optional URL to deeper docs for this error. */
19
+ docsUrl?: string;
20
+ /** Optional actionable fix, authored as Markdown (rendered by CLI/overlay/Studio). */
21
+ hint?: ErrorHint;
22
+ /**
23
+ * When `true`, this code's `message` must NOT cross the wire — an internal
24
+ * failure or unhandled invariant may carry SQL fragments, file paths, or
25
+ * internal identifiers. The transport mappers emit a generic message for
26
+ * these (and log the real one server-side). See {@link isInternalCode}.
27
+ */
28
+ internal?: boolean;
29
+ /** HTTP/RPC status this code maps to on the wire. */
30
+ status: number;
31
+ /** Short, human-readable summary. */
32
+ title: string;
33
+ }
34
+ /**
35
+ * Every well-known Lunora error code. Domain packages may throw additional
36
+ * codes (passing an explicit `status`); those are added here as their package is
37
+ * migrated. The keys of this object form the {@link LunoraErrorCode} union.
38
+ */
39
+ declare const ERROR_CATALOG: {
40
+ readonly BAD_REQUEST: {
41
+ readonly status: 400;
42
+ readonly title: "Bad request";
43
+ };
44
+ readonly UNAUTHORIZED: {
45
+ readonly status: 401;
46
+ readonly title: "Unauthorized";
47
+ };
48
+ readonly FORBIDDEN: {
49
+ readonly status: 403;
50
+ readonly title: "Forbidden";
51
+ };
52
+ readonly NOT_FOUND: {
53
+ readonly status: 404;
54
+ readonly title: "Not found";
55
+ };
56
+ readonly CONFLICT: {
57
+ readonly hint: readonly ["Another write changed this row while your mutation was running (optimistic concurrency conflict).", "", "Re-read the row and retry the mutation with the fresh value. Lunora serializes a DO's mutations, so a persistent conflict usually means the handler conflicts **with itself** (e.g. a trigger or cascade touching the same row) — split that work rather than adding a retry loop."];
58
+ readonly status: 409;
59
+ readonly title: "Conflict";
60
+ };
61
+ readonly NOT_UNIQUE: {
62
+ readonly hint: readonly ["`.unique()` matched more than one document — it expects the query to identify at most one row.", "", "- If several matches are legitimate, use `.first()` (take one) or `.collect()` (take all) instead.", "- Otherwise tighten the query (e.g. filter on a unique/indexed field) so it can only match one row."];
63
+ readonly status: 400;
64
+ readonly title: "Query matched more than one document";
65
+ };
66
+ readonly VALIDATION_ERROR: {
67
+ readonly status: 400;
68
+ readonly title: "Validation failed";
69
+ };
70
+ readonly TOO_MANY_REQUESTS: {
71
+ readonly status: 429;
72
+ readonly title: "Too many requests";
73
+ };
74
+ readonly UNPROCESSABLE: {
75
+ readonly status: 422;
76
+ readonly title: "Unprocessable";
77
+ };
78
+ readonly NOT_IMPLEMENTED: {
79
+ readonly status: 501;
80
+ readonly title: "Not implemented";
81
+ }; /** RPC/REST dispatch codes emitted by the runtime + Durable Object router. */
82
+ readonly FUNCTION_NOT_FOUND: {
83
+ readonly status: 404;
84
+ readonly title: "Function not found";
85
+ };
86
+ readonly METHOD_NOT_ALLOWED: {
87
+ readonly status: 405;
88
+ readonly title: "Method not allowed";
89
+ };
90
+ readonly PAYLOAD_TOO_LARGE: {
91
+ readonly status: 413;
92
+ readonly title: "Payload too large";
93
+ }; /** Free-form internal failure — redacted to a generic message on the wire. */
94
+ readonly INTERNAL: {
95
+ readonly internal: true;
96
+ readonly status: 500;
97
+ readonly title: "Internal error";
98
+ };
99
+ /** Alias of {@link ERROR_CATALOG.INTERNAL} kept for `@lunora/server`'s historical code name. */
100
+ readonly INTERNAL_SERVER_ERROR: {
101
+ readonly internal: true;
102
+ readonly status: 500;
103
+ readonly title: "Internal error";
104
+ };
105
+ /** Non-mappable throw crossed the RPC boundary. */
106
+ readonly RPC_FAILED: {
107
+ readonly internal: true;
108
+ readonly status: 500;
109
+ readonly title: "Internal error";
110
+ };
111
+ readonly COUNT_RLS_UNSUPPORTED: {
112
+ readonly status: 422;
113
+ readonly title: "count() is unsupported under an RLS policy";
114
+ };
115
+ readonly MASK_UNSUPPORTED: {
116
+ readonly status: 422;
117
+ readonly title: "Aggregation over a masked column is unsupported";
118
+ };
119
+ readonly RELATION_PREDICATE_UNSUPPORTED: {
120
+ readonly status: 422;
121
+ readonly title: "Relation predicate is unsupported in a write policy";
122
+ };
123
+ readonly RLS_REQUIRED: {
124
+ readonly hint: readonly ["This table is secure-by-default: it has no `.public()` marker and no RLS policy resolved for the caller, so the read fails closed.", "", "Add a read policy with `.rls(...)`, or mark the table `.public()` if it is intentionally world-readable."];
125
+ readonly status: 403;
126
+ readonly title: "RLS policy required";
127
+ };
128
+ readonly SHARD_ERROR: {
129
+ readonly status: 503;
130
+ readonly title: "Shard error";
131
+ };
132
+ readonly SHARD_UNAVAILABLE: {
133
+ readonly status: 503;
134
+ readonly title: "Shard unavailable";
135
+ };
136
+ readonly OFFLINE_IDENTITY_CHANGED: {
137
+ readonly status: 409;
138
+ readonly title: "Offline identity changed";
139
+ }; /** Package-specific codes. Build-time-only — never cross the RPC wire, so deliberately not `internal`. */
140
+ readonly CODEGEN_DIAGNOSTIC: {
141
+ readonly status: 500;
142
+ readonly title: "Codegen diagnostic";
143
+ };
144
+ /** Build-time-only — never crosses the RPC wire, so deliberately not `internal`. */
145
+ readonly SCHEMA_SNAPSHOT_PARSE: {
146
+ readonly status: 500;
147
+ readonly title: "Schema snapshot parse error";
148
+ };
149
+ /** Runtime-reachable (env.ts): message enumerates failing env key names — redact on the wire. */
150
+ readonly ENV_INVALID: {
151
+ readonly internal: true;
152
+ readonly status: 500;
153
+ readonly title: "Invalid environment";
154
+ };
155
+ /** Runtime-reachable (auth/middleware.ts): message carries auth-wiring guidance — redact on the wire. */
156
+ readonly AUTH_HEADERS_MISSING: {
157
+ readonly internal: true;
158
+ readonly status: 500;
159
+ readonly title: "Auth headers missing";
160
+ };
161
+ /**
162
+ * Upstream Cloudflare API failures surfaced from an action. The message
163
+ * carries the upstream response body (Cloudflare's own error text — trusted
164
+ * infra, not user input), so it is echoed rather than redacted. `status`
165
+ * here is a fallback; each throw passes the actual upstream HTTP status.
166
+ */
167
+ readonly ANALYTICS_SQL_ERROR: {
168
+ readonly status: 502;
169
+ readonly title: "Analytics Engine SQL API error";
170
+ };
171
+ readonly R2_SQL_ERROR: {
172
+ readonly status: 502;
173
+ readonly title: "R2 SQL API error";
174
+ };
175
+ readonly WORKFLOWS_REST_ERROR: {
176
+ readonly status: 502;
177
+ readonly title: "Cloudflare Workflows REST API error";
178
+ };
179
+ };
180
+ /** A well-known Lunora error code (a key of {@link ERROR_CATALOG}). */
181
+ type LunoraErrorCode = keyof typeof ERROR_CATALOG;
182
+ /**
183
+ * True when `code` is an internal/redacted code — an internal failure or
184
+ * unhandled invariant whose `message` must NOT cross the wire (it may carry SQL
185
+ * fragments, file paths, or internal identifiers). Derived from the catalog's
186
+ * `internal` flag so the redaction posture stays in one place (the table).
187
+ * Throwing a `LunoraError` with any non-internal code is the author's vouch that
188
+ * its message is client-safe; an unknown/unregistered code is treated as safe.
189
+ */
190
+ declare const isInternalCode: (code: string) => boolean;
191
+ /**
192
+ * A message-matched solution for errors that reach a consumer without a `code`
193
+ * — chiefly `@lunora/codegen` build errors, which are thrown as plain messages
194
+ * into generated code (and flattened to `{ message }` by the Vite overlay), so
195
+ * the message text is the only stable join key. Ordered most- to least-specific;
196
+ * the first matching rule wins.
197
+ */
198
+ interface Solution {
199
+ /** Markdown body shown under the header. */
200
+ body: string;
201
+ /** Short header for the solution. */
202
+ header: string;
203
+ /** Stable id (used in DEBUG logs and tests). */
204
+ id: string;
205
+ }
206
+ /** A {@link Solution} plus its message matcher. */
207
+ interface SolutionRule extends Solution {
208
+ /** True when this rule recognizes the error message. */
209
+ test: (message: string) => boolean;
210
+ }
211
+ /**
212
+ * Message-matched solutions (migrated verbatim from the former
213
+ * `@lunora/codegen` solutions table). Re-exported by `@lunora/codegen` as
214
+ * `LUNORA_SOLUTION_RULES` for backward compatibility.
215
+ */
216
+ declare const MESSAGE_SOLUTIONS: ReadonlyArray<SolutionRule>;
217
+ /**
218
+ * Flatten a Markdown hint to plain text for a terminal / non-Markdown surface:
219
+ * drop code-fence markers and strip inline `**bold**` / `` `code` `` emphasis.
220
+ * Shared by the CLI renderer and the Studio `ErrorAlert` so the two can't drift.
221
+ */
222
+ declare const flattenHint: (hint: ErrorHint) => string;
223
+ /**
224
+ * Find the first message-matched {@link Solution} for `message`, or `undefined`
225
+ * if none recognize it. Re-exported by `@lunora/codegen` as `findLunoraSolution`.
226
+ */
227
+ declare const findSolutionByMessage: (message: string) => Solution | undefined;
228
+ /**
229
+ * Resolve an actionable hint for an error: prefer a hint carried on the error
230
+ * (or its `code`'s catalog entry), then fall back to a message match. Returns
231
+ * `undefined` when nothing recognizes it.
232
+ */
233
+ declare const resolveHint: (input: {
234
+ code?: string;
235
+ hint?: ErrorHint;
236
+ message?: string;
237
+ } | string) => ErrorHint | undefined;
238
+ /** Source location for an error (mirrors `@visulima/error`'s `ErrorLocation`). */
239
+ interface ErrorLocation {
240
+ column?: number;
241
+ file?: string;
242
+ line?: number;
243
+ }
244
+ /** Options for {@link LunoraError}. Explicit values override the catalog defaults. */
245
+ interface LunoraErrorOptions {
246
+ /** Underlying error/value that triggered this one. */
247
+ cause?: unknown;
248
+ /** Structured, JSON+wire-encodable payload surfaced to the client alongside `code`. */
249
+ data?: unknown;
250
+ /** Link to deeper docs for this error. */
251
+ docsUrl?: string;
252
+ /** Actionable fix (Markdown). Defaults to the catalog entry's hint. */
253
+ hint?: ErrorHint;
254
+ /** Source location, when known (e.g. a codegen/schema diagnostic). */
255
+ location?: ErrorLocation;
256
+ /** Override the error `name` (e.g. a subclass like `"ConflictError"`). */
257
+ name?: string;
258
+ /** Override the transport status. Defaults to the catalog entry's status, else 500. */
259
+ status?: number;
260
+ /** Override the short title. Defaults to the catalog entry's title. */
261
+ title?: string;
262
+ }
263
+ /**
264
+ * A code string: a well-known {@link LunoraErrorCode} (with autocomplete) or any
265
+ * package-specific code not yet in the catalog.
266
+ */
267
+ type LunoraErrorCodeInput = LunoraErrorCode | (string & {});
268
+ declare class LunoraError extends Error {
269
+ /**
270
+ * Discriminator recognised by `@visulima/error`'s `renderError`/`isVisulimaError`
271
+ * (`error.type === "VisulimaError"`), so a `LunoraError` renders like a native
272
+ * `VisulimaError` — hint and all.
273
+ */
274
+ readonly type = "VisulimaError";
275
+ /** Actionable fix (Markdown), rendered by the CLI/overlay/Studio. */
276
+ readonly hint: ErrorHint | undefined;
277
+ /** Short, human-readable summary (separate from `message`). */
278
+ readonly title: string | undefined;
279
+ /** Source location, when known (mirrors `VisulimaError.loc`). */
280
+ readonly loc: ErrorLocation | undefined;
281
+ /** Machine-readable reason, keyed into {@link ERROR_CATALOG}. */
282
+ readonly code: string;
283
+ /** HTTP/RPC status for the transport mappers. */
284
+ readonly status: number;
285
+ /** Optional link to deeper docs. */
286
+ readonly docsUrl: string | undefined;
287
+ /** Optional structured payload propagated verbatim to the client. */
288
+ readonly data: unknown;
289
+ constructor(code: LunoraErrorCodeInput, message?: string, options?: LunoraErrorOptions);
290
+ }
291
+ /** The wire-relevant shape of a Lunora error (a real `LunoraError` or a wire-decoded twin). */
292
+ interface LunoraErrorLike extends Error {
293
+ code: string;
294
+ data?: unknown;
295
+ docsUrl?: string;
296
+ hint?: ErrorHint;
297
+ status: number;
298
+ }
299
+ /** True when `error` carries the Lunora transport shape (string `code` + numeric `status`). */
300
+ declare const isLunoraError: (error: unknown) => error is LunoraErrorLike;
301
+ /** Throw an `INTERNAL` {@link LunoraError} when `condition` is falsy. */
302
+ declare const invariant: (condition: unknown, message: string) => asserts condition;
303
+ /** Throw an `INTERNAL` {@link LunoraError} for an unreachable branch. */
304
+ declare const unreachable: (message: string) => never;
305
+ /** The client-facing error envelope body. */
306
+ interface ErrorBody {
307
+ code: string;
308
+ data?: unknown;
309
+ docsUrl?: string;
310
+ hint?: ErrorHint;
311
+ message: string;
312
+ }
313
+ interface ToErrorBodyOptions {
314
+ /**
315
+ * Wire-encode a `LunoraError`'s structured `data` for the client (so a
316
+ * `bigint`/`bytes` inside it survives). Omit to drop `data` from the body.
317
+ */
318
+ encodeData?: (data: unknown) => unknown;
319
+ /** Code for an unrecognized (non-`LunoraError`) throw. Default `"INTERNAL"`. */
320
+ fallbackCode?: string;
321
+ /** Message sent when the error is redacted (internal-coded or unrecognized). Default `"Internal error"`. */
322
+ redactedMessage?: string;
323
+ }
324
+ interface ToErrorBodyResult {
325
+ body: ErrorBody;
326
+ /** True when the message was redacted — the caller should log the raw error server-side. */
327
+ redacted: boolean;
328
+ /** HTTP/RPC status. */
329
+ status: number;
330
+ }
331
+ /**
332
+ * Turn a thrown value into an {@link ErrorBody} + status, applying the redaction
333
+ * invariant. A `LunoraError` with a non-internal code is echoed with its
334
+ * `message`, resolved `hint`, `docsUrl`, and (when `encodeData` is given) its
335
+ * `data`. An internal-coded `LunoraError` keeps its `code`/`status` but its
336
+ * message is replaced with `redactedMessage`. Anything else becomes a generic
337
+ * `fallbackCode`/500. When `redacted` is `true`, log the raw error server-side.
338
+ */
339
+ declare const toErrorBody: (error: unknown, options?: ToErrorBodyOptions) => ToErrorBodyResult;
340
+ export { ERROR_CATALOG, type ErrorBody, type ErrorCatalogEntry, type ErrorHint, type ErrorLocation, LunoraError, type LunoraErrorCode, type LunoraErrorCodeInput, type LunoraErrorLike, type LunoraErrorOptions, MESSAGE_SOLUTIONS, type Solution, type SolutionRule, type ToErrorBodyOptions, type ToErrorBodyResult, findSolutionByMessage, flattenHint, invariant, isInternalCode, isLunoraError, resolveHint, toErrorBody, unreachable };
package/dist/index.mjs ADDED
@@ -0,0 +1,5 @@
1
+ export { LunoraError } from './packem_shared/LunoraError-Dg03M4uC.mjs';
2
+ export { ERROR_CATALOG, MESSAGE_SOLUTIONS, findSolutionByMessage, flattenHint, isInternalCode, resolveHint } from './packem_shared/ERROR_CATALOG-DAg3Unhb.mjs';
3
+ export { isLunoraError } from './packem_shared/isLunoraError-BvsoKcWE.mjs';
4
+ export { invariant, unreachable } from './packem_shared/invariant-BsrkuTaN.mjs';
5
+ export { toErrorBody } from './packem_shared/toErrorBody-BbR2r6pO.mjs';