@lunora/errors 1.0.0-alpha.5 → 1.0.0-alpha.50
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/README.md +2 -1
- package/dist/index.d.mts +1028 -84
- package/dist/index.d.ts +1028 -84
- package/dist/index.mjs +1 -5
- package/dist/packem_shared/CLOUDFLARE_PLATFORM_ERRORS-DDY3BZ7p.mjs +15 -0
- package/dist/packem_shared/LunoraError-r0XZHGwS.mjs +1 -0
- package/dist/packem_shared/invariant-CUGqcOQE.mjs +1 -0
- package/dist/packem_shared/isLunoraError-kpNg-Mxd.mjs +1 -0
- package/dist/packem_shared/toErrorBody-DMpIG2Fq.mjs +1 -0
- package/package.json +1 -1
- package/dist/packem_shared/ERROR_CATALOG-pJvm6SQP.mjs +0 -225
- package/dist/packem_shared/LunoraError-Cdb2-Hda.mjs +0 -38
- package/dist/packem_shared/invariant-HhlkSkbP.mjs +0 -12
- package/dist/packem_shared/isLunoraError-CSQtYMrF.mjs +0 -9
- package/dist/packem_shared/toErrorBody-BXvKCSrv.mjs +0 -26
package/dist/index.d.ts
CHANGED
|
@@ -1,18 +1,5 @@
|
|
|
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
1
|
/** Markdown hint: a single string or an array of lines. Shape matches `@visulima/error`'s `hint`. */
|
|
15
|
-
type ErrorHint = string | string
|
|
2
|
+
type ErrorHint = string | ReadonlyArray<string>;
|
|
16
3
|
/** A catalog entry: the fixed metadata for one error `code`. */
|
|
17
4
|
interface ErrorCatalogEntry {
|
|
18
5
|
/** Optional URL to deeper docs for this error. */
|
|
@@ -20,11 +7,11 @@ interface ErrorCatalogEntry {
|
|
|
20
7
|
/** Optional actionable fix, authored as Markdown (rendered by CLI/overlay/Studio). */
|
|
21
8
|
hint?: ErrorHint;
|
|
22
9
|
/**
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
10
|
+
* When `true`, this code's `message` must NOT cross the wire — an internal
|
|
11
|
+
* failure or unhandled invariant may carry SQL fragments, file paths, or
|
|
12
|
+
* internal identifiers. The transport mappers emit a generic message for
|
|
13
|
+
* these (and log the real one server-side). See {@link isInternalCode}.
|
|
14
|
+
*/
|
|
28
15
|
internal?: boolean;
|
|
29
16
|
/** HTTP/RPC status this code maps to on the wire. */
|
|
30
17
|
status: number;
|
|
@@ -32,10 +19,10 @@ interface ErrorCatalogEntry {
|
|
|
32
19
|
title: string;
|
|
33
20
|
}
|
|
34
21
|
/**
|
|
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
|
-
*/
|
|
22
|
+
* Every well-known Lunora error code. Domain packages may throw additional
|
|
23
|
+
* codes (passing an explicit `status`); those are added here as their package is
|
|
24
|
+
* migrated. The keys of this object form the {@link LunoraErrorCode} union.
|
|
25
|
+
*/
|
|
39
26
|
declare const ERROR_CATALOG: {
|
|
40
27
|
readonly BAD_REQUEST: {
|
|
41
28
|
readonly status: 400;
|
|
@@ -78,7 +65,8 @@ declare const ERROR_CATALOG: {
|
|
|
78
65
|
readonly NOT_IMPLEMENTED: {
|
|
79
66
|
readonly status: 501;
|
|
80
67
|
readonly title: "Not implemented";
|
|
81
|
-
};
|
|
68
|
+
};
|
|
69
|
+
/** RPC/REST dispatch codes emitted by the runtime + Durable Object router. */
|
|
82
70
|
readonly FUNCTION_NOT_FOUND: {
|
|
83
71
|
readonly status: 404;
|
|
84
72
|
readonly title: "Function not found";
|
|
@@ -90,7 +78,8 @@ declare const ERROR_CATALOG: {
|
|
|
90
78
|
readonly PAYLOAD_TOO_LARGE: {
|
|
91
79
|
readonly status: 413;
|
|
92
80
|
readonly title: "Payload too large";
|
|
93
|
-
};
|
|
81
|
+
};
|
|
82
|
+
/** Free-form internal failure — redacted to a generic message on the wire. */
|
|
94
83
|
readonly INTERNAL: {
|
|
95
84
|
readonly internal: true;
|
|
96
85
|
readonly status: 500;
|
|
@@ -130,10 +119,34 @@ declare const ERROR_CATALOG: {
|
|
|
130
119
|
readonly status: 500;
|
|
131
120
|
readonly title: "Run depth exceeded";
|
|
132
121
|
};
|
|
122
|
+
/**
|
|
123
|
+
* A `query` context reached for `ctx.runMutation` / `ctx.runAction`. The
|
|
124
|
+
* generated context object installs all three `run*` methods on every kind, so
|
|
125
|
+
* the TYPE is the only thing that stops a read-only handler from writing — and
|
|
126
|
+
* a cast walks straight past it, inside a subscription re-run that may execute
|
|
127
|
+
* many times per write. Deliberately NOT `internal`: the message names the
|
|
128
|
+
* function that was reached for, and it is a programming error the developer
|
|
129
|
+
* needs to read.
|
|
130
|
+
*/
|
|
131
|
+
readonly RUN_KIND_FORBIDDEN: {
|
|
132
|
+
readonly status: 500;
|
|
133
|
+
readonly title: "Function kind may not be composed from a query";
|
|
134
|
+
};
|
|
135
|
+
readonly TRANSACTION_LIMIT_EXCEEDED: {
|
|
136
|
+
readonly hint: readonly ["A single mutation may only read and write a bounded amount before it is stopped.", "", "Narrow the read with an index (`.withIndex(...)`) instead of scanning the table, or split the write across several mutations — for a large backfill use `defineMigration` + `lunora migrate up`, which batches and checkpoints for you.", "", "The ceilings are deliberately conservative — they exist to stop one request taking down the whole shard. A deployment that genuinely needs bigger transactions can raise them by overriding the `transactionLimits()` seam on its generated shard class."];
|
|
137
|
+
readonly status: 413;
|
|
138
|
+
readonly title: "Transaction limit exceeded";
|
|
139
|
+
};
|
|
133
140
|
readonly MIGRATION_NOT_FOUND: {
|
|
134
141
|
readonly status: 404;
|
|
135
142
|
readonly title: "Data migration not found";
|
|
136
143
|
};
|
|
144
|
+
/** `@lunora/d1`'s `MigrationRunner` refused to run: an applied migration's SQL changed, or the tracking table cannot say which migrations already ran. */
|
|
145
|
+
readonly MIGRATION_DRIFT: {
|
|
146
|
+
readonly hint: readonly ["An applied SQL migration is never re-run, so the runner stopped before applying anything.", "", "- **Its text changed after it ran:** revert the edit and put the change in a new migration. If the edit is inert (a comment, whitespace, line endings), record the new hash with the `UPDATE` the message prints.", "- **Tracking rows predate versions and match no migration:** set each row's `version` with the `UPDATE` the message prints, so the runner can tell what already ran."];
|
|
147
|
+
readonly status: 409;
|
|
148
|
+
readonly title: "Migration drift";
|
|
149
|
+
};
|
|
137
150
|
readonly UNKNOWN_TABLE: {
|
|
138
151
|
readonly status: 404;
|
|
139
152
|
readonly title: "Unknown table";
|
|
@@ -150,14 +163,47 @@ declare const ERROR_CATALOG: {
|
|
|
150
163
|
readonly status: 503;
|
|
151
164
|
readonly title: "Shard unavailable";
|
|
152
165
|
};
|
|
166
|
+
/** A fan-out shard call exceeded the coordinator's per-shard deadline. */
|
|
167
|
+
readonly SHARD_TIMEOUT: {
|
|
168
|
+
readonly status: 504;
|
|
169
|
+
readonly title: "Shard timeout";
|
|
170
|
+
};
|
|
171
|
+
/** A fan-out shard call answered with a non-2xx status; the status is in the message, the body is not. */
|
|
172
|
+
readonly SHARD_HTTP_ERROR: {
|
|
173
|
+
readonly status: 502;
|
|
174
|
+
readonly title: "Shard HTTP error";
|
|
175
|
+
};
|
|
176
|
+
/** The shard could not write a subscription's attachment to storage, so the subscription was refused. */
|
|
177
|
+
readonly SUBSCRIPTION_PERSIST_FAILED: {
|
|
178
|
+
readonly status: 500;
|
|
179
|
+
readonly title: "Subscription persist failed";
|
|
180
|
+
};
|
|
181
|
+
/** A connection asked for more concurrent subscriptions than the shard allows. */
|
|
182
|
+
readonly TOO_MANY_SUBSCRIPTIONS: {
|
|
183
|
+
readonly status: 429;
|
|
184
|
+
readonly title: "Too many subscriptions";
|
|
185
|
+
};
|
|
153
186
|
readonly OFFLINE_IDENTITY_CHANGED: {
|
|
154
187
|
readonly status: 409;
|
|
155
188
|
readonly title: "Offline identity changed";
|
|
156
|
-
};
|
|
189
|
+
};
|
|
190
|
+
/** A replayed write named, in `x-lunora-expect-subject`, a different user than the request resolved to. */
|
|
191
|
+
readonly IDENTITY_MISMATCH: {
|
|
192
|
+
readonly hint: readonly ["A write queued offline was replayed on a session that now belongs to someone else (a sign-out, or another user's sign-in, while it waited), so the worker refused it rather than commit it as them.", "", "`@lunora/client` re-resolves the session on this refusal and settles the write itself. If every replay is refused while the same user stays signed in, your worker's `resolveIdentity` returns a different `userId` than better-auth's `user.id`, or the RPC request carries no session cookie."];
|
|
193
|
+
readonly status: 409;
|
|
194
|
+
readonly title: "Session changed since the write was queued";
|
|
195
|
+
};
|
|
196
|
+
/** Package-specific codes. Build-time-only — never cross the RPC wire, so deliberately not `internal`. */
|
|
157
197
|
readonly CODEGEN_DIAGNOSTIC: {
|
|
158
198
|
readonly status: 500;
|
|
159
199
|
readonly title: "Codegen diagnostic";
|
|
160
200
|
};
|
|
201
|
+
/** Build-time-only (codegen): `.jurisdiction()` would move DO-backed auth to a new, empty object without `{ pinAuth: true }`. */
|
|
202
|
+
readonly JURISDICTION_MOVE: {
|
|
203
|
+
readonly hint: readonly ["The schema declares `.jurisdiction(...)` and the project has DO-backed auth (`.auth({ namespace })`). Pinning the auth object to the jurisdiction resolves it to a new, empty one: every user, account and session stays in the unpinned object until copied across. Voice sessions are pinned without an acknowledgement: they store nothing, and their transcripts are rows in the already-pinned shards.", "", "Acknowledge with `.jurisdiction(\"eu\", { pinAuth: true })` and deploy, then copy the auth tables across with the `__lunora_admin__:copyAuthToJurisdiction` admin op and, once the counts check out, purge the unpinned copy with `__lunora_admin__:purgeUnpinnedAuth` (see [Pinning auth and voice](/docs/concepts/data-residency#pinning-auth-and-voice)). For D1-mode auth the acknowledgement changes nothing."];
|
|
204
|
+
readonly status: 422;
|
|
205
|
+
readonly title: "Unacknowledged jurisdiction move";
|
|
206
|
+
};
|
|
161
207
|
/** Build-time-only — never crosses the RPC wire, so deliberately not `internal`. */
|
|
162
208
|
readonly SCHEMA_SNAPSHOT_PARSE: {
|
|
163
209
|
readonly status: 500;
|
|
@@ -176,14 +222,38 @@ declare const ERROR_CATALOG: {
|
|
|
176
222
|
readonly title: "Auth headers missing";
|
|
177
223
|
};
|
|
178
224
|
/**
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
225
|
+
* Signup rejected by `@lunora/auth`'s email-domain gate — a disposable/throwaway
|
|
226
|
+
* provider (or a caller deny-list hit). Client-safe: the message names only the
|
|
227
|
+
* offending domain class, never a secret, so it is echoed rather than redacted.
|
|
228
|
+
*/
|
|
229
|
+
readonly EMAIL_DOMAIN_BLOCKED: {
|
|
230
|
+
readonly hint: readonly ["This address's domain is on the disposable/throwaway blocklist (or your configured deny-list).", "", "Sign up with a permanent mailbox. To tune the policy, pass `blockDisposable` / `allowDomains` / `denyDomains` in the `EmailGateConfig` you hand `assertEmailAllowed` / `classifyEmail` / `emailGateMiddleware` (`@lunora/auth/email-guard`)."];
|
|
231
|
+
readonly status: 400;
|
|
232
|
+
readonly title: "Email domain not allowed";
|
|
233
|
+
};
|
|
234
|
+
/**
|
|
235
|
+
* Opt-in MX verification (`@lunora/auth/email-guard`, `mx: true`) found no mail
|
|
236
|
+
* exchanger for the address's domain, so mail to it would never deliver.
|
|
237
|
+
* Client-safe: names only the domain, no secret.
|
|
238
|
+
*/
|
|
239
|
+
readonly EMAIL_UNDELIVERABLE: {
|
|
240
|
+
readonly hint: readonly ["The address's domain publishes no MX (or fallback A/AAAA) records, so it can't receive mail.", "", "Check for a typo in the domain. MX verification is opt-in (`mx: true`) and needs DNS — leave it off on the edge path if DNS is unavailable."];
|
|
241
|
+
readonly status: 400;
|
|
242
|
+
readonly title: "Email domain cannot receive mail";
|
|
243
|
+
};
|
|
244
|
+
/**
|
|
245
|
+
* Upstream Cloudflare API failures surfaced from an action. The message
|
|
246
|
+
* carries the upstream response body (Cloudflare's own error text — trusted
|
|
247
|
+
* infra, not user input), so it is echoed rather than redacted. `status`
|
|
248
|
+
* here is a fallback; each throw passes the actual upstream HTTP status.
|
|
249
|
+
*/
|
|
250
|
+
readonly ANALYTICS_SQL_QUERY_ERROR: {
|
|
185
251
|
readonly status: 502;
|
|
186
|
-
readonly title: "Analytics
|
|
252
|
+
readonly title: "Analytics SQL query error";
|
|
253
|
+
};
|
|
254
|
+
readonly BROWSER_RUN_ERROR: {
|
|
255
|
+
readonly status: 502;
|
|
256
|
+
readonly title: "Browser Run API error";
|
|
187
257
|
};
|
|
188
258
|
readonly R2_SQL_ERROR: {
|
|
189
259
|
readonly status: 502;
|
|
@@ -193,25 +263,783 @@ declare const ERROR_CATALOG: {
|
|
|
193
263
|
readonly status: 502;
|
|
194
264
|
readonly title: "Cloudflare Workflows REST API error";
|
|
195
265
|
};
|
|
266
|
+
/** Server-side payment wiring is wrong or missing (`webhook secret not configured`, a duplicate/absent adapter). A 500 that names internal configuration — redact on the wire. */
|
|
267
|
+
readonly CONFIG_INVALID: {
|
|
268
|
+
readonly internal: true;
|
|
269
|
+
readonly status: 500;
|
|
270
|
+
readonly title: "Payment configuration invalid";
|
|
271
|
+
};
|
|
272
|
+
/** Arithmetic across two currencies. The message names only the two currency codes the caller supplied. */
|
|
273
|
+
readonly CURRENCY_MISMATCH: {
|
|
274
|
+
readonly status: 400;
|
|
275
|
+
readonly title: "Currency mismatch";
|
|
276
|
+
};
|
|
277
|
+
/**
|
|
278
|
+
* A payment provider refused or answered unusably. NOT `internal`, matching
|
|
279
|
+
* the upstream-API 502s above: the messages are fixed capability text
|
|
280
|
+
* ("<provider> does not support refundPayment") or echo back the malformed
|
|
281
|
+
* id the CALLER passed in, so none of them reveals state the caller did not
|
|
282
|
+
* already have.
|
|
283
|
+
*/
|
|
284
|
+
readonly PROVIDER_ERROR: {
|
|
285
|
+
readonly status: 502;
|
|
286
|
+
readonly title: "Payment provider error";
|
|
287
|
+
};
|
|
288
|
+
/**
|
|
289
|
+
* Webhook rejections, addressed to the provider posting the hook, so NOT
|
|
290
|
+
* `internal`. Five of the six mint sites are fixed strings ("no matching
|
|
291
|
+
* signature", "missing creem-signature header"). The sixth,
|
|
292
|
+
* `providers/stripe.ts`, passes the Stripe SDK's own verification message
|
|
293
|
+
* through — "No signatures found matching…", "Timestamp outside the
|
|
294
|
+
* tolerance zone" — which still describes the REQUEST the poster sent, not
|
|
295
|
+
* this server's state. Keep that true of anything added here: an upstream
|
|
296
|
+
* message is only safe to forward while it is about the caller's input.
|
|
297
|
+
*/
|
|
298
|
+
readonly WEBHOOK_EVENT_ID_MISSING: {
|
|
299
|
+
readonly status: 400;
|
|
300
|
+
readonly title: "Webhook event id missing";
|
|
301
|
+
};
|
|
302
|
+
readonly WEBHOOK_SIGNATURE_INVALID: {
|
|
303
|
+
readonly status: 400;
|
|
304
|
+
readonly title: "Webhook signature invalid";
|
|
305
|
+
};
|
|
306
|
+
readonly WEBHOOK_TIMESTAMP_INVALID: {
|
|
307
|
+
readonly status: 400;
|
|
308
|
+
readonly title: "Webhook timestamp outside tolerance";
|
|
309
|
+
};
|
|
310
|
+
/**
|
|
311
|
+
* Admin-gated `/_lunora/admin/*` and `__lunora_admin__:*` codes. Registered
|
|
312
|
+
* here (plan 230, ERRORS-01) after an audit found them minted with `code:`
|
|
313
|
+
* but never added to the catalog — `isInternalCode` fails OPEN for an
|
|
314
|
+
* unregistered code, so each was already echoing its message unredacted.
|
|
315
|
+
* Below is that audit's verdict per code, not a blanket allow: most of these
|
|
316
|
+
* are deliberately actionable ("you forgot to configure X") and stay
|
|
317
|
+
* client-safe; the ones that can carry backend detail are flagged
|
|
318
|
+
* `internal: true` individually, with the reason noted alongside.
|
|
319
|
+
*/
|
|
320
|
+
readonly ADMIN_FORBIDDEN: {
|
|
321
|
+
readonly status: 403;
|
|
322
|
+
readonly title: "Admin access forbidden";
|
|
323
|
+
};
|
|
324
|
+
readonly ADMIN_TOKEN_NOT_CONFIGURED: {
|
|
325
|
+
readonly status: 400;
|
|
326
|
+
readonly title: "Admin token not configured";
|
|
327
|
+
};
|
|
328
|
+
/**
|
|
329
|
+
* Deliberately NOT `internal`, unlike its 500 neighbours: the message is
|
|
330
|
+
* fixed configuration guidance that names no table, path or identifier, and
|
|
331
|
+
* redacting it to "Internal error" would strip the only thing this code
|
|
332
|
+
* exists to deliver. It is raised before any query runs, so there is no
|
|
333
|
+
* backend detail to leak.
|
|
334
|
+
*/
|
|
335
|
+
readonly AUTH_MIGRATOR_UNSUPPORTED: {
|
|
336
|
+
readonly hint: readonly ["better-auth migrates only through its Kysely adapter, so `ensureMigrated` / `compileMigrationsSql` need the raw D1 binding as `database` — a custom adapter (`lunoraD1Adapter`, `lunoraAuthAdapter`, `lunoraDoAdapter`) cannot be migrated through, and neither can an absent `database`.", "", "Build a SECOND, migration-only instance over the raw binding — `createAuth({ ...options, database: env.DB })` — and hand that one to `ensureMigrated`. Keep the adapter on the instance that serves requests: the adapter exists to dodge a dev-runner hang in `$context`, which the migration instance never resolves.", "", "To compile the SQL off-platform (`compileMigrationsSql`), diff against an empty local database — `new DatabaseSync(':memory:')` from `node:sqlite` — rather than passing no `database` at all."];
|
|
337
|
+
readonly status: 500;
|
|
338
|
+
readonly title: "Auth migrator cannot drive the configured database";
|
|
339
|
+
};
|
|
340
|
+
readonly AUTH_NOT_CONFIGURED: {
|
|
341
|
+
readonly status: 400;
|
|
342
|
+
readonly title: "Auth admin not configured";
|
|
343
|
+
};
|
|
344
|
+
readonly AUTH_OP_NOT_SUPPORTED: {
|
|
345
|
+
readonly status: 400;
|
|
346
|
+
readonly title: "Auth admin operation not supported";
|
|
347
|
+
};
|
|
348
|
+
/**
|
|
349
|
+
* MCP OAuth wiring mistakes, raised while the auth instance or MCP route is
|
|
350
|
+
* built (plan 461). Not `internal`, for the same reason as the auth migrator
|
|
351
|
+
* entry above: the message is fixed configuration guidance (at most echoing
|
|
352
|
+
* the app's own configured `resource` value), raised before any request is
|
|
353
|
+
* served, so it carries no backend detail and redacting it would hide the fix.
|
|
354
|
+
*/
|
|
355
|
+
readonly AUTH_CIMD_FETCH_NOT_PUBLIC: {
|
|
356
|
+
readonly hint: "Add \"global_fetch_strictly_public\" to \"compatibility_flags\" in wrangler.jsonc, so CIMD metadata fetches cannot reach private addresses. `lunora doctor` reports it as `cimd-fetch-not-strictly-public`.";
|
|
357
|
+
readonly status: 500;
|
|
358
|
+
readonly title: "CIMD transport needs the global_fetch_strictly_public flag";
|
|
359
|
+
};
|
|
360
|
+
readonly AUTH_MCP_RESOURCE_AMBIGUOUS: {
|
|
361
|
+
readonly hint: "Use `mcp` from \"@lunora/auth/plugins\" (not `@better-auth/mcp`) and pass the plugin object as is: it records its `resource`, which the worker needs to serve the protected-resource metadata when the provider names several resources.";
|
|
362
|
+
readonly status: 500;
|
|
363
|
+
readonly title: "MCP resource cannot be told apart from the provider's other resources";
|
|
364
|
+
};
|
|
365
|
+
readonly AUTH_MCP_RESOURCE_INVALID: {
|
|
366
|
+
readonly hint: "Pass `resource` to `requireMcpAuth` (and `mcp()`) as the absolute URL of the MCP route, e.g. `https://app.example.com/mcp`: issued tokens are audience-bound to it.";
|
|
367
|
+
readonly status: 500;
|
|
368
|
+
readonly title: "MCP resource missing or not an absolute URL";
|
|
369
|
+
};
|
|
370
|
+
readonly MCP_STEP_UP_MISCONFIGURED: {
|
|
371
|
+
readonly hint: "Give `createAuthedMcpFetchHandler`'s `stepUp` a non-empty `scope` (such as `lunora:write`) and a `challenge` (`createInsufficientScopeError` from `@lunora/auth/plugins`), or leave `stepUp` out.";
|
|
372
|
+
readonly status: 500;
|
|
373
|
+
readonly title: "MCP step-up misconfigured";
|
|
374
|
+
};
|
|
375
|
+
readonly BACKUP_NOT_CONFIGURED: {
|
|
376
|
+
readonly status: 500;
|
|
377
|
+
readonly title: "Scheduled backup not configured";
|
|
378
|
+
};
|
|
379
|
+
readonly BACKUP_RETENTION_NOT_CONFIGURED: {
|
|
380
|
+
readonly hint: readonly ["`lunora backup prune` removes snapshots past the retention window, and this worker has no window: set `backupRetain` (how many to keep) and `backupCron` (which decides whose snapshots retention owns) on `createWorker`.", "", "Nothing was deleted. A default is deliberately not invented here — retention deleting on its own is exactly what this command exists to replace."];
|
|
381
|
+
readonly status: 400;
|
|
382
|
+
readonly title: "Backup retention window not configured";
|
|
383
|
+
};
|
|
384
|
+
readonly BACKUP_TOO_LARGE: {
|
|
385
|
+
readonly hint: readonly ["The scheduled backup is assembled inside the Worker isolate, so it caps the snapshot it will build. Nothing was written.", "", "The cap is on the NDJSON, not on peak memory: the export fan-out resolves every shard's rows before the first row is encoded, so a snapshot under the cap can still exhaust the isolate. It is set well below the isolate's limit for that reason.", "", "Narrow the snapshot with `backupTables`, or take this backup off-platform with `lunora backup create --bucket`, which runs on a machine rather than in an isolate. Backing up more often does not help — every run is a full snapshot."];
|
|
386
|
+
readonly status: 507;
|
|
387
|
+
readonly title: "Backup too large to assemble in a Worker";
|
|
388
|
+
};
|
|
389
|
+
readonly CRON_JOBS_NOT_CONFIGURED: {
|
|
390
|
+
readonly status: 400;
|
|
391
|
+
readonly title: "Cron jobs not configured";
|
|
392
|
+
};
|
|
393
|
+
readonly CRON_JOB_NOT_FOUND: {
|
|
394
|
+
readonly status: 404;
|
|
395
|
+
readonly title: "Cron job not found";
|
|
396
|
+
};
|
|
397
|
+
readonly EXPORT_SHARD_FAILED: {
|
|
398
|
+
readonly hint: readonly ["One or more shards failed to export, so the snapshot would have been short. Nothing is written when this fires — a partial export must never be mistaken for a complete one.", "", "The message names each failed shard key and its error. Re-run the export once those shards are reachable; a shard that fails repeatedly is usually over the per-request memory budget, which `backupTables` narrows."];
|
|
399
|
+
readonly status: 502;
|
|
400
|
+
readonly title: "Export failed on one or more shards";
|
|
401
|
+
};
|
|
402
|
+
readonly EXPORT_TAP_NOT_CONFIGURED: {
|
|
403
|
+
readonly status: 400;
|
|
404
|
+
readonly title: "Export tap not configured";
|
|
405
|
+
};
|
|
406
|
+
readonly FUNCTIONS_NOT_CONFIGURED: {
|
|
407
|
+
readonly status: 400;
|
|
408
|
+
readonly title: "Functions registry not configured";
|
|
409
|
+
};
|
|
410
|
+
readonly GLOBALS_NOT_CONFIGURED: {
|
|
411
|
+
readonly status: 400;
|
|
412
|
+
readonly title: "Global-table introspector not configured";
|
|
413
|
+
};
|
|
414
|
+
readonly ID_COLLISION: {
|
|
415
|
+
readonly hint: readonly ["The imported row carries an `_id` that is already held by a DIFFERENT table in this shard. Ids are per-table, so inserting it would leave two tables claiming one id and make a later lookup resolve to whichever one it reached first.", "", "This is reported per row rather than aborting the import: the remaining rows still apply. Re-mint the id on the source side, or import that table into a shard that does not already hold it."];
|
|
416
|
+
readonly status: 409;
|
|
417
|
+
readonly title: "Document id already belongs to another table";
|
|
418
|
+
};
|
|
419
|
+
readonly KV_NOT_CONFIGURED: {
|
|
420
|
+
readonly status: 400;
|
|
421
|
+
readonly title: "KV introspector not configured";
|
|
422
|
+
};
|
|
423
|
+
readonly MIGRATION_ID_REQUIRED: {
|
|
424
|
+
readonly status: 400;
|
|
425
|
+
readonly title: "Migration id required";
|
|
426
|
+
};
|
|
427
|
+
readonly PITR_UNAVAILABLE: {
|
|
428
|
+
readonly status: 409;
|
|
429
|
+
readonly title: "Point-in-time recovery unavailable";
|
|
430
|
+
};
|
|
431
|
+
readonly SCHEDULER_NOT_CONFIGURED: {
|
|
432
|
+
readonly status: 400;
|
|
433
|
+
readonly title: "Scheduler not configured";
|
|
434
|
+
};
|
|
435
|
+
readonly STORAGE_CHECKSUM_MISMATCH: {
|
|
436
|
+
readonly hint: readonly ["The upload body did not match the declared `expectedSize` or `expectedSha256`, so nothing was written — this check fails closed.", "", "Re-read the bytes from the source export and retry the transfer. A persistent mismatch means the source blob is corrupt or truncated; fix the export rather than bypassing the check."];
|
|
437
|
+
readonly status: 400;
|
|
438
|
+
readonly title: "Storage checksum mismatch";
|
|
439
|
+
};
|
|
440
|
+
readonly STORAGE_DELETE_NOT_CONFIGURED: {
|
|
441
|
+
readonly status: 400;
|
|
442
|
+
readonly title: "Storage delete not configured";
|
|
443
|
+
};
|
|
444
|
+
readonly STORAGE_DOWNLOAD_NOT_CONFIGURED: {
|
|
445
|
+
readonly hint: readonly ["`GET /_lunora/admin/storage/object` needs a `storageDownload` function on the worker. The generated app worker wires it up; a hand-written `createWorker({ ... })` has to pass `(key, opts) => pick(opts?.bucket).download(key)` — forwarding `opts.bucket` to the right bucket, and wrapping rather than passing `createStorage(...).download` itself, whose second parameter is a byte range.", "", "Without it a bucket-backed `lunora backup restore --bucket` cannot read the snapshot. The object is still readable out of band with `wrangler r2 object get`."];
|
|
446
|
+
readonly status: 400;
|
|
447
|
+
readonly title: "Storage download not configured";
|
|
448
|
+
};
|
|
449
|
+
readonly STORAGE_NOT_CONFIGURED: {
|
|
450
|
+
readonly status: 400;
|
|
451
|
+
readonly title: "Storage not configured";
|
|
452
|
+
};
|
|
453
|
+
readonly STORAGE_OBJECT_NOT_FOUND: {
|
|
454
|
+
readonly status: 404;
|
|
455
|
+
readonly title: "Storage object not found";
|
|
456
|
+
};
|
|
457
|
+
readonly STORAGE_UPLOAD_NOT_CONFIGURED: {
|
|
458
|
+
readonly status: 400;
|
|
459
|
+
readonly title: "Storage upload not configured";
|
|
460
|
+
};
|
|
461
|
+
readonly STORAGE_URL_NOT_CONFIGURED: {
|
|
462
|
+
readonly status: 400;
|
|
463
|
+
readonly title: "Storage signed URL not configured";
|
|
464
|
+
};
|
|
465
|
+
readonly RAG_DIMENSION_MISMATCH: {
|
|
466
|
+
readonly hint: readonly ["A stored vector and the query embedding have different widths, so they cannot be compared.", "", "This is what changing a RAG index's `embeddingModel` (or a provider's `dimensions` option) without reindexing looks like. Either put the previous model back, or reindex the namespace under the new one — bump `embeddingModelVersion` so the index rebuilds instead of mixing widths."];
|
|
467
|
+
readonly status: 409;
|
|
468
|
+
readonly title: "Embedding dimension mismatch";
|
|
469
|
+
};
|
|
470
|
+
readonly VECTORS_NOT_CONFIGURED: {
|
|
471
|
+
readonly status: 400;
|
|
472
|
+
readonly title: "Vector index introspector not configured";
|
|
473
|
+
};
|
|
474
|
+
readonly VECTOR_QUERY_UNSUPPORTED: {
|
|
475
|
+
readonly status: 400;
|
|
476
|
+
readonly title: "Vector index querying not enabled";
|
|
477
|
+
};
|
|
478
|
+
readonly WORKFLOWS_NOT_CONFIGURED: {
|
|
479
|
+
readonly status: 501;
|
|
480
|
+
readonly title: "Workflows not configured";
|
|
481
|
+
};
|
|
482
|
+
/** The auth/security audit read plane (`__lunora_admin__:getAuthAuditLog`). */
|
|
483
|
+
readonly AUTH_AUDIT_NOT_CONFIGURED: {
|
|
484
|
+
readonly status: 400;
|
|
485
|
+
readonly title: "Auth audit reader not configured";
|
|
486
|
+
};
|
|
487
|
+
/**
|
|
488
|
+
* Backend/DB failure reading the auth audit store. The throw site already
|
|
489
|
+
* keeps the message generic and logs the real error server-side only — flagged
|
|
490
|
+
* `internal: true` anyway as the deliberate posture for "a backend read
|
|
491
|
+
* failed", so a future edit that inlines the driver error can't leak it.
|
|
492
|
+
*/
|
|
493
|
+
readonly AUTH_AUDIT_READ_FAILED: {
|
|
494
|
+
readonly internal: true;
|
|
495
|
+
readonly status: 500;
|
|
496
|
+
readonly title: "Auth audit read failed";
|
|
497
|
+
};
|
|
498
|
+
/** Copying DO-backed auth into its jurisdiction-pinned object (`__lunora_admin__:copyAuthToJurisdiction` / `purgeUnpinnedAuth`). */
|
|
499
|
+
readonly AUTH_MOVE_NOT_CONFIGURED: {
|
|
500
|
+
readonly hint: "The copy needs DO-backed auth (`.auth({ namespace })`) pinned with `.jurisdiction(…, { pinAuth: true })`, and the auth object's `internalSecret` set.";
|
|
501
|
+
readonly status: 400;
|
|
502
|
+
readonly title: "Auth jurisdiction move not configured";
|
|
503
|
+
};
|
|
504
|
+
readonly AUTH_MOVE_TARGET_NOT_EMPTY: {
|
|
505
|
+
readonly hint: "The pinned auth object holds users the copy did not write (someone signed up there), so copying into it would merge two user bases. Pass `force: true` to copy anyway: a copied row that collides with one of the pinned object's rows is left out and counted under `conflicts`.";
|
|
506
|
+
readonly status: 409;
|
|
507
|
+
readonly title: "Pinned auth object is not empty";
|
|
508
|
+
};
|
|
509
|
+
readonly AUTH_MOVE_CONFLICT: {
|
|
510
|
+
readonly hint: "A copied row has the same key or unique value (such as an email) as a row the pinned auth object holds on its own, or the pinned object changed a row the source changed too. Nothing from that page was written. Pass `force: true` to keep the pinned object's rows and count the collisions under `conflicts`.";
|
|
511
|
+
readonly status: 409;
|
|
512
|
+
readonly title: "Auth copy conflict";
|
|
513
|
+
};
|
|
514
|
+
readonly AUTH_MOVE_SOURCE_CHANGED: {
|
|
515
|
+
readonly hint: "The un-pinned auth object changed after the copy read it (an update, a delete, or a new row), for example during a gradual rollout or a rollback. Run the copy again: it reconciles the changed tables. Then purge.";
|
|
516
|
+
readonly status: 409;
|
|
517
|
+
readonly title: "Un-pinned auth object changed since the copy";
|
|
518
|
+
};
|
|
519
|
+
readonly AUTH_MOVE_SOURCE_PURGED: {
|
|
520
|
+
readonly hint: "The un-pinned auth object was already purged. Its tables may exist again (a rollback served requests there), but they hold only rows written since, so the copy refuses rather than read them as deletions and remove the pinned object's users. Nothing is left to copy.";
|
|
521
|
+
readonly status: 409;
|
|
522
|
+
readonly title: "Un-pinned auth object already purged";
|
|
523
|
+
};
|
|
524
|
+
readonly AUTH_MOVE_INCOMPLETE: {
|
|
525
|
+
readonly hint: "Run `__lunora_admin__:copyAuthToJurisdiction` until it answers `done: true`, check the per-table counts, then purge.";
|
|
526
|
+
readonly status: 409;
|
|
527
|
+
readonly title: "Auth copy not finished";
|
|
528
|
+
};
|
|
529
|
+
/**
|
|
530
|
+
* A move step failed inside an auth object. Not `internal`: the message is built by
|
|
531
|
+
* the object from the step, the table and a SQLite error class, never row data, so
|
|
532
|
+
* the admin caller sees why; the object logs the full error.
|
|
533
|
+
*/
|
|
534
|
+
readonly AUTH_MOVE_FAILED: {
|
|
535
|
+
readonly hint: "The message names the step, the table and the class of the SQLite error; the auth object's log has the full error. The copy resumes where it stopped once the cause is fixed.";
|
|
536
|
+
readonly status: 500;
|
|
537
|
+
readonly title: "Auth jurisdiction move failed";
|
|
538
|
+
};
|
|
539
|
+
/**
|
|
540
|
+
* Cron-job codes. The `*_NOT_STATIC` / `_INVALID` family are codegen
|
|
541
|
+
* build-time diagnostics — like `CODEGEN_DIAGNOSTIC`, they're thrown as plain
|
|
542
|
+
* messages into the CLI/Vite-overlay output, never cross the RPC wire, and are
|
|
543
|
+
* deliberately not `internal` (the message IS the fix). `CRON_JOB_FAILED` is
|
|
544
|
+
* the one runtime-reachable code in this family — its message interpolates
|
|
545
|
+
* binding/function/job names from the deployment, so it is flagged internal.
|
|
546
|
+
*/
|
|
547
|
+
readonly CRON_EXPR_INVALID: {
|
|
548
|
+
readonly status: 500;
|
|
549
|
+
readonly title: "Invalid cron expression";
|
|
550
|
+
};
|
|
551
|
+
readonly CRON_EXPR_NOT_STATIC: {
|
|
552
|
+
readonly status: 500;
|
|
553
|
+
readonly title: "Cron expression is not statically analyzable";
|
|
554
|
+
};
|
|
555
|
+
readonly CRON_JOB_FAILED: {
|
|
556
|
+
readonly internal: true;
|
|
557
|
+
readonly status: 500;
|
|
558
|
+
readonly title: "Cron job failed";
|
|
559
|
+
};
|
|
560
|
+
readonly CRON_NAME_NOT_STATIC: {
|
|
561
|
+
readonly status: 500;
|
|
562
|
+
readonly title: "Cron job name is not statically analyzable";
|
|
563
|
+
};
|
|
564
|
+
readonly CRON_NON_STATIC_FN: {
|
|
565
|
+
readonly status: 500;
|
|
566
|
+
readonly title: "Cron function reference is not statically analyzable";
|
|
567
|
+
};
|
|
568
|
+
readonly CRON_NON_STATIC_VALUE: {
|
|
569
|
+
readonly status: 500;
|
|
570
|
+
readonly title: "Cron value is not statically analyzable";
|
|
571
|
+
};
|
|
572
|
+
readonly CRON_SCHEDULE_INVALID: {
|
|
573
|
+
readonly status: 500;
|
|
574
|
+
readonly title: "Invalid cron schedule";
|
|
575
|
+
};
|
|
576
|
+
readonly CRON_SCHEDULE_NOT_STATIC: {
|
|
577
|
+
readonly status: 500;
|
|
578
|
+
readonly title: "Cron schedule is not statically analyzable";
|
|
579
|
+
};
|
|
580
|
+
readonly DUPLICATE_CRON_NAME: {
|
|
581
|
+
readonly status: 500;
|
|
582
|
+
readonly title: "Duplicate cron job name";
|
|
583
|
+
};
|
|
584
|
+
/** More codegen build-time diagnostics — see the cron-family comment above; same reasoning applies. */
|
|
585
|
+
readonly DUPLICATE_AGENT_CLASS: {
|
|
586
|
+
readonly status: 500;
|
|
587
|
+
readonly title: "Duplicate agent generated class name";
|
|
588
|
+
};
|
|
589
|
+
readonly DUPLICATE_AGENT_NAME: {
|
|
590
|
+
readonly status: 500;
|
|
591
|
+
readonly title: "Duplicate agent name";
|
|
592
|
+
};
|
|
593
|
+
readonly DUPLICATE_MIGRATION_ID: {
|
|
594
|
+
readonly status: 500;
|
|
595
|
+
readonly title: "Duplicate migration id";
|
|
596
|
+
};
|
|
597
|
+
readonly DUPLICATE_QUEUE_BINDING: {
|
|
598
|
+
readonly status: 500;
|
|
599
|
+
readonly title: "Duplicate queue binding";
|
|
600
|
+
};
|
|
601
|
+
readonly DUPLICATE_QUEUE_NAME: {
|
|
602
|
+
readonly status: 500;
|
|
603
|
+
readonly title: "Duplicate queue name";
|
|
604
|
+
};
|
|
605
|
+
readonly DUPLICATE_WORKFLOW_CLASS: {
|
|
606
|
+
readonly status: 500;
|
|
607
|
+
readonly title: "Duplicate workflow generated class name";
|
|
608
|
+
};
|
|
609
|
+
readonly MIGRATION_ID_NOT_STATIC: {
|
|
610
|
+
readonly status: 500;
|
|
611
|
+
readonly title: "Migration id is not statically analyzable";
|
|
612
|
+
};
|
|
613
|
+
readonly NAMESPACE_COLLISION: {
|
|
614
|
+
readonly status: 500;
|
|
615
|
+
readonly title: "Function namespace collision";
|
|
616
|
+
};
|
|
617
|
+
/**
|
|
618
|
+
* `@lunora/runtime`'s dispatch/security-boundary codes — RPC/HTTP entry, not
|
|
619
|
+
* admin-gated. Fixed, non-sensitive messages throughout, so none are `internal`.
|
|
620
|
+
*/
|
|
621
|
+
readonly BAD_ROW: {
|
|
622
|
+
readonly status: 400;
|
|
623
|
+
readonly title: "Malformed import row";
|
|
624
|
+
};
|
|
625
|
+
readonly BAD_SUBSCRIPTION_ARGS: {
|
|
626
|
+
readonly status: 400;
|
|
627
|
+
readonly title: "Invalid subscription arguments";
|
|
628
|
+
};
|
|
629
|
+
readonly BATCH_LIMIT_EXCEEDED: {
|
|
630
|
+
readonly status: 400;
|
|
631
|
+
readonly title: "Batch limit exceeded";
|
|
632
|
+
};
|
|
633
|
+
readonly CROSS_SHARD_RANK_UNSUPPORTED: {
|
|
634
|
+
readonly status: 400;
|
|
635
|
+
readonly title: "Cross-shard rank() is unsupported";
|
|
636
|
+
};
|
|
637
|
+
/**
|
|
638
|
+
* The `/_lunora/scheduler/dispatch` entry rejected the request's own
|
|
639
|
+
* signature/bearer — a worker/scheduler MISCONFIGURATION (missing, wrong, or
|
|
640
|
+
* rotated `LUNORA_SCHEDULER_SECRET` / `LUNORA_ADMIN_TOKEN`), not a verdict on
|
|
641
|
+
* the function being dispatched. Distinct from `FORBIDDEN`/`FORBIDDEN_SHARD`
|
|
642
|
+
* because dispatch consumers classify a 403 as deterministic and stop
|
|
643
|
+
* retrying: an auth failure clears the moment the secret is fixed, so it must
|
|
644
|
+
* stay retryable or every queued message drains into the void while the
|
|
645
|
+
* credential is wrong. See `isDeterministicDispatchFailure` in
|
|
646
|
+
* `@lunora/dispatch`.
|
|
647
|
+
*/
|
|
648
|
+
readonly DISPATCH_UNAUTHENTICATED: {
|
|
649
|
+
readonly hint: "The scheduler could not authenticate to the worker. Check that `LUNORA_SCHEDULER_SECRET` matches on both sides, or that `LUNORA_ADMIN_TOKEN` is set and current.";
|
|
650
|
+
readonly status: 403;
|
|
651
|
+
readonly title: "Dispatch caller not authenticated";
|
|
652
|
+
};
|
|
653
|
+
/**
|
|
654
|
+
* A dispatch arrived carrying an `x-lunora-mutation-id` whose handler is
|
|
655
|
+
* ALREADY executing in the shard, on the ungated (non-mutation) path where
|
|
656
|
+
* nothing serialises the two. The shard declines the second delivery rather
|
|
657
|
+
* than running it alongside the first.
|
|
658
|
+
*
|
|
659
|
+
* Deliberately a `409` and deliberately NOT one of `@lunora/dispatch`'s
|
|
660
|
+
* `DETERMINISTIC_DISPATCH_STATUSES`: the refusal is about WHEN the request
|
|
661
|
+
* arrived, not about the request. It clears on its own the moment the first
|
|
662
|
+
* attempt settles — at which point the same id replays into the dedup row
|
|
663
|
+
* and is served its cached result — so every caller (the scheduler's retry
|
|
664
|
+
* ladder, a queue consumer, a workflow step) must keep retrying it.
|
|
665
|
+
*/
|
|
666
|
+
readonly DISPATCH_IN_PROGRESS: {
|
|
667
|
+
readonly hint: "A dispatch with this idempotency id is still running on the shard. Retry it; once the first attempt settles the same id is served from the replay cache.";
|
|
668
|
+
readonly status: 409;
|
|
669
|
+
readonly title: "Dispatch already in progress";
|
|
670
|
+
};
|
|
671
|
+
readonly FORBIDDEN_FANOUT: {
|
|
672
|
+
readonly status: 403;
|
|
673
|
+
readonly title: "Fan-out forbidden";
|
|
674
|
+
};
|
|
675
|
+
readonly GLOBAL_SEARCH_SCORES_UNSUPPORTED: {
|
|
676
|
+
readonly status: 400;
|
|
677
|
+
readonly title: "collectWithScores() is unsupported on a global table";
|
|
678
|
+
};
|
|
679
|
+
readonly FORBIDDEN_ORIGIN: {
|
|
680
|
+
readonly status: 403;
|
|
681
|
+
readonly title: "Origin forbidden";
|
|
682
|
+
};
|
|
683
|
+
readonly FORBIDDEN_SHARD: {
|
|
684
|
+
readonly status: 403;
|
|
685
|
+
readonly title: "Shard access forbidden";
|
|
686
|
+
};
|
|
687
|
+
readonly GLOBAL_NOT_CONFIGURED: {
|
|
688
|
+
readonly status: 400;
|
|
689
|
+
readonly title: "Global table import not configured";
|
|
690
|
+
};
|
|
691
|
+
readonly INVALID_INPUT: {
|
|
692
|
+
readonly status: 400;
|
|
693
|
+
readonly title: "Invalid input";
|
|
694
|
+
};
|
|
695
|
+
/** A caller-supplied `RunOptions.id` that is not a safe key segment. NOT internal: the message is fixed guidance about the caller's own argument, and withholding it would leave them guessing at a 400. */
|
|
696
|
+
readonly INVALID_SCHEDULE_ID: {
|
|
697
|
+
readonly status: 400;
|
|
698
|
+
readonly title: "Invalid schedule id";
|
|
699
|
+
};
|
|
700
|
+
readonly RATE_LIMITED: {
|
|
701
|
+
readonly status: 429;
|
|
702
|
+
readonly title: "Rate limited";
|
|
703
|
+
};
|
|
704
|
+
/**
|
|
705
|
+
* A read replica could not answer at the freshness the caller required. `421`
|
|
706
|
+
* rather than an error class: it is a ROUTING verdict the runtime turns into
|
|
707
|
+
* one retry against the owner, and a caller never sees it.
|
|
708
|
+
*/
|
|
709
|
+
readonly REPLICA_NOT_READY: {
|
|
710
|
+
readonly status: 421;
|
|
711
|
+
readonly title: "Replica not caught up";
|
|
712
|
+
};
|
|
713
|
+
/** A write reached a read replica. Same `421` routing verdict — writes belong to the owner. */
|
|
714
|
+
readonly REPLICA_READ_ONLY: {
|
|
715
|
+
readonly status: 421;
|
|
716
|
+
readonly title: "Replica is read-only";
|
|
717
|
+
};
|
|
718
|
+
/**
|
|
719
|
+
* A search index is provisioned but still covers only part of its table, so
|
|
720
|
+
* the read refuses rather than answering from the indexed prefix.
|
|
721
|
+
*
|
|
722
|
+
* `503` and retryable, but deliberately NOT `SERVICE_UNAVAILABLE`: nothing is
|
|
723
|
+
* down. One index on one table is warming, every other read is fine, and the
|
|
724
|
+
* backfill advances on each read — so a caller that retries makes progress,
|
|
725
|
+
* where a generic outage code invites it to back off. The message names both
|
|
726
|
+
* exits (wait, or run the `backfillSearch` admin op).
|
|
727
|
+
*/
|
|
728
|
+
readonly SEARCH_INDEX_BUILDING: {
|
|
729
|
+
readonly status: 503;
|
|
730
|
+
readonly title: "Search index is still building";
|
|
731
|
+
};
|
|
732
|
+
/** Thrown by `@lunora/auth` (Turnstile) and `@lunora/ratelimit` — an upstream dependency didn't respond. Fixed, safe message. */
|
|
733
|
+
readonly SERVICE_UNAVAILABLE: {
|
|
734
|
+
readonly status: 503;
|
|
735
|
+
readonly title: "Service unavailable";
|
|
736
|
+
};
|
|
737
|
+
/**
|
|
738
|
+
* A shape was declared over, or whose predicate joins, a `.memory()` table. Refused at subscribe, because
|
|
739
|
+
* the poke path replicates from `__cdc_log` and a memory table is deliberately
|
|
740
|
+
* never appended to it — so the shape would seed once and then stay frozen
|
|
741
|
+
* while the table changed underneath it. Same registration-time refusal as
|
|
742
|
+
* `SHAPE_CROSS_SHARD_JOIN`, for the same reason: the diff can never move.
|
|
743
|
+
*/
|
|
744
|
+
readonly SHAPE_MEMORY_TABLE: {
|
|
745
|
+
readonly status: 400;
|
|
746
|
+
readonly title: "Shape over a memory table is unsupported";
|
|
747
|
+
};
|
|
748
|
+
readonly SHAPE_CROSS_SHARD_JOIN: {
|
|
749
|
+
readonly status: 400;
|
|
750
|
+
readonly title: "Shape cross-shard join is unsupported";
|
|
751
|
+
};
|
|
752
|
+
readonly UNAUTHENTICATED: {
|
|
753
|
+
readonly status: 401;
|
|
754
|
+
readonly title: "Unauthenticated";
|
|
755
|
+
};
|
|
756
|
+
/**
|
|
757
|
+
* The client could not decode a frame the server sent for a subscription.
|
|
758
|
+
*
|
|
759
|
+
* `502` because the failure is upstream of the caller: their query was valid
|
|
760
|
+
* and the payload that came back was not readable. Delivered to the
|
|
761
|
+
* subscription's `onError` rather than thrown, so one bad frame cannot escape
|
|
762
|
+
* the socket listener and abort every other subscription on the connection —
|
|
763
|
+
* which is what it did before, while the status indicator still read
|
|
764
|
+
* `connected` and the cursor silently stopped advancing.
|
|
765
|
+
*/
|
|
766
|
+
readonly WIRE_DECODE_FAILED: {
|
|
767
|
+
readonly status: 502;
|
|
768
|
+
readonly title: "Could not decode a server frame";
|
|
769
|
+
};
|
|
770
|
+
/**
|
|
771
|
+
* A function's RETURN value cannot be carried by the wire codec — a class
|
|
772
|
+
* instance (`Decimal`, an ORM entity, `Temporal.*`, `RegExp`, `Headers`), or
|
|
773
|
+
* nesting past the codec's depth cap.
|
|
774
|
+
*
|
|
775
|
+
* Deliberately NOT `internal`: the message names the offending constructor,
|
|
776
|
+
* which is the caller's own handler code and the only thing that makes the
|
|
777
|
+
* failure actionable. Redacting it leaves a bare 500 with nothing to grep.
|
|
778
|
+
* Raised INSIDE a mutation's transaction, so the writes roll back rather than
|
|
779
|
+
* committing behind a response that then fails to serialize.
|
|
780
|
+
*/
|
|
781
|
+
readonly WIRE_ENCODE_FAILED: {
|
|
782
|
+
readonly status: 500;
|
|
783
|
+
readonly title: "Could not encode a return value";
|
|
784
|
+
};
|
|
785
|
+
readonly UNKNOWN_COLUMN: {
|
|
786
|
+
readonly status: 404;
|
|
787
|
+
readonly title: "Unknown column";
|
|
788
|
+
};
|
|
789
|
+
/**
|
|
790
|
+
* `@lunora/do`'s ShardDO — WebSocket-frame codes, changelog-retention refusals
|
|
791
|
+
* and SQLite-in-DO invariants.
|
|
792
|
+
*
|
|
793
|
+
* `NESTED_TRANSACTION` and `SQL_UNAVAILABLE` are "should never happen" state
|
|
794
|
+
* invariants (mirrors `RUN_DEPTH_EXCEEDED`'s posture above): today's message
|
|
795
|
+
* is static and safe, but flagged internal so a future edit that adds
|
|
796
|
+
* diagnostic detail can't accidentally start leaking it. The three `CDC_*`
|
|
797
|
+
* codes are the opposite — ordinary, expected outcomes of a configured
|
|
798
|
+
* retention window or an operator-ordered restore — and all three are `409`
|
|
799
|
+
* because the cursor the caller holds is real but no longer serveable, so
|
|
800
|
+
* the recovery is a snapshot rather than a retry.
|
|
801
|
+
*/
|
|
802
|
+
/** A resume below the deleted changelog prefix: the entries are gone outright, for every consumer. */
|
|
803
|
+
readonly CDC_LOG_TRIMMED: {
|
|
804
|
+
readonly status: 409;
|
|
805
|
+
readonly title: "CDC log trimmed";
|
|
806
|
+
};
|
|
807
|
+
/** A resume from ABOVE the changelog's high-watermark: the shard's log rewound (a point-in-time restore), so the cursor indexes a timeline that no longer exists. Carries the surviving cursor and the freshly minted epoch to resynchronise against. */
|
|
808
|
+
readonly CDC_TIMELINE_FORKED: {
|
|
809
|
+
readonly status: 409;
|
|
810
|
+
readonly title: "CDC timeline forked";
|
|
811
|
+
};
|
|
812
|
+
/** A resume below the compacted prefix: the keys survive but their post-images do not, so only a payload consumer (streaming export, replay-PITR, a read replica) is refused. */
|
|
813
|
+
readonly CDC_PAYLOAD_COMPACTED: {
|
|
814
|
+
readonly status: 409;
|
|
815
|
+
readonly title: "CDC payloads compacted";
|
|
816
|
+
};
|
|
817
|
+
readonly EXPIRED: {
|
|
818
|
+
readonly status: 404;
|
|
819
|
+
readonly title: "Session expired";
|
|
820
|
+
};
|
|
821
|
+
readonly NESTED_TRANSACTION: {
|
|
822
|
+
readonly internal: true;
|
|
823
|
+
readonly status: 500;
|
|
824
|
+
readonly title: "Nested transaction";
|
|
825
|
+
};
|
|
826
|
+
readonly OUT_OF_ORDER: {
|
|
827
|
+
readonly status: 409;
|
|
828
|
+
readonly title: "Out-of-order mutation";
|
|
829
|
+
};
|
|
830
|
+
readonly SHAPE_GLOBAL_TOO_LARGE: {
|
|
831
|
+
readonly status: 413;
|
|
832
|
+
readonly title: "Global shape too large";
|
|
833
|
+
};
|
|
834
|
+
readonly SHAPE_NOT_FOUND: {
|
|
835
|
+
readonly status: 404;
|
|
836
|
+
readonly title: "Shape not found";
|
|
837
|
+
};
|
|
838
|
+
readonly SHAPE_REQUIRES_CDC: {
|
|
839
|
+
readonly status: 409;
|
|
840
|
+
readonly title: "Shape requires change-data-capture";
|
|
841
|
+
};
|
|
842
|
+
readonly SQL_UNAVAILABLE: {
|
|
843
|
+
readonly internal: true;
|
|
844
|
+
readonly status: 500;
|
|
845
|
+
readonly title: "SQL storage unavailable";
|
|
846
|
+
};
|
|
847
|
+
readonly STREAM_ID_IN_USE: {
|
|
848
|
+
readonly status: 409;
|
|
849
|
+
readonly title: "Stream id already in use";
|
|
850
|
+
};
|
|
851
|
+
readonly STREAM_INTERRUPTED: {
|
|
852
|
+
readonly status: 503;
|
|
853
|
+
readonly title: "Durable stream interrupted";
|
|
854
|
+
};
|
|
855
|
+
readonly STREAM_TOO_LONG: {
|
|
856
|
+
readonly status: 507;
|
|
857
|
+
readonly title: "Durable stream exceeded its chunk ceiling";
|
|
858
|
+
};
|
|
859
|
+
readonly TOKEN_EXPIRED: {
|
|
860
|
+
readonly hint: readonly ["The caller's credential verified but its expiry has passed. The worker refuses an HTTP call with this code before it runs; a shard drops a socket with close code `4001` and the same code.", "", "It is a refusal of the credential, not of the call: `@lunora/client` holds a queued write, fires `onTokenExpired`, and re-sends it after `setAuthToken`. The held write survives the refresh only if you pass the same `subject` (the user id) with the new token, `setAuthToken(freshToken, user.id)`; without one a refresh reads as a user switch and the write is rejected `OFFLINE_IDENTITY_CHANGED`. Under a cookie session there is no token to replace: renew the cookie and the write is re-sent on a backoff. The worker can only tell expired from anonymous if your `resolveIdentity` returns the identity with its past `exp` (or `expiresAtMs`), or throws a `LunoraError` with this code. Returning `null` runs the call anonymous."];
|
|
861
|
+
readonly status: 401;
|
|
862
|
+
readonly title: "Authentication token expired";
|
|
863
|
+
};
|
|
864
|
+
readonly TOO_MANY_STREAMS: {
|
|
865
|
+
readonly status: 429;
|
|
866
|
+
readonly title: "Too many streams";
|
|
867
|
+
};
|
|
868
|
+
/**
|
|
869
|
+
* A socket asked an `onWhisper` authorizer to decide more distinct topics
|
|
870
|
+
* than the shard allows. Names only the socket's own ceiling — never the
|
|
871
|
+
* topic, never what the authorizer would have answered — so it is safe on
|
|
872
|
+
* the wire and not `internal`.
|
|
873
|
+
*/
|
|
874
|
+
readonly TOO_MANY_WHISPER_TOPICS: {
|
|
875
|
+
readonly hint: "Reuse a stable set of whisper topic names, or reconnect — the per-socket verdict memo is in-memory, so a new connection starts empty. Leaving a topic does not free its entry.";
|
|
876
|
+
readonly status: 429;
|
|
877
|
+
readonly title: "Too many whisper topics";
|
|
878
|
+
};
|
|
879
|
+
readonly UNKNOWN_ADMIN_OP: {
|
|
880
|
+
readonly status: 404;
|
|
881
|
+
readonly title: "Unknown admin operation";
|
|
882
|
+
};
|
|
883
|
+
/**
|
|
884
|
+
* `@lunora/platform-cloudflare`'s `SocketHost.accept` guard — a caller
|
|
885
|
+
* supplied more accept-time tags (or a longer tag) than Cloudflare's
|
|
886
|
+
* `acceptWebSocket` budget allows once the host's own identity tag is
|
|
887
|
+
* reserved. Caller-actionable and safe (names counts, not internals) —
|
|
888
|
+
* not `internal`.
|
|
889
|
+
*/
|
|
890
|
+
readonly SOCKET_TAG_BUDGET_EXCEEDED: {
|
|
891
|
+
readonly status: 400;
|
|
892
|
+
readonly title: "Socket tag budget exceeded";
|
|
893
|
+
};
|
|
894
|
+
/**
|
|
895
|
+
* `@lunora/shard-engine`'s relay hub (cross-shard shape relay coordination).
|
|
896
|
+
* Mirrors `SHARD_ERROR`/`SHARD_UNAVAILABLE` above: operational status for an
|
|
897
|
+
* app's own relay topology, not a secret — not `internal`.
|
|
898
|
+
*/
|
|
899
|
+
readonly RELAY_CANNOT_SEED: {
|
|
900
|
+
readonly status: 500;
|
|
901
|
+
readonly title: "Relay cannot seed";
|
|
902
|
+
};
|
|
903
|
+
readonly RELAY_MISCONFIGURED: {
|
|
904
|
+
readonly status: 500;
|
|
905
|
+
readonly title: "Relay misconfigured";
|
|
906
|
+
};
|
|
907
|
+
readonly RELAY_SEED_FAILED: {
|
|
908
|
+
readonly status: 502;
|
|
909
|
+
readonly title: "Relay seed failed";
|
|
910
|
+
};
|
|
911
|
+
readonly RELAY_SHAPE_UNROUTABLE: {
|
|
912
|
+
readonly status: 500;
|
|
913
|
+
readonly title: "Relay shape unroutable";
|
|
914
|
+
};
|
|
915
|
+
/**
|
|
916
|
+
* A worker option required by the request path is absent (a deploy-config
|
|
917
|
+
* gap, not a caller error) — the fixed message names the missing option, so
|
|
918
|
+
* it's actionable and echoed. `MISCONFIGURED` is the one exception: its
|
|
919
|
+
* message interpolates the caller-supplied `functionPath`, and it's the code
|
|
920
|
+
* this plan's audit found live-leaking (`create-worker.ts`'s x402 gate).
|
|
921
|
+
*/
|
|
922
|
+
readonly MISCONFIGURED: {
|
|
923
|
+
readonly internal: true;
|
|
924
|
+
readonly status: 500;
|
|
925
|
+
readonly title: "Worker misconfigured";
|
|
926
|
+
};
|
|
927
|
+
/** `@lunora/nuxt`'s Nitro bridge: the request carried no Cloudflare bindings. Fixed, safe message. */
|
|
928
|
+
readonly LUNORA_RUNTIME_UNAVAILABLE: {
|
|
929
|
+
readonly status: 500;
|
|
930
|
+
readonly title: "Lunora runtime unavailable";
|
|
931
|
+
};
|
|
932
|
+
/**
|
|
933
|
+
* `@lunora/replica`'s event-log Durable Object: generic request-handler
|
|
934
|
+
* catch-all, mirroring `INTERNAL`/`INTERNAL_SERVER_ERROR`/`RPC_FAILED` above
|
|
935
|
+
* — the real error is logged server-side only, never in this code's message.
|
|
936
|
+
*/
|
|
937
|
+
readonly INTERNAL_ERROR: {
|
|
938
|
+
readonly internal: true;
|
|
939
|
+
readonly status: 500;
|
|
940
|
+
readonly title: "Internal error";
|
|
941
|
+
};
|
|
942
|
+
/**
|
|
943
|
+
* Client-SDK-only codes (`@lunora/client`), thrown locally in the browser/app
|
|
944
|
+
* process rather than by the server — `internal`'s wire-redaction semantics
|
|
945
|
+
* don't apply the same way here, since the "wire" is the app's own code
|
|
946
|
+
* catching its own client's exception. Kept in the catalog for the client's
|
|
947
|
+
* `code`-discrimination union and Studio/CLI rendering consistency.
|
|
948
|
+
*/
|
|
949
|
+
readonly BROWSER_TIMEOUT: {
|
|
950
|
+
readonly status: 504;
|
|
951
|
+
readonly title: "Browser operation timed out";
|
|
952
|
+
};
|
|
953
|
+
readonly CLIENT_CLOSED: {
|
|
954
|
+
readonly status: 400;
|
|
955
|
+
readonly title: "Client is closed";
|
|
956
|
+
};
|
|
957
|
+
readonly HTTP_STREAM_BAD_CHUNK: {
|
|
958
|
+
readonly status: 502;
|
|
959
|
+
readonly title: "Malformed HTTP stream chunk";
|
|
960
|
+
};
|
|
961
|
+
readonly HTTP_STREAM_INTERRUPTED: {
|
|
962
|
+
readonly status: 502;
|
|
963
|
+
readonly title: "HTTP stream interrupted";
|
|
964
|
+
};
|
|
965
|
+
readonly HTTP_STREAM_MISSING_PARAM: {
|
|
966
|
+
readonly status: 400;
|
|
967
|
+
readonly title: "HTTP stream missing path parameter";
|
|
968
|
+
};
|
|
969
|
+
readonly HTTP_STREAM_NO_BODY: {
|
|
970
|
+
readonly status: 502;
|
|
971
|
+
readonly title: "HTTP stream response has no body";
|
|
972
|
+
};
|
|
973
|
+
readonly HTTP_STREAM_STATUS: {
|
|
974
|
+
readonly status: 502;
|
|
975
|
+
readonly title: "HTTP stream request failed";
|
|
976
|
+
};
|
|
977
|
+
readonly HTTP_STREAM_TRANSPORT: {
|
|
978
|
+
readonly status: 502;
|
|
979
|
+
readonly title: "HTTP stream transport error";
|
|
980
|
+
};
|
|
981
|
+
readonly STREAM_BACKPRESSURE: {
|
|
982
|
+
readonly status: 429;
|
|
983
|
+
readonly title: "Stream backpressure";
|
|
984
|
+
};
|
|
985
|
+
readonly STREAM_DISCONNECTED: {
|
|
986
|
+
readonly status: 503;
|
|
987
|
+
readonly title: "Stream disconnected";
|
|
988
|
+
};
|
|
989
|
+
readonly STREAM_QUEUE_OVERFLOW: {
|
|
990
|
+
readonly status: 429;
|
|
991
|
+
readonly title: "Stream queue overflow";
|
|
992
|
+
};
|
|
993
|
+
/** `@lunora/db`'s offline outbox: a queued write targeted a collection removed/renamed in a later deploy. */
|
|
994
|
+
readonly UNKNOWN_MUTATION_FN: {
|
|
995
|
+
readonly status: 404;
|
|
996
|
+
readonly title: "Unknown mutation function";
|
|
997
|
+
};
|
|
998
|
+
/**
|
|
999
|
+
* A local tool the CLI shells out to — `wrangler`, `git`, `docker`, a
|
|
1000
|
+
* package manager — is not on PATH, so the child process never started.
|
|
1001
|
+
*
|
|
1002
|
+
* CLI-only and build-time-like: it never crosses the RPC wire (the same
|
|
1003
|
+
* posture as `CODEGEN_DIAGNOSTIC`), so it is deliberately not `internal` —
|
|
1004
|
+
* the message names the missing command, which IS the fix. The `500` is a
|
|
1005
|
+
* placeholder for a transport that never carries it; `@lunora/cli` maps this
|
|
1006
|
+
* code to its `missing local dependency` exit code BY NAME, because no HTTP
|
|
1007
|
+
* status means "that program isn't installed".
|
|
1008
|
+
*/
|
|
1009
|
+
readonly LOCAL_DEPENDENCY_MISSING: {
|
|
1010
|
+
readonly hint: readonly ["The command Lunora tried to run is not on your PATH, so nothing ran.", "", "Install it (or put it on PATH) and retry — `wrangler` ships as a dependency of a Lunora app, so `pnpm install` usually fixes that one; `git` and `docker` are installed separately."];
|
|
1011
|
+
readonly status: 500;
|
|
1012
|
+
readonly title: "Required local tool not found";
|
|
1013
|
+
};
|
|
196
1014
|
};
|
|
197
1015
|
/** A well-known Lunora error code (a key of {@link ERROR_CATALOG}). */
|
|
198
1016
|
type LunoraErrorCode = keyof typeof ERROR_CATALOG;
|
|
199
1017
|
/**
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
*
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
|
|
1018
|
+
* Look up a catalog entry by `code`, or `undefined` when the code isn't
|
|
1019
|
+
* registered. The single guarded seam for reading `ERROR_CATALOG` by an
|
|
1020
|
+
* arbitrary string: because the catalog is a plain object literal, a bracket
|
|
1021
|
+
* read for an inherited key (e.g. `"constructor"`, `"toString"`) would resolve
|
|
1022
|
+
* to `Object.prototype`'s member instead of `undefined`, so this uses
|
|
1023
|
+
* `Object.hasOwn` to only ever return an own entry. Reused by the
|
|
1024
|
+
* `LunoraError` constructor, {@link isInternalCode}, and {@link resolveHint}.
|
|
1025
|
+
*/
|
|
1026
|
+
declare const getCatalogEntry: (code: string) => ErrorCatalogEntry | undefined;
|
|
1027
|
+
/**
|
|
1028
|
+
* True when `code` is an internal/redacted code — an internal failure or
|
|
1029
|
+
* unhandled invariant whose `message` must NOT cross the wire (it may carry SQL
|
|
1030
|
+
* fragments, file paths, or internal identifiers). Derived from the catalog's
|
|
1031
|
+
* `internal` flag so the redaction posture stays in one place (the table).
|
|
1032
|
+
* Throwing a `LunoraError` with any non-internal code is the author's vouch that
|
|
1033
|
+
* its message is client-safe; an unknown/unregistered code is treated as safe.
|
|
1034
|
+
*/
|
|
207
1035
|
declare const isInternalCode: (code: string) => boolean;
|
|
208
1036
|
/**
|
|
209
|
-
* A message-matched solution for errors that reach a consumer without a `code`
|
|
210
|
-
* — chiefly `@lunora/codegen` build errors, which are thrown as plain messages
|
|
211
|
-
* into generated code (and flattened to `{ message }` by the Vite overlay), so
|
|
212
|
-
* the message text is the only stable join key. Ordered most- to least-specific;
|
|
213
|
-
* the first matching rule wins.
|
|
214
|
-
*/
|
|
1037
|
+
* A message-matched solution for errors that reach a consumer without a `code`
|
|
1038
|
+
* — chiefly `@lunora/codegen` build errors, which are thrown as plain messages
|
|
1039
|
+
* into generated code (and flattened to `{ message }` by the Vite overlay), so
|
|
1040
|
+
* the message text is the only stable join key. Ordered most- to least-specific;
|
|
1041
|
+
* the first matching rule wins.
|
|
1042
|
+
*/
|
|
215
1043
|
interface Solution {
|
|
216
1044
|
/** Markdown body shown under the header. */
|
|
217
1045
|
body: string;
|
|
@@ -226,27 +1054,92 @@ interface SolutionRule extends Solution {
|
|
|
226
1054
|
test: (message: string) => boolean;
|
|
227
1055
|
}
|
|
228
1056
|
/**
|
|
229
|
-
* Message-matched solutions (migrated verbatim from the former
|
|
230
|
-
* `@lunora/codegen` solutions table). Re-exported by `@lunora/codegen` as
|
|
231
|
-
* `LUNORA_SOLUTION_RULES` for backward compatibility.
|
|
232
|
-
*/
|
|
1057
|
+
* Message-matched solutions (migrated verbatim from the former
|
|
1058
|
+
* `@lunora/codegen` solutions table). Re-exported by `@lunora/codegen` as
|
|
1059
|
+
* `LUNORA_SOLUTION_RULES` for backward compatibility.
|
|
1060
|
+
*/
|
|
233
1061
|
declare const MESSAGE_SOLUTIONS: ReadonlyArray<SolutionRule>;
|
|
234
1062
|
/**
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
*
|
|
238
|
-
|
|
1063
|
+
* One documented Cloudflare **platform** error — an edge/origin (5xx) or
|
|
1064
|
+
* Cloudflare-service (1xxx) failure surfaced in an error *message* rather than
|
|
1065
|
+
* thrown by Lunora as a coded `LunoraError`. These reach a Lunora app as
|
|
1066
|
+
* plain text: a Worker that fetches a Cloudflare-fronted origin sees `Error 522`,
|
|
1067
|
+
* a deploy that throws surfaces as `Error 1101`, and so on. The fields are the
|
|
1068
|
+
* curated facts a grounded explainer elaborates on — never invents beyond.
|
|
1069
|
+
*/
|
|
1070
|
+
interface CloudflarePlatformError {
|
|
1071
|
+
/** Documented likely causes (a short, comma-joined clause). */
|
|
1072
|
+
causes: string;
|
|
1073
|
+
/** The numeric Cloudflare error code, as it appears in the message (e.g. `"522"`, `"1101"`). */
|
|
1074
|
+
code: string;
|
|
1075
|
+
/** Canonical Cloudflare support-docs URL for this error's family. */
|
|
1076
|
+
docsUrl: string;
|
|
1077
|
+
/** Which docs family the code belongs to — shown in the "see docs" line. */
|
|
1078
|
+
family: "1xxx" | "5xx";
|
|
1079
|
+
/** The documented remediation. */
|
|
1080
|
+
fix: string;
|
|
1081
|
+
/** One-line summary of what the code means. */
|
|
1082
|
+
summary: string;
|
|
1083
|
+
/** Cloudflare's short name for the code (e.g. `"Connection timed out"`). */
|
|
1084
|
+
title: string;
|
|
1085
|
+
}
|
|
1086
|
+
/**
|
|
1087
|
+
* The curated Cloudflare platform-error table. Sourced from Cloudflare's official
|
|
1088
|
+
* support docs — the codes surfaced to app authors on Workers/DO deployments (the
|
|
1089
|
+
* origin-connection 52x family and the Worker/DNS/security 1xxx family). `1101`
|
|
1090
|
+
* (a Worker threw) and `1102` (a Worker exceeded CPU) are the most Lunora-relevant.
|
|
1091
|
+
*/
|
|
1092
|
+
declare const CLOUDFLARE_PLATFORM_ERRORS: ReadonlyArray<CloudflarePlatformError>;
|
|
1093
|
+
/**
|
|
1094
|
+
* Recognize a Cloudflare platform-error {@link CloudflarePlatformError} in a raw
|
|
1095
|
+
* error message, conservatively: the message must carry Cloudflare's own
|
|
1096
|
+
* `Error <code>` phrasing, or mention `cloudflare` alongside the standalone
|
|
1097
|
+
* code. That keeps a bare number (`expected 520 items`) from false-matching a 5xx
|
|
1098
|
+
* code, at the cost of missing a context-free code — the safe trade for a
|
|
1099
|
+
* grounded hint. Returns the matched code's {@link Solution}, or `undefined`.
|
|
1100
|
+
*
|
|
1101
|
+
* Matching runs in two passes, strongest first: Cloudflare's own `Error <code>`
|
|
1102
|
+
* phrasing is unambiguous, so it must win over the weaker "mentions cloudflare
|
|
1103
|
+
* near some number" heuristic regardless of table order. A single pass let a weak
|
|
1104
|
+
* match on an earlier entry beat an explicit match on a later one — `"Cloudflare
|
|
1105
|
+
* Error 1102: exceeded after 524 ms"` resolved to 524, and that wrong grounded
|
|
1106
|
+
* fix is exactly what the explainer prompt is built from.
|
|
1107
|
+
*/
|
|
1108
|
+
declare const findCloudflarePlatformSolution: (message: string) => Solution | undefined;
|
|
1109
|
+
/**
|
|
1110
|
+
* Flatten a Markdown hint to plain text for a terminal / non-Markdown surface:
|
|
1111
|
+
* drop code-fence markers and strip inline `**bold**` / `` `code` `` emphasis.
|
|
1112
|
+
* Shared by the CLI renderer and the Studio `ErrorAlert` so the two can't drift.
|
|
1113
|
+
*/
|
|
239
1114
|
declare const flattenHint: (hint: ErrorHint) => string;
|
|
240
1115
|
/**
|
|
241
|
-
* Find the first message-matched {@link Solution} for `message`, or `undefined`
|
|
242
|
-
* if none recognize it. Re-exported by `@lunora/codegen` as `findLunoraSolution`.
|
|
243
|
-
*/
|
|
1116
|
+
* Find the first message-matched {@link Solution} for `message`, or `undefined`
|
|
1117
|
+
* if none recognize it. Re-exported by `@lunora/codegen` as `findLunoraSolution`.
|
|
1118
|
+
*/
|
|
244
1119
|
declare const findSolutionByMessage: (message: string) => Solution | undefined;
|
|
245
1120
|
/**
|
|
246
|
-
*
|
|
247
|
-
*
|
|
248
|
-
* `
|
|
249
|
-
|
|
1121
|
+
* Find a solution for `message` across BOTH Lunora's own rules and the curated
|
|
1122
|
+
* Cloudflare platform-error table — the lookup the Studio Issues panel and the
|
|
1123
|
+
* `explainIssue` grounding use.
|
|
1124
|
+
*
|
|
1125
|
+
* Deliberately separate from {@link findSolutionByMessage} rather than folded into
|
|
1126
|
+
* it. That function is on `resolveHint`, and therefore on `toErrorBody` — the
|
|
1127
|
+
* envelope builder for every failed request. Most `ERROR_CATALOG` entries
|
|
1128
|
+
* carry no `hint`, so folding the platform table in there meant an ordinary
|
|
1129
|
+
* `BAD_REQUEST` whose message merely mentioned "cloudflare" near a number shipped
|
|
1130
|
+
* zone-configuration guidance ("review the zone's Firewall/WAF and IP Access
|
|
1131
|
+
* Rules") to unauthenticated browsers. The same fold put the table on the CLI
|
|
1132
|
+
* renderer and the Vite overlay, and on `toErrorBody`'s hot path.
|
|
1133
|
+
*
|
|
1134
|
+
* Platform errors are operator-facing context for an already-persisted Issue, so
|
|
1135
|
+
* the operator-facing surfaces opt in here and the wire path stays Lunora-only.
|
|
1136
|
+
*/
|
|
1137
|
+
declare const findIssueSolution: (message: string) => Solution | undefined;
|
|
1138
|
+
/**
|
|
1139
|
+
* Resolve an actionable hint for an error: prefer a hint carried on the error
|
|
1140
|
+
* (or its `code`'s catalog entry), then fall back to a message match. Returns
|
|
1141
|
+
* `undefined` when nothing recognizes it.
|
|
1142
|
+
*/
|
|
250
1143
|
declare const resolveHint: (input: {
|
|
251
1144
|
code?: string;
|
|
252
1145
|
hint?: ErrorHint;
|
|
@@ -278,16 +1171,16 @@ interface LunoraErrorOptions {
|
|
|
278
1171
|
title?: string;
|
|
279
1172
|
}
|
|
280
1173
|
/**
|
|
281
|
-
* A code string: a well-known {@link LunoraErrorCode} (with autocomplete) or any
|
|
282
|
-
* package-specific code not yet in the catalog.
|
|
283
|
-
*/
|
|
1174
|
+
* A code string: a well-known {@link LunoraErrorCode} (with autocomplete) or any
|
|
1175
|
+
* package-specific code not yet in the catalog.
|
|
1176
|
+
*/
|
|
284
1177
|
type LunoraErrorCodeInput = LunoraErrorCode | (string & {});
|
|
285
1178
|
declare class LunoraError extends Error {
|
|
286
1179
|
/**
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
1180
|
+
* Discriminator recognised by `@visulima/error`'s `renderError`/`isVisulimaError`
|
|
1181
|
+
* (`error.type === "VisulimaError"`), so a `LunoraError` renders like a native
|
|
1182
|
+
* `VisulimaError` — hint and all.
|
|
1183
|
+
*/
|
|
291
1184
|
readonly type = "VisulimaError";
|
|
292
1185
|
/** Actionable fix (Markdown), rendered by the CLI/overlay/Studio. */
|
|
293
1186
|
readonly hint: ErrorHint | undefined;
|
|
@@ -316,16 +1209,37 @@ interface LunoraErrorLike extends Error {
|
|
|
316
1209
|
type: "VisulimaError";
|
|
317
1210
|
}
|
|
318
1211
|
/**
|
|
319
|
-
* True when `error` carries the Lunora transport shape (string `code` + numeric
|
|
320
|
-
* `status` + the `VisulimaError` brand). The `type` brand is what distinguishes
|
|
321
|
-
* a real `LunoraError` (or its wire-decoded twin) from a foreign error that
|
|
322
|
-
* happens to carry `code`/`status` — see plan 119 for the full rationale.
|
|
323
|
-
*/
|
|
1212
|
+
* True when `error` carries the Lunora transport shape (string `code` + numeric
|
|
1213
|
+
* `status` + the `VisulimaError` brand). The `type` brand is what distinguishes
|
|
1214
|
+
* a real `LunoraError` (or its wire-decoded twin) from a foreign error that
|
|
1215
|
+
* happens to carry `code`/`status` — see plan 119 for the full rationale.
|
|
1216
|
+
*/
|
|
324
1217
|
declare const isLunoraError: (error: unknown) => error is LunoraErrorLike;
|
|
325
1218
|
/** Throw an `INTERNAL` {@link LunoraError} when `condition` is falsy. */
|
|
326
1219
|
declare const invariant: (condition: unknown, message: string) => asserts condition;
|
|
327
1220
|
/** Throw an `INTERNAL` {@link LunoraError} for an unreachable branch. */
|
|
328
1221
|
declare const unreachable: (message: string) => never;
|
|
1222
|
+
/**
|
|
1223
|
+
* Throw a {@link LunoraError} from expression position.
|
|
1224
|
+
*
|
|
1225
|
+
* `throw` is a statement, so it cannot sit on the right of `??` or in a ternary
|
|
1226
|
+
* arm — the two places a missing value is most naturally rejected. Unlike
|
|
1227
|
+
* {@link invariant} and {@link unreachable}, which are pinned to `INTERNAL`, this
|
|
1228
|
+
* takes the code, which is what a client-actionable failure needs.
|
|
1229
|
+
*
|
|
1230
|
+
* Any string is a valid `code`. A well-known `ERROR_CATALOG` key fills in the
|
|
1231
|
+
* status, title and hint; a package-specific code with no catalog entry defaults
|
|
1232
|
+
* to status 500 and no hint, so pass those in `options` instead.
|
|
1233
|
+
* @param code machine-readable reason
|
|
1234
|
+
* @param message human-readable detail; defaults to `code`
|
|
1235
|
+
* @param options status/title/hint/data overrides
|
|
1236
|
+
* @example
|
|
1237
|
+
* ```ts
|
|
1238
|
+
* const thread = (await ctx.db.threads.get(id)) ?? raise("NOT_FOUND", `thread ${id}`);
|
|
1239
|
+
* // thread is non-nullable here
|
|
1240
|
+
* ```
|
|
1241
|
+
*/
|
|
1242
|
+
declare const raise: (code: LunoraErrorCodeInput, message?: string, options?: LunoraErrorOptions) => never;
|
|
329
1243
|
/** The client-facing error envelope body. */
|
|
330
1244
|
interface ErrorBody {
|
|
331
1245
|
code: string;
|
|
@@ -336,9 +1250,9 @@ interface ErrorBody {
|
|
|
336
1250
|
}
|
|
337
1251
|
interface ToErrorBodyOptions {
|
|
338
1252
|
/**
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
1253
|
+
* Wire-encode a `LunoraError`'s structured `data` for the client (so a
|
|
1254
|
+
* `bigint`/`bytes` inside it survives). Omit to drop `data` from the body.
|
|
1255
|
+
*/
|
|
342
1256
|
encodeData?: (data: unknown) => unknown;
|
|
343
1257
|
/** Code for an unrecognized (non-`LunoraError`) throw. Default `"INTERNAL"`. */
|
|
344
1258
|
fallbackCode?: string;
|
|
@@ -353,12 +1267,42 @@ interface ToErrorBodyResult {
|
|
|
353
1267
|
status: number;
|
|
354
1268
|
}
|
|
355
1269
|
/**
|
|
356
|
-
* Turn a thrown value into an {@link ErrorBody} + status, applying the redaction
|
|
357
|
-
* invariant. A `LunoraError` with a non-internal code is echoed with its
|
|
358
|
-
* `message`, resolved `hint`, `docsUrl`, and (when `encodeData` is given) its
|
|
359
|
-
* `data`. An internal-coded `LunoraError` keeps its `code`/`status` but its
|
|
360
|
-
* message is replaced with `redactedMessage`. Anything else becomes a generic
|
|
361
|
-
* `fallbackCode`/500. When `redacted` is `true`, log the raw error server-side.
|
|
362
|
-
*/
|
|
1270
|
+
* Turn a thrown value into an {@link ErrorBody} + status, applying the redaction
|
|
1271
|
+
* invariant. A `LunoraError` with a non-internal code is echoed with its
|
|
1272
|
+
* `message`, resolved `hint`, `docsUrl`, and (when `encodeData` is given) its
|
|
1273
|
+
* `data`. An internal-coded `LunoraError` keeps its `code`/`status` but its
|
|
1274
|
+
* message is replaced with `redactedMessage`. Anything else becomes a generic
|
|
1275
|
+
* `fallbackCode`/500. When `redacted` is `true`, log the raw error server-side.
|
|
1276
|
+
*/
|
|
363
1277
|
declare const toErrorBody: (error: unknown, options?: ToErrorBodyOptions) => ToErrorBodyResult;
|
|
364
|
-
export {
|
|
1278
|
+
export { CLOUDFLARE_PLATFORM_ERRORS, type CloudflarePlatformError, ERROR_CATALOG, type ErrorBody, type ErrorCatalogEntry, type ErrorHint,
|
|
1279
|
+
/**
|
|
1280
|
+
* `@lunora/errors` — the unified error layer.
|
|
1281
|
+
*
|
|
1282
|
+
* This package is **zero-dependency** and safe on every runtime (browser client,
|
|
1283
|
+
* workerd runtime, Node CLI): it exports the `LunoraError` class, the central
|
|
1284
|
+
* catalog, the structural guard, and the invariant helpers. The terminal
|
|
1285
|
+
* renderer (`renderLunoraError`, using `@visulima/error`'s `renderError`) lives
|
|
1286
|
+
* in `@lunora/cli`, which already depends on `@visulima/error`.
|
|
1287
|
+
*/
|
|
1288
|
+
type ErrorLocation, LunoraError, type LunoraErrorCode,
|
|
1289
|
+
/**
|
|
1290
|
+
* `@lunora/errors` — the unified error layer.
|
|
1291
|
+
*
|
|
1292
|
+
* This package is **zero-dependency** and safe on every runtime (browser client,
|
|
1293
|
+
* workerd runtime, Node CLI): it exports the `LunoraError` class, the central
|
|
1294
|
+
* catalog, the structural guard, and the invariant helpers. The terminal
|
|
1295
|
+
* renderer (`renderLunoraError`, using `@visulima/error`'s `renderError`) lives
|
|
1296
|
+
* in `@lunora/cli`, which already depends on `@visulima/error`.
|
|
1297
|
+
*/
|
|
1298
|
+
type LunoraErrorCodeInput, type LunoraErrorLike,
|
|
1299
|
+
/**
|
|
1300
|
+
* `@lunora/errors` — the unified error layer.
|
|
1301
|
+
*
|
|
1302
|
+
* This package is **zero-dependency** and safe on every runtime (browser client,
|
|
1303
|
+
* workerd runtime, Node CLI): it exports the `LunoraError` class, the central
|
|
1304
|
+
* catalog, the structural guard, and the invariant helpers. The terminal
|
|
1305
|
+
* renderer (`renderLunoraError`, using `@visulima/error`'s `renderError`) lives
|
|
1306
|
+
* in `@lunora/cli`, which already depends on `@visulima/error`.
|
|
1307
|
+
*/
|
|
1308
|
+
type LunoraErrorOptions, MESSAGE_SOLUTIONS, type Solution, type SolutionRule, type ToErrorBodyOptions, type ToErrorBodyResult, findCloudflarePlatformSolution, findIssueSolution, findSolutionByMessage, flattenHint, getCatalogEntry, invariant, isInternalCode, isLunoraError, raise, resolveHint, toErrorBody, unreachable };
|