@constructive-io/errors 0.3.0
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/LICENSE +23 -0
- package/README.md +139 -0
- package/classify.d.ts +10 -0
- package/classify.js +20 -0
- package/define.d.ts +16 -0
- package/define.js +11 -0
- package/error.d.ts +32 -0
- package/error.js +47 -0
- package/esm/classify.d.ts +10 -0
- package/esm/classify.js +16 -0
- package/esm/define.d.ts +16 -0
- package/esm/define.js +8 -0
- package/esm/error.d.ts +32 -0
- package/esm/error.js +43 -0
- package/esm/factory.d.ts +35 -0
- package/esm/factory.js +49 -0
- package/esm/format.d.ts +24 -0
- package/esm/format.js +54 -0
- package/esm/generated/registry.generated.d.ts +27 -0
- package/esm/generated/registry.generated.js +601 -0
- package/esm/index.d.ts +9 -0
- package/esm/index.js +9 -0
- package/esm/interpolate.d.ts +6 -0
- package/esm/interpolate.js +11 -0
- package/esm/parse.d.ts +30 -0
- package/esm/parse.js +199 -0
- package/esm/pg.d.ts +14 -0
- package/esm/pg.js +49 -0
- package/esm/registry.d.ts +147 -0
- package/esm/registry.js +468 -0
- package/esm/types.d.ts +81 -0
- package/esm/types.js +10 -0
- package/factory.d.ts +35 -0
- package/factory.js +55 -0
- package/format.d.ts +24 -0
- package/format.js +59 -0
- package/generated/registry.generated.d.ts +27 -0
- package/generated/registry.generated.js +604 -0
- package/index.d.ts +9 -0
- package/index.js +25 -0
- package/interpolate.d.ts +6 -0
- package/interpolate.js +14 -0
- package/package.json +45 -0
- package/parse.d.ts +30 -0
- package/parse.js +203 -0
- package/pg.d.ts +14 -0
- package/pg.js +53 -0
- package/registry.d.ts +147 -0
- package/registry.js +474 -0
- package/types.d.ts +81 -0
- package/types.js +11 -0
package/esm/registry.js
ADDED
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
import { defineError } from './define';
|
|
2
|
+
import { generatedRegistry } from './generated/registry.generated';
|
|
3
|
+
export { defineError } from './define';
|
|
4
|
+
/**
|
|
5
|
+
* The curated Constructive error registry.
|
|
6
|
+
*
|
|
7
|
+
* These entries carry refined, typed context and hand-written copy for the
|
|
8
|
+
* codes that matter most (public auth/limit codes with real dashboard copy,
|
|
9
|
+
* native PostgreSQL constraint codes, and the pgpm CLI codes). They OVERRIDE
|
|
10
|
+
* the generated entries in {@link generatedRegistry} — every constructive-db
|
|
11
|
+
* code is present via generation; these just refine a subset.
|
|
12
|
+
*/
|
|
13
|
+
export const registry = {
|
|
14
|
+
// ===========================================================================
|
|
15
|
+
// Auth / account (public) — copy sourced from dashboard auth-errors.ts
|
|
16
|
+
// ===========================================================================
|
|
17
|
+
ACCOUNT_EXISTS: defineError({
|
|
18
|
+
code: 'ACCOUNT_EXISTS',
|
|
19
|
+
class: 'public',
|
|
20
|
+
http: 409,
|
|
21
|
+
message: 'An account with this email already exists. Please sign in or use a different email.'
|
|
22
|
+
}),
|
|
23
|
+
ACCOUNT_NOT_FOUND: defineError({
|
|
24
|
+
code: 'ACCOUNT_NOT_FOUND',
|
|
25
|
+
class: 'public',
|
|
26
|
+
http: 404,
|
|
27
|
+
message: 'No account was found.'
|
|
28
|
+
}),
|
|
29
|
+
NO_ACCOUNT_EXISTS: defineError({
|
|
30
|
+
code: 'NO_ACCOUNT_EXISTS',
|
|
31
|
+
class: 'public',
|
|
32
|
+
http: 404,
|
|
33
|
+
message: 'No account exists for this user.'
|
|
34
|
+
}),
|
|
35
|
+
INVALID_CREDENTIALS: defineError({
|
|
36
|
+
code: 'INVALID_CREDENTIALS',
|
|
37
|
+
class: 'public',
|
|
38
|
+
http: 401,
|
|
39
|
+
message: 'Invalid email or password.'
|
|
40
|
+
}),
|
|
41
|
+
INCORRECT_PASSWORD: defineError({
|
|
42
|
+
code: 'INCORRECT_PASSWORD',
|
|
43
|
+
class: 'public',
|
|
44
|
+
http: 401,
|
|
45
|
+
message: 'The password you entered is incorrect. Please try again.'
|
|
46
|
+
}),
|
|
47
|
+
ACCOUNT_LOCKED_EXCEED_ATTEMPTS: defineError({
|
|
48
|
+
code: 'ACCOUNT_LOCKED_EXCEED_ATTEMPTS',
|
|
49
|
+
class: 'public',
|
|
50
|
+
http: 423,
|
|
51
|
+
message: 'Your account has been temporarily locked due to too many failed login attempts. Please try again later or reset your password.'
|
|
52
|
+
}),
|
|
53
|
+
ACCOUNT_DISABLED: defineError({
|
|
54
|
+
code: 'ACCOUNT_DISABLED',
|
|
55
|
+
class: 'public',
|
|
56
|
+
http: 403,
|
|
57
|
+
message: 'Your account has been disabled. Please contact support for assistance.'
|
|
58
|
+
}),
|
|
59
|
+
PASSWORD_INSECURE: defineError({
|
|
60
|
+
code: 'PASSWORD_INSECURE',
|
|
61
|
+
class: 'public',
|
|
62
|
+
http: 400,
|
|
63
|
+
message: 'This password is not secure enough. Please choose a stronger password.'
|
|
64
|
+
}),
|
|
65
|
+
PASSWORD_LEN: defineError({
|
|
66
|
+
code: 'PASSWORD_LEN',
|
|
67
|
+
class: 'public',
|
|
68
|
+
http: 400,
|
|
69
|
+
message: 'Password must be between 8 and 63 characters long.'
|
|
70
|
+
}),
|
|
71
|
+
EMAIL_NOT_VERIFIED: defineError({
|
|
72
|
+
code: 'EMAIL_NOT_VERIFIED',
|
|
73
|
+
class: 'public',
|
|
74
|
+
http: 403,
|
|
75
|
+
message: 'Please verify your email address to continue.'
|
|
76
|
+
}),
|
|
77
|
+
SIGN_UP_DISABLED: defineError({
|
|
78
|
+
code: 'SIGN_UP_DISABLED',
|
|
79
|
+
class: 'public',
|
|
80
|
+
http: 403,
|
|
81
|
+
message: 'Sign-up is currently disabled.'
|
|
82
|
+
}),
|
|
83
|
+
// ===========================================================================
|
|
84
|
+
// Auth / account (public) — migrated from the server graphile SAFE_ERROR_CODES
|
|
85
|
+
// allowlist so the registry (not a hand-maintained set) is the source of truth
|
|
86
|
+
// for what is user-facing.
|
|
87
|
+
// ===========================================================================
|
|
88
|
+
USER_NOT_AUTHENTICATED: defineError({
|
|
89
|
+
code: 'USER_NOT_AUTHENTICATED',
|
|
90
|
+
class: 'public',
|
|
91
|
+
http: 401,
|
|
92
|
+
message: 'You must be signed in to do that.'
|
|
93
|
+
}),
|
|
94
|
+
ACCOUNT_LOCKED: defineError({
|
|
95
|
+
code: 'ACCOUNT_LOCKED',
|
|
96
|
+
class: 'public',
|
|
97
|
+
http: 423,
|
|
98
|
+
message: 'Your account is locked. Please try again later or reset your password.'
|
|
99
|
+
}),
|
|
100
|
+
USER_NOT_FOUND: defineError({
|
|
101
|
+
code: 'USER_NOT_FOUND',
|
|
102
|
+
class: 'public',
|
|
103
|
+
http: 404,
|
|
104
|
+
message: 'No matching user was found.'
|
|
105
|
+
}),
|
|
106
|
+
NO_PRIMARY_EMAIL: defineError({
|
|
107
|
+
code: 'NO_PRIMARY_EMAIL',
|
|
108
|
+
class: 'public',
|
|
109
|
+
http: 400,
|
|
110
|
+
message: 'No primary email address is set for this account.'
|
|
111
|
+
}),
|
|
112
|
+
NO_CREDENTIALS: defineError({
|
|
113
|
+
code: 'NO_CREDENTIALS',
|
|
114
|
+
class: 'public',
|
|
115
|
+
http: 400,
|
|
116
|
+
message: 'No credentials were provided.'
|
|
117
|
+
}),
|
|
118
|
+
BAD_SIGNIN: defineError({
|
|
119
|
+
code: 'BAD_SIGNIN',
|
|
120
|
+
class: 'public',
|
|
121
|
+
http: 401,
|
|
122
|
+
message: 'Sign in failed. Please check your credentials and try again.'
|
|
123
|
+
}),
|
|
124
|
+
// ===========================================================================
|
|
125
|
+
// Auth method toggles (public) — app-level allow_* settings
|
|
126
|
+
// ===========================================================================
|
|
127
|
+
SSO_SIGN_IN_DISABLED: defineError({
|
|
128
|
+
code: 'SSO_SIGN_IN_DISABLED',
|
|
129
|
+
class: 'public',
|
|
130
|
+
http: 403,
|
|
131
|
+
message: 'Single sign-on is not enabled for sign in.'
|
|
132
|
+
}),
|
|
133
|
+
SSO_SIGN_UP_DISABLED: defineError({
|
|
134
|
+
code: 'SSO_SIGN_UP_DISABLED',
|
|
135
|
+
class: 'public',
|
|
136
|
+
http: 403,
|
|
137
|
+
message: 'Single sign-on is not enabled for sign up.'
|
|
138
|
+
}),
|
|
139
|
+
SSO_ACCOUNT_NOT_FOUND: defineError({
|
|
140
|
+
code: 'SSO_ACCOUNT_NOT_FOUND',
|
|
141
|
+
class: 'public',
|
|
142
|
+
http: 404,
|
|
143
|
+
message: 'No single sign-on account was found.'
|
|
144
|
+
}),
|
|
145
|
+
MAGIC_LINK_SIGN_IN_DISABLED: defineError({
|
|
146
|
+
code: 'MAGIC_LINK_SIGN_IN_DISABLED',
|
|
147
|
+
class: 'public',
|
|
148
|
+
http: 403,
|
|
149
|
+
message: 'Magic link sign in is disabled.'
|
|
150
|
+
}),
|
|
151
|
+
MAGIC_LINK_SIGN_UP_DISABLED: defineError({
|
|
152
|
+
code: 'MAGIC_LINK_SIGN_UP_DISABLED',
|
|
153
|
+
class: 'public',
|
|
154
|
+
http: 403,
|
|
155
|
+
message: 'Magic link sign up is disabled.'
|
|
156
|
+
}),
|
|
157
|
+
EMAIL_OTP_SIGN_IN_DISABLED: defineError({
|
|
158
|
+
code: 'EMAIL_OTP_SIGN_IN_DISABLED',
|
|
159
|
+
class: 'public',
|
|
160
|
+
http: 403,
|
|
161
|
+
message: 'Email one-time-code sign in is disabled.'
|
|
162
|
+
}),
|
|
163
|
+
// ===========================================================================
|
|
164
|
+
// PublicKeySignature / uploads / misc (public) — migrated from the allowlist
|
|
165
|
+
// ===========================================================================
|
|
166
|
+
INVALID_PUBLIC_KEY: defineError({
|
|
167
|
+
code: 'INVALID_PUBLIC_KEY',
|
|
168
|
+
class: 'public',
|
|
169
|
+
http: 400,
|
|
170
|
+
message: 'The public key is invalid.'
|
|
171
|
+
}),
|
|
172
|
+
INVALID_MESSAGE: defineError({
|
|
173
|
+
code: 'INVALID_MESSAGE',
|
|
174
|
+
class: 'public',
|
|
175
|
+
http: 400,
|
|
176
|
+
message: 'The message is invalid.'
|
|
177
|
+
}),
|
|
178
|
+
INVALID_SIGNATURE: defineError({
|
|
179
|
+
code: 'INVALID_SIGNATURE',
|
|
180
|
+
class: 'public',
|
|
181
|
+
http: 401,
|
|
182
|
+
message: 'The signature is invalid.'
|
|
183
|
+
}),
|
|
184
|
+
UPLOAD_MIMETYPE: defineError({
|
|
185
|
+
code: 'UPLOAD_MIMETYPE',
|
|
186
|
+
class: 'public',
|
|
187
|
+
http: 415,
|
|
188
|
+
message: 'This file type is not allowed.'
|
|
189
|
+
}),
|
|
190
|
+
SINGLETON_TABLE: defineError({
|
|
191
|
+
code: 'SINGLETON_TABLE',
|
|
192
|
+
class: 'public',
|
|
193
|
+
http: 409,
|
|
194
|
+
message: 'Only one record is allowed for this resource.'
|
|
195
|
+
}),
|
|
196
|
+
// ===========================================================================
|
|
197
|
+
// Invites (public)
|
|
198
|
+
// ===========================================================================
|
|
199
|
+
INVITE_NOT_FOUND: defineError({
|
|
200
|
+
code: 'INVITE_NOT_FOUND',
|
|
201
|
+
class: 'public',
|
|
202
|
+
http: 404,
|
|
203
|
+
message: 'The invitation code is invalid or has expired. Please check the code or request a new invitation.'
|
|
204
|
+
}),
|
|
205
|
+
INVITE_LIMIT: defineError({
|
|
206
|
+
code: 'INVITE_LIMIT',
|
|
207
|
+
class: 'public',
|
|
208
|
+
http: 429,
|
|
209
|
+
message: 'This invitation has reached its usage limit. Please request a new invitation.'
|
|
210
|
+
}),
|
|
211
|
+
INVITE_EMAIL_NOT_FOUND: defineError({
|
|
212
|
+
code: 'INVITE_EMAIL_NOT_FOUND',
|
|
213
|
+
class: 'public',
|
|
214
|
+
http: 404,
|
|
215
|
+
message: 'This email is not associated with the invitation. Please use the email address the invitation was sent to.'
|
|
216
|
+
}),
|
|
217
|
+
// ===========================================================================
|
|
218
|
+
// Authorization / step-up (public)
|
|
219
|
+
// ===========================================================================
|
|
220
|
+
UNAUTHENTICATED: defineError({
|
|
221
|
+
code: 'UNAUTHENTICATED',
|
|
222
|
+
class: 'public',
|
|
223
|
+
http: 401,
|
|
224
|
+
message: 'You must be signed in to do that.'
|
|
225
|
+
}),
|
|
226
|
+
FORBIDDEN: defineError({
|
|
227
|
+
code: 'FORBIDDEN',
|
|
228
|
+
class: 'public',
|
|
229
|
+
http: 403,
|
|
230
|
+
message: 'You do not have permission to do that.'
|
|
231
|
+
}),
|
|
232
|
+
STEP_UP_REQUIRED: defineError({
|
|
233
|
+
code: 'STEP_UP_REQUIRED',
|
|
234
|
+
class: 'public',
|
|
235
|
+
http: 403,
|
|
236
|
+
message: 'Additional verification is required to continue.'
|
|
237
|
+
}),
|
|
238
|
+
MFA_REQUIRED: defineError({
|
|
239
|
+
code: 'MFA_REQUIRED',
|
|
240
|
+
class: 'public',
|
|
241
|
+
http: 403,
|
|
242
|
+
message: 'Multi-factor authentication is required to continue.'
|
|
243
|
+
}),
|
|
244
|
+
INVALID_TOKEN: defineError({
|
|
245
|
+
code: 'INVALID_TOKEN',
|
|
246
|
+
class: 'public',
|
|
247
|
+
http: 401,
|
|
248
|
+
message: 'This link or token is invalid or has expired.'
|
|
249
|
+
}),
|
|
250
|
+
// ===========================================================================
|
|
251
|
+
// Limits (public, dynamic)
|
|
252
|
+
// ===========================================================================
|
|
253
|
+
LIMIT_REACHED: defineError({
|
|
254
|
+
code: 'LIMIT_REACHED',
|
|
255
|
+
class: 'public',
|
|
256
|
+
http: 429,
|
|
257
|
+
message: 'You have reached a plan limit for this resource.',
|
|
258
|
+
positional: ['resource', 'limit']
|
|
259
|
+
}),
|
|
260
|
+
RATE_LIMITED: defineError({
|
|
261
|
+
code: 'RATE_LIMITED',
|
|
262
|
+
class: 'public',
|
|
263
|
+
http: 429,
|
|
264
|
+
message: 'Too many requests. Please slow down and try again shortly.'
|
|
265
|
+
}),
|
|
266
|
+
// ===========================================================================
|
|
267
|
+
// Client / transport (public) — synthesized client-side by the GraphQL client
|
|
268
|
+
// for network, timeout, and HTTP-level failures (no DB/server origin).
|
|
269
|
+
// ===========================================================================
|
|
270
|
+
NETWORK_ERROR: defineError({
|
|
271
|
+
code: 'NETWORK_ERROR',
|
|
272
|
+
class: 'public',
|
|
273
|
+
http: 0,
|
|
274
|
+
message: 'Network error. Please check your connection and try again.'
|
|
275
|
+
}),
|
|
276
|
+
TIMEOUT_ERROR: defineError({
|
|
277
|
+
code: 'TIMEOUT_ERROR',
|
|
278
|
+
class: 'public',
|
|
279
|
+
http: 408,
|
|
280
|
+
message: 'The request timed out. Please try again.'
|
|
281
|
+
}),
|
|
282
|
+
BAD_USER_INPUT: defineError({
|
|
283
|
+
code: 'BAD_USER_INPUT',
|
|
284
|
+
class: 'public',
|
|
285
|
+
http: 400,
|
|
286
|
+
message: 'The request was invalid. Please check your input and try again.'
|
|
287
|
+
}),
|
|
288
|
+
VALIDATION_FAILED: defineError({
|
|
289
|
+
code: 'VALIDATION_FAILED',
|
|
290
|
+
class: 'public',
|
|
291
|
+
http: 400,
|
|
292
|
+
message: 'Validation failed. Please check your input and try again.'
|
|
293
|
+
}),
|
|
294
|
+
UNKNOWN_ERROR: defineError({
|
|
295
|
+
code: 'UNKNOWN_ERROR',
|
|
296
|
+
class: 'internal',
|
|
297
|
+
http: 500,
|
|
298
|
+
message: 'An unexpected error occurred.'
|
|
299
|
+
}),
|
|
300
|
+
// ===========================================================================
|
|
301
|
+
// Native PostgreSQL constraint violations (public) — synthetic codes mapped
|
|
302
|
+
// from SQLSTATE by parse().
|
|
303
|
+
// ===========================================================================
|
|
304
|
+
UNIQUE_VIOLATION: defineError({
|
|
305
|
+
code: 'UNIQUE_VIOLATION',
|
|
306
|
+
class: 'public',
|
|
307
|
+
http: 409,
|
|
308
|
+
message: 'That value already exists.'
|
|
309
|
+
}),
|
|
310
|
+
FOREIGN_KEY_VIOLATION: defineError({
|
|
311
|
+
code: 'FOREIGN_KEY_VIOLATION',
|
|
312
|
+
class: 'public',
|
|
313
|
+
http: 409,
|
|
314
|
+
message: 'A related record is required or still referenced.'
|
|
315
|
+
}),
|
|
316
|
+
NOT_NULL_VIOLATION: defineError({
|
|
317
|
+
code: 'NOT_NULL_VIOLATION',
|
|
318
|
+
class: 'public',
|
|
319
|
+
http: 400,
|
|
320
|
+
message: 'A required value is missing.'
|
|
321
|
+
}),
|
|
322
|
+
CHECK_VIOLATION: defineError({
|
|
323
|
+
code: 'CHECK_VIOLATION',
|
|
324
|
+
class: 'public',
|
|
325
|
+
http: 400,
|
|
326
|
+
message: 'A value did not satisfy a constraint.'
|
|
327
|
+
}),
|
|
328
|
+
EXCLUSION_VIOLATION: defineError({
|
|
329
|
+
code: 'EXCLUSION_VIOLATION',
|
|
330
|
+
class: 'public',
|
|
331
|
+
http: 409,
|
|
332
|
+
message: 'A value conflicts with an existing record.'
|
|
333
|
+
}),
|
|
334
|
+
// ===========================================================================
|
|
335
|
+
// pgpm CLI / engine (mostly internal) — behavior preserved from the former
|
|
336
|
+
// pgpm/types error-factory so existing call sites are unchanged.
|
|
337
|
+
// ===========================================================================
|
|
338
|
+
NOT_FOUND: defineError({
|
|
339
|
+
code: 'NOT_FOUND',
|
|
340
|
+
class: 'public',
|
|
341
|
+
http: 404,
|
|
342
|
+
message: 'Not found.'
|
|
343
|
+
}),
|
|
344
|
+
MODULE_NOT_FOUND: defineError({
|
|
345
|
+
code: 'MODULE_NOT_FOUND',
|
|
346
|
+
class: 'internal',
|
|
347
|
+
http: 404,
|
|
348
|
+
message: 'Module "{{name}}" not found in modules list.'
|
|
349
|
+
}),
|
|
350
|
+
INTERNAL_FAILURE: defineError({
|
|
351
|
+
code: 'INTERNAL_FAILURE',
|
|
352
|
+
class: 'internal',
|
|
353
|
+
http: 500,
|
|
354
|
+
message: 'Something went wrong: {{details}}'
|
|
355
|
+
}),
|
|
356
|
+
CONTEXT_MISSING: defineError({
|
|
357
|
+
code: 'CONTEXT_MISSING',
|
|
358
|
+
class: 'internal',
|
|
359
|
+
http: 500,
|
|
360
|
+
message: 'Context is not initialized. Did you run setup()?'
|
|
361
|
+
}),
|
|
362
|
+
NOT_IN_WORKSPACE: defineError({
|
|
363
|
+
code: 'NOT_IN_WORKSPACE',
|
|
364
|
+
class: 'internal',
|
|
365
|
+
http: 400,
|
|
366
|
+
message: 'You must be in a PGPM workspace. Initialize with "pgpm init workspace".'
|
|
367
|
+
}),
|
|
368
|
+
NOT_IN_WORKSPACE_MODULE: defineError({
|
|
369
|
+
code: 'NOT_IN_WORKSPACE_MODULE',
|
|
370
|
+
class: 'internal',
|
|
371
|
+
http: 400,
|
|
372
|
+
message: 'Error: You must be inside one of the workspace packages.'
|
|
373
|
+
}),
|
|
374
|
+
DEPLOYMENT_FAILED: defineError({
|
|
375
|
+
code: 'DEPLOYMENT_FAILED',
|
|
376
|
+
class: 'internal',
|
|
377
|
+
http: 500,
|
|
378
|
+
message: '{{type}} failed for module: {{module}}'
|
|
379
|
+
}),
|
|
380
|
+
UNSUPPORTED_TYPE_HINT: defineError({
|
|
381
|
+
code: 'UNSUPPORTED_TYPE_HINT',
|
|
382
|
+
class: 'internal',
|
|
383
|
+
http: 400,
|
|
384
|
+
message: 'Unsupported type hint: {{typeHint}}'
|
|
385
|
+
}),
|
|
386
|
+
BAD_FILE_NAME: defineError({
|
|
387
|
+
code: 'BAD_FILE_NAME',
|
|
388
|
+
class: 'internal',
|
|
389
|
+
http: 400,
|
|
390
|
+
message: 'Invalid file name: {{name}}'
|
|
391
|
+
}),
|
|
392
|
+
UNKNOWN_COMMAND: defineError({
|
|
393
|
+
code: 'UNKNOWN_COMMAND',
|
|
394
|
+
class: 'internal',
|
|
395
|
+
http: 400,
|
|
396
|
+
message: 'Unknown command: {{cmd}}'
|
|
397
|
+
}),
|
|
398
|
+
CHANGE_NOT_FOUND: defineError({
|
|
399
|
+
code: 'CHANGE_NOT_FOUND',
|
|
400
|
+
class: 'internal',
|
|
401
|
+
http: 404,
|
|
402
|
+
message: ({ change, plan }) => `Change '${change}' not found in plan${plan ? ` file: ${plan}` : ''}`
|
|
403
|
+
}),
|
|
404
|
+
TAG_NOT_FOUND: defineError({
|
|
405
|
+
code: 'TAG_NOT_FOUND',
|
|
406
|
+
class: 'internal',
|
|
407
|
+
http: 404,
|
|
408
|
+
message: ({ tag, project }) => `Tag '${tag}' not found${project ? ` in project ${project}` : ' in plan'}`
|
|
409
|
+
}),
|
|
410
|
+
PATH_NOT_FOUND: defineError({
|
|
411
|
+
code: 'PATH_NOT_FOUND',
|
|
412
|
+
class: 'internal',
|
|
413
|
+
http: 404,
|
|
414
|
+
message: ({ path, type }) => `${type} path not found: ${path}`
|
|
415
|
+
}),
|
|
416
|
+
OPERATION_FAILED: defineError({
|
|
417
|
+
code: 'OPERATION_FAILED',
|
|
418
|
+
class: 'internal',
|
|
419
|
+
http: 500,
|
|
420
|
+
message: ({ operation, target, reason }) => `${operation} failed${target ? ` for ${target}` : ''}${reason ? `: ${reason}` : ''}`
|
|
421
|
+
}),
|
|
422
|
+
PLAN_PARSE_ERROR: defineError({
|
|
423
|
+
code: 'PLAN_PARSE_ERROR',
|
|
424
|
+
class: 'internal',
|
|
425
|
+
http: 400,
|
|
426
|
+
message: ({ planPath, errors }) => `Failed to parse plan file ${planPath}: ${errors}`
|
|
427
|
+
}),
|
|
428
|
+
CIRCULAR_DEPENDENCY: defineError({
|
|
429
|
+
code: 'CIRCULAR_DEPENDENCY',
|
|
430
|
+
class: 'internal',
|
|
431
|
+
http: 400,
|
|
432
|
+
message: ({ module, dependency }) => `Circular reference detected: ${module} → ${dependency}`
|
|
433
|
+
}),
|
|
434
|
+
INVALID_NAME: defineError({
|
|
435
|
+
code: 'INVALID_NAME',
|
|
436
|
+
class: 'internal',
|
|
437
|
+
http: 400,
|
|
438
|
+
message: ({ name, type, rules }) => `Invalid ${type} name: ${name}${rules ? `. ${rules}` : ''}`
|
|
439
|
+
}),
|
|
440
|
+
WORKSPACE_OPERATION_ERROR: defineError({
|
|
441
|
+
code: 'WORKSPACE_OPERATION_ERROR',
|
|
442
|
+
class: 'internal',
|
|
443
|
+
http: 400,
|
|
444
|
+
message: ({ operation }) => `Cannot perform non-recursive ${operation} on workspace. Use recursive=true or specify a target module.`
|
|
445
|
+
}),
|
|
446
|
+
FILE_NOT_FOUND: defineError({
|
|
447
|
+
code: 'FILE_NOT_FOUND',
|
|
448
|
+
class: 'internal',
|
|
449
|
+
http: 404,
|
|
450
|
+
message: ({ filePath, type }) => `${type ? `${type} file` : 'File'} not found: ${filePath}`
|
|
451
|
+
})
|
|
452
|
+
};
|
|
453
|
+
/**
|
|
454
|
+
* All known definitions, keyed by code: every constructive-db code (generated)
|
|
455
|
+
* merged with the curated entries, curated winning on conflict.
|
|
456
|
+
*/
|
|
457
|
+
const allDefinitions = {
|
|
458
|
+
...generatedRegistry,
|
|
459
|
+
...registry
|
|
460
|
+
};
|
|
461
|
+
/** Look up a definition by code (from anywhere, not just typed keys). */
|
|
462
|
+
export function getDefinition(code) {
|
|
463
|
+
return allDefinitions[code];
|
|
464
|
+
}
|
|
465
|
+
/** Every registered code (curated + generated). */
|
|
466
|
+
export function allCodes() {
|
|
467
|
+
return Object.keys(allDefinitions);
|
|
468
|
+
}
|
package/esm/types.d.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core types for the Constructive error system.
|
|
3
|
+
*
|
|
4
|
+
* A Constructive error is identified by a stable, machine-readable `code`
|
|
5
|
+
* (ALL_CAPS). Human-facing copy is kept separate (in i18n catalogs) so the
|
|
6
|
+
* same code can be localized. Every code is classified as `public` (safe to
|
|
7
|
+
* surface to end users) or `internal` (an invariant/developer error that must
|
|
8
|
+
* be masked in production).
|
|
9
|
+
*/
|
|
10
|
+
/** Values that can be interpolated into a message or carried as error context. */
|
|
11
|
+
export type ContextValue = string | number | boolean | null | undefined;
|
|
12
|
+
/** Structured, machine-readable context attached to an error. */
|
|
13
|
+
export type ErrorContext = Record<string, ContextValue>;
|
|
14
|
+
/** A context with no parameters (codes that take no dynamic values). */
|
|
15
|
+
export type EmptyContext = Record<never, never>;
|
|
16
|
+
/**
|
|
17
|
+
* Classification of a code.
|
|
18
|
+
* - `public` — user-facing; safe to send to clients as-is.
|
|
19
|
+
* - `internal` — developer/invariant error; masked in production.
|
|
20
|
+
*/
|
|
21
|
+
export type ErrorClass = 'public' | 'internal';
|
|
22
|
+
/**
|
|
23
|
+
* A single registry entry describing one error code. This is the source of
|
|
24
|
+
* truth; message copy, factories, and classification all derive from it.
|
|
25
|
+
*/
|
|
26
|
+
export interface ErrorDefinition<C extends ErrorContext = EmptyContext> {
|
|
27
|
+
/** Stable machine code, e.g. `ACCOUNT_EXISTS`. */
|
|
28
|
+
code: string;
|
|
29
|
+
/** Public (surface to users) vs internal (mask in prod). */
|
|
30
|
+
class: ErrorClass;
|
|
31
|
+
/** HTTP status hint for transports that want one. */
|
|
32
|
+
http: number;
|
|
33
|
+
/**
|
|
34
|
+
* Default message. Either an i18n template using `{{var}}` placeholders
|
|
35
|
+
* (preferred — localizable) or a function for messages with conditional
|
|
36
|
+
* logic that a flat template cannot express.
|
|
37
|
+
*/
|
|
38
|
+
message: string | ((context: C) => string);
|
|
39
|
+
/** Documented context parameter names (for tooling / codegen). */
|
|
40
|
+
params?: readonly (keyof C & string)[];
|
|
41
|
+
/**
|
|
42
|
+
* For legacy dynamic DB throws of the form `CODE (arg0, arg1)`, maps the
|
|
43
|
+
* positional args to context keys so `parse()` can recover structure.
|
|
44
|
+
*/
|
|
45
|
+
positional?: readonly (keyof C & string)[];
|
|
46
|
+
}
|
|
47
|
+
/** The canonical, normalized result of parsing an error from any source. */
|
|
48
|
+
export interface ParsedError {
|
|
49
|
+
/** Constructive code if one could be determined, else `null`. */
|
|
50
|
+
code: string | null;
|
|
51
|
+
/** Structured context recovered from the error (may be empty). */
|
|
52
|
+
context: ErrorContext;
|
|
53
|
+
/** Classification (`internal` when the code is unknown — fail safe). */
|
|
54
|
+
class: ErrorClass;
|
|
55
|
+
/** `true` when `code` is present in the registry. */
|
|
56
|
+
known: boolean;
|
|
57
|
+
/** Best-effort raw message from the source error. */
|
|
58
|
+
rawMessage: string;
|
|
59
|
+
/** Original PostgreSQL SQLSTATE, when the source was a pg error. */
|
|
60
|
+
sqlState?: string;
|
|
61
|
+
/** The original error, for logging/debugging. */
|
|
62
|
+
originalError: unknown;
|
|
63
|
+
}
|
|
64
|
+
/** Extended PostgreSQL error fields (populated by pg-protocol on errors). */
|
|
65
|
+
export interface PgErrorFields {
|
|
66
|
+
code?: string;
|
|
67
|
+
detail?: string;
|
|
68
|
+
hint?: string;
|
|
69
|
+
where?: string;
|
|
70
|
+
position?: string;
|
|
71
|
+
internalPosition?: string;
|
|
72
|
+
internalQuery?: string;
|
|
73
|
+
schema?: string;
|
|
74
|
+
table?: string;
|
|
75
|
+
column?: string;
|
|
76
|
+
dataType?: string;
|
|
77
|
+
constraint?: string;
|
|
78
|
+
file?: string;
|
|
79
|
+
line?: string;
|
|
80
|
+
routine?: string;
|
|
81
|
+
}
|
package/esm/types.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core types for the Constructive error system.
|
|
3
|
+
*
|
|
4
|
+
* A Constructive error is identified by a stable, machine-readable `code`
|
|
5
|
+
* (ALL_CAPS). Human-facing copy is kept separate (in i18n catalogs) so the
|
|
6
|
+
* same code can be localized. Every code is classified as `public` (safe to
|
|
7
|
+
* surface to end users) or `internal` (an invariant/developer error that must
|
|
8
|
+
* be masked in production).
|
|
9
|
+
*/
|
|
10
|
+
export {};
|
package/factory.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ConstructiveError } from './error';
|
|
2
|
+
import { registry } from './registry';
|
|
3
|
+
import type { ErrorClass, ErrorContext, ErrorDefinition } from './types';
|
|
4
|
+
/**
|
|
5
|
+
* The callable produced for a registry entry. Codes with no context params can
|
|
6
|
+
* be called with no arguments; codes with params require a matching context.
|
|
7
|
+
* The `[keyof C]` tuple wrapper prevents `never` from distributing.
|
|
8
|
+
*/
|
|
9
|
+
export type ErrorFactory<C extends ErrorContext> = [keyof C] extends [never] ? (context?: Record<string, never>, overrideMessage?: string) => ConstructiveError : (context: C, overrideMessage?: string) => ConstructiveError;
|
|
10
|
+
export type ErrorsApi<R> = {
|
|
11
|
+
[K in keyof R]: R[K] extends {
|
|
12
|
+
__context: (context: infer C) => void;
|
|
13
|
+
} ? C extends ErrorContext ? ErrorFactory<C> : never : never;
|
|
14
|
+
};
|
|
15
|
+
/** Build a throwable factory for a single definition. */
|
|
16
|
+
export declare function makeErrorFromDefinition<C extends ErrorContext>(def: ErrorDefinition<C>): ErrorFactory<C>;
|
|
17
|
+
/** Build the `errors.*` factory object from a registry. */
|
|
18
|
+
export declare function buildErrors<R extends Record<string, {
|
|
19
|
+
__context: (context: never) => void;
|
|
20
|
+
}>>(reg: R): ErrorsApi<R>;
|
|
21
|
+
/**
|
|
22
|
+
* Low-level factory helper for defining an error inline (the pattern the former
|
|
23
|
+
* pgpm `error-factory` exposed). Prefer registering codes in the registry, but
|
|
24
|
+
* this remains available for ad-hoc / dynamically-coded errors.
|
|
25
|
+
*/
|
|
26
|
+
export declare function makeError<C extends ErrorContext>(code: string, messageFn: (context: C) => string, httpCode?: number, errorClass?: ErrorClass): (context: C, overrideMessage?: string) => ConstructiveError;
|
|
27
|
+
/**
|
|
28
|
+
* The canonical `errors.*` factory.
|
|
29
|
+
*
|
|
30
|
+
* Curated codes are strongly typed (`errors.MODULE_NOT_FOUND({ name })`); every
|
|
31
|
+
* other constructive-db code is available via a generic factory through the
|
|
32
|
+
* index signature (`errors.ACCOUNT_EXISTS()`), backed by the generated
|
|
33
|
+
* registry. Curated entries override generated ones on conflict.
|
|
34
|
+
*/
|
|
35
|
+
export declare const errors: ErrorsApi<typeof registry> & Record<string, ErrorFactory<ErrorContext>>;
|
package/factory.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.errors = void 0;
|
|
4
|
+
exports.makeErrorFromDefinition = makeErrorFromDefinition;
|
|
5
|
+
exports.buildErrors = buildErrors;
|
|
6
|
+
exports.makeError = makeError;
|
|
7
|
+
const error_1 = require("./error");
|
|
8
|
+
const format_1 = require("./format");
|
|
9
|
+
const registry_generated_1 = require("./generated/registry.generated");
|
|
10
|
+
const registry_1 = require("./registry");
|
|
11
|
+
/** Build a throwable factory for a single definition. */
|
|
12
|
+
function makeErrorFromDefinition(def) {
|
|
13
|
+
const factory = (context, overrideMessage) => new error_1.ConstructiveError({
|
|
14
|
+
code: def.code,
|
|
15
|
+
message: overrideMessage ?? (0, format_1.format)(def.code, context ?? {}),
|
|
16
|
+
errorClass: def.class,
|
|
17
|
+
http: def.http,
|
|
18
|
+
context
|
|
19
|
+
});
|
|
20
|
+
return factory;
|
|
21
|
+
}
|
|
22
|
+
/** Build the `errors.*` factory object from a registry. */
|
|
23
|
+
function buildErrors(reg) {
|
|
24
|
+
const out = {};
|
|
25
|
+
for (const key of Object.keys(reg)) {
|
|
26
|
+
out[key] = makeErrorFromDefinition(reg[key]);
|
|
27
|
+
}
|
|
28
|
+
return out;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Low-level factory helper for defining an error inline (the pattern the former
|
|
32
|
+
* pgpm `error-factory` exposed). Prefer registering codes in the registry, but
|
|
33
|
+
* this remains available for ad-hoc / dynamically-coded errors.
|
|
34
|
+
*/
|
|
35
|
+
function makeError(code, messageFn, httpCode = 500, errorClass = 'internal') {
|
|
36
|
+
return (context, overrideMessage) => new error_1.ConstructiveError({
|
|
37
|
+
code,
|
|
38
|
+
message: overrideMessage ?? messageFn(context),
|
|
39
|
+
errorClass,
|
|
40
|
+
http: httpCode,
|
|
41
|
+
context
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The canonical `errors.*` factory.
|
|
46
|
+
*
|
|
47
|
+
* Curated codes are strongly typed (`errors.MODULE_NOT_FOUND({ name })`); every
|
|
48
|
+
* other constructive-db code is available via a generic factory through the
|
|
49
|
+
* index signature (`errors.ACCOUNT_EXISTS()`), backed by the generated
|
|
50
|
+
* registry. Curated entries override generated ones on conflict.
|
|
51
|
+
*/
|
|
52
|
+
exports.errors = buildErrors({
|
|
53
|
+
...registry_generated_1.generatedRegistry,
|
|
54
|
+
...registry_1.registry
|
|
55
|
+
});
|
package/format.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ErrorContext } from './types';
|
|
2
|
+
/** A locale catalog maps `code` → message template (with `{{var}}`). */
|
|
3
|
+
export type MessageCatalog = Record<string, string>;
|
|
4
|
+
/**
|
|
5
|
+
* Locale to use when `format` is called without one. This is a plain fallback
|
|
6
|
+
* label, not a special catalog: the untagged default copy lives in the registry
|
|
7
|
+
* itself, so there is no built-in `'en'` catalog to keep in sync.
|
|
8
|
+
*/
|
|
9
|
+
export declare const DEFAULT_LOCALE = "en";
|
|
10
|
+
/**
|
|
11
|
+
* Register or extend a locale catalog. Merges with any existing entries for
|
|
12
|
+
* that locale (new entries win), so hosts can override individual codes — pass
|
|
13
|
+
* the default locale to override the base copy.
|
|
14
|
+
*/
|
|
15
|
+
export declare function registerCatalog(locale: string, catalog: MessageCatalog): void;
|
|
16
|
+
/**
|
|
17
|
+
* Render a localized message for a code.
|
|
18
|
+
*
|
|
19
|
+
* Resolution order:
|
|
20
|
+
* 1. requested-locale overlay entry (template) → interpolate
|
|
21
|
+
* 2. registry default message (template → interpolate, or function → call)
|
|
22
|
+
* 3. humanized code
|
|
23
|
+
*/
|
|
24
|
+
export declare function format(code: string, context?: ErrorContext, locale?: string): string;
|