honertia 0.1.21 → 0.1.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,700 @@
1
+ /**
2
+ * Error Catalog for Honertia
3
+ *
4
+ * Central registry of all error codes with fix generators.
5
+ * This is the single source of truth for error definitions.
6
+ */
7
+ /**
8
+ * All Honertia error codes organized by category.
9
+ *
10
+ * Naming convention: HON_<CATEGORY>_<NUMBER>_<NAME>
11
+ *
12
+ * Categories:
13
+ * - VAL (001-099): Validation errors
14
+ * - AUTH (100-199): Authentication/authorization
15
+ * - RES (200-299): Resource errors
16
+ * - CFG (300-399): Configuration errors
17
+ * - HTTP (400-499): HTTP errors
18
+ * - DB (500-599): Database errors
19
+ * - RTE (600-699): Routing errors
20
+ * - SVC (700-799): Service errors
21
+ * - INT (800-899): Internal errors
22
+ */
23
+ export const ErrorCodes = {
24
+ // Validation Errors (VAL)
25
+ VAL_001_FIELD_REQUIRED: 'HON_VAL_001_FIELD_REQUIRED',
26
+ VAL_002_FIELD_INVALID: 'HON_VAL_002_FIELD_INVALID',
27
+ VAL_003_BODY_PARSE_FAILED: 'HON_VAL_003_BODY_PARSE_FAILED',
28
+ VAL_004_SCHEMA_MISMATCH: 'HON_VAL_004_SCHEMA_MISMATCH',
29
+ VAL_005_TYPE_COERCION_FAILED: 'HON_VAL_005_TYPE_COERCION_FAILED',
30
+ // Auth Errors (AUTH)
31
+ AUTH_100_UNAUTHENTICATED: 'HON_AUTH_100_UNAUTHENTICATED',
32
+ AUTH_101_SESSION_EXPIRED: 'HON_AUTH_101_SESSION_EXPIRED',
33
+ AUTH_102_FORBIDDEN: 'HON_AUTH_102_FORBIDDEN',
34
+ AUTH_103_INVALID_CREDENTIALS: 'HON_AUTH_103_INVALID_CREDENTIALS',
35
+ // Resource Errors (RES)
36
+ RES_200_NOT_FOUND: 'HON_RES_200_NOT_FOUND',
37
+ RES_201_ALREADY_EXISTS: 'HON_RES_201_ALREADY_EXISTS',
38
+ RES_202_GONE: 'HON_RES_202_GONE',
39
+ // Configuration Errors (CFG)
40
+ CFG_300_DATABASE_NOT_CONFIGURED: 'HON_CFG_300_DATABASE_NOT_CONFIGURED',
41
+ CFG_301_AUTH_NOT_CONFIGURED: 'HON_CFG_301_AUTH_NOT_CONFIGURED',
42
+ CFG_302_SCHEMA_NOT_CONFIGURED: 'HON_CFG_302_SCHEMA_NOT_CONFIGURED',
43
+ CFG_303_HONERTIA_NOT_CONFIGURED: 'HON_CFG_303_HONERTIA_NOT_CONFIGURED',
44
+ CFG_304_BINDINGS_NOT_CONFIGURED: 'HON_CFG_304_BINDINGS_NOT_CONFIGURED',
45
+ CFG_305_INVALID_CONFIG: 'HON_CFG_305_INVALID_CONFIG',
46
+ // HTTP Errors (HTTP)
47
+ HTTP_400_BAD_REQUEST: 'HON_HTTP_400_BAD_REQUEST',
48
+ HTTP_429_RATE_LIMITED: 'HON_HTTP_429_RATE_LIMITED',
49
+ HTTP_500_INTERNAL_ERROR: 'HON_HTTP_500_INTERNAL_ERROR',
50
+ HTTP_502_BAD_GATEWAY: 'HON_HTTP_502_BAD_GATEWAY',
51
+ HTTP_503_SERVICE_UNAVAILABLE: 'HON_HTTP_503_SERVICE_UNAVAILABLE',
52
+ // Database Errors (DB)
53
+ DB_500_CONNECTION_FAILED: 'HON_DB_500_CONNECTION_FAILED',
54
+ DB_501_QUERY_FAILED: 'HON_DB_501_QUERY_FAILED',
55
+ DB_502_CONSTRAINT_VIOLATION: 'HON_DB_502_CONSTRAINT_VIOLATION',
56
+ DB_503_TRANSACTION_FAILED: 'HON_DB_503_TRANSACTION_FAILED',
57
+ // Routing Errors (RTE)
58
+ RTE_600_BINDING_NOT_FOUND: 'HON_RTE_600_BINDING_NOT_FOUND',
59
+ RTE_601_TABLE_NOT_FOUND: 'HON_RTE_601_TABLE_NOT_FOUND',
60
+ RTE_602_PARAM_VALIDATION: 'HON_RTE_602_PARAM_VALIDATION',
61
+ RTE_603_RELATION_NOT_FOUND: 'HON_RTE_603_RELATION_NOT_FOUND',
62
+ // Service Errors (SVC)
63
+ SVC_700_SERVICE_UNAVAILABLE: 'HON_SVC_700_SERVICE_UNAVAILABLE',
64
+ SVC_701_SERVICE_ERROR: 'HON_SVC_701_SERVICE_ERROR',
65
+ // Internal Errors (INT)
66
+ INT_800_UNEXPECTED: 'HON_INT_800_UNEXPECTED',
67
+ INT_801_EFFECT_DEFECT: 'HON_INT_801_EFFECT_DEFECT',
68
+ };
69
+ /**
70
+ * Fix generators for common scenarios.
71
+ */
72
+ const fixGenerators = {
73
+ /**
74
+ * Generate fix for adding database configuration.
75
+ */
76
+ addDatabaseConfig: () => ({
77
+ id: 'add-database-config',
78
+ type: 'modify_code',
79
+ confidence: 'high',
80
+ description: 'Add database configuration to setupHonertia',
81
+ automated: true,
82
+ operations: [
83
+ {
84
+ type: 'modify_code',
85
+ position: { after: 'setupHonertia({' },
86
+ content: `
87
+ honertia: {
88
+ database: (c) => drizzle(c.env.DB),`,
89
+ },
90
+ ],
91
+ postActions: [
92
+ {
93
+ type: 'restart_server',
94
+ description: 'Restart the dev server for changes to take effect',
95
+ },
96
+ ],
97
+ }),
98
+ /**
99
+ * Generate fix for adding auth configuration.
100
+ */
101
+ addAuthConfig: () => ({
102
+ id: 'add-auth-config',
103
+ type: 'modify_code',
104
+ confidence: 'high',
105
+ description: 'Add auth configuration to setupHonertia',
106
+ automated: true,
107
+ operations: [
108
+ {
109
+ type: 'modify_code',
110
+ position: { after: 'setupHonertia({' },
111
+ content: `
112
+ honertia: {
113
+ auth: (c) => betterAuth({ database: c.var.db }),`,
114
+ },
115
+ ],
116
+ postActions: [
117
+ {
118
+ type: 'restart_server',
119
+ description: 'Restart the dev server for changes to take effect',
120
+ },
121
+ ],
122
+ }),
123
+ /**
124
+ * Generate fix for adding schema configuration.
125
+ */
126
+ addSchemaConfig: () => ({
127
+ id: 'add-schema-config',
128
+ type: 'modify_code',
129
+ confidence: 'high',
130
+ description: 'Add schema configuration for route model binding',
131
+ automated: true,
132
+ operations: [
133
+ {
134
+ type: 'add_code',
135
+ position: { line: 1 },
136
+ content: "import * as schema from './db/schema'",
137
+ },
138
+ {
139
+ type: 'modify_code',
140
+ position: { after: 'honertia: {' },
141
+ content: '\n schema,',
142
+ },
143
+ ],
144
+ postActions: [
145
+ {
146
+ type: 'restart_server',
147
+ description: 'Restart the dev server for changes to take effect',
148
+ },
149
+ ],
150
+ }),
151
+ /**
152
+ * Generate fix for making a field optional.
153
+ */
154
+ makeFieldOptional: (ctx, params) => ({
155
+ id: 'make-field-optional',
156
+ type: 'modify_code',
157
+ confidence: 'medium',
158
+ description: `Make the "${params.field}" field optional in your schema`,
159
+ automated: false,
160
+ operations: [
161
+ {
162
+ type: 'modify_code',
163
+ file: ctx.handler?.file,
164
+ content: `S.optional(S.String), // Make ${params.field} optional`,
165
+ },
166
+ ],
167
+ }),
168
+ /**
169
+ * Generate fix for providing a required field.
170
+ */
171
+ provideRequiredField: (ctx, params) => {
172
+ const routeInfo = ctx.route
173
+ ? ` for ${ctx.route.method} ${ctx.route.path}`
174
+ : '';
175
+ return {
176
+ id: 'provide-field-value',
177
+ type: 'modify_code',
178
+ confidence: 'high',
179
+ description: `Provide a value for the "${params.field}" field in your request${routeInfo}`,
180
+ automated: false,
181
+ operations: [],
182
+ };
183
+ },
184
+ /**
185
+ * Generate fix for adding RequireAuthLayer.
186
+ */
187
+ addAuthMiddleware: () => ({
188
+ id: 'add-auth-middleware',
189
+ type: 'modify_code',
190
+ confidence: 'high',
191
+ description: 'Add RequireAuthLayer to protect this route',
192
+ automated: true,
193
+ operations: [
194
+ {
195
+ type: 'modify_code',
196
+ content: `.provide(RequireAuthLayer)`,
197
+ },
198
+ ],
199
+ }),
200
+ /**
201
+ * Generate fix for redirecting to login.
202
+ */
203
+ redirectToLogin: () => ({
204
+ id: 'redirect-to-login',
205
+ type: 'modify_code',
206
+ confidence: 'high',
207
+ description: 'User needs to authenticate - redirect to login page',
208
+ automated: false,
209
+ operations: [],
210
+ }),
211
+ };
212
+ /**
213
+ * The error catalog with all error definitions.
214
+ */
215
+ export const ErrorCatalog = {
216
+ // Validation Errors
217
+ [ErrorCodes.VAL_001_FIELD_REQUIRED]: {
218
+ code: ErrorCodes.VAL_001_FIELD_REQUIRED,
219
+ tag: 'ValidationError',
220
+ category: 'validation',
221
+ title: 'Required Field Missing',
222
+ messageTemplate: 'The field "{field}" is required but was not provided.',
223
+ httpStatus: 422,
224
+ defaultFixes: [fixGenerators.provideRequiredField, fixGenerators.makeFieldOptional],
225
+ docsPath: '/errors/validation/field-required',
226
+ related: [ErrorCodes.VAL_002_FIELD_INVALID, ErrorCodes.VAL_004_SCHEMA_MISMATCH],
227
+ },
228
+ [ErrorCodes.VAL_002_FIELD_INVALID]: {
229
+ code: ErrorCodes.VAL_002_FIELD_INVALID,
230
+ tag: 'ValidationError',
231
+ category: 'validation',
232
+ title: 'Invalid Field Value',
233
+ messageTemplate: 'The field "{field}" has an invalid value: {reason}',
234
+ httpStatus: 422,
235
+ defaultFixes: [],
236
+ docsPath: '/errors/validation/field-invalid',
237
+ related: [ErrorCodes.VAL_001_FIELD_REQUIRED, ErrorCodes.VAL_005_TYPE_COERCION_FAILED],
238
+ },
239
+ [ErrorCodes.VAL_003_BODY_PARSE_FAILED]: {
240
+ code: ErrorCodes.VAL_003_BODY_PARSE_FAILED,
241
+ tag: 'ValidationError',
242
+ category: 'validation',
243
+ title: 'Request Body Parse Failed',
244
+ messageTemplate: 'Could not parse request body: {reason}',
245
+ httpStatus: 400,
246
+ defaultFixes: [],
247
+ docsPath: '/errors/validation/body-parse-failed',
248
+ related: [ErrorCodes.HTTP_400_BAD_REQUEST],
249
+ },
250
+ [ErrorCodes.VAL_004_SCHEMA_MISMATCH]: {
251
+ code: ErrorCodes.VAL_004_SCHEMA_MISMATCH,
252
+ tag: 'ValidationError',
253
+ category: 'validation',
254
+ title: 'Schema Validation Failed',
255
+ messageTemplate: 'Request data does not match the expected schema.',
256
+ httpStatus: 422,
257
+ defaultFixes: [],
258
+ docsPath: '/errors/validation/schema-mismatch',
259
+ related: [ErrorCodes.VAL_001_FIELD_REQUIRED, ErrorCodes.VAL_002_FIELD_INVALID],
260
+ },
261
+ [ErrorCodes.VAL_005_TYPE_COERCION_FAILED]: {
262
+ code: ErrorCodes.VAL_005_TYPE_COERCION_FAILED,
263
+ tag: 'ValidationError',
264
+ category: 'validation',
265
+ title: 'Type Coercion Failed',
266
+ messageTemplate: 'Could not convert "{field}" to {expectedType}.',
267
+ httpStatus: 422,
268
+ defaultFixes: [],
269
+ docsPath: '/errors/validation/type-coercion-failed',
270
+ related: [ErrorCodes.VAL_002_FIELD_INVALID],
271
+ },
272
+ // Auth Errors
273
+ [ErrorCodes.AUTH_100_UNAUTHENTICATED]: {
274
+ code: ErrorCodes.AUTH_100_UNAUTHENTICATED,
275
+ tag: 'UnauthorizedError',
276
+ category: 'auth',
277
+ title: 'Authentication Required',
278
+ messageTemplate: 'You must be logged in to access this resource.',
279
+ httpStatus: 401,
280
+ defaultFixes: [fixGenerators.redirectToLogin],
281
+ docsPath: '/errors/auth/unauthenticated',
282
+ related: [ErrorCodes.AUTH_101_SESSION_EXPIRED, ErrorCodes.AUTH_102_FORBIDDEN],
283
+ },
284
+ [ErrorCodes.AUTH_101_SESSION_EXPIRED]: {
285
+ code: ErrorCodes.AUTH_101_SESSION_EXPIRED,
286
+ tag: 'UnauthorizedError',
287
+ category: 'auth',
288
+ title: 'Session Expired',
289
+ messageTemplate: 'Your session has expired. Please log in again.',
290
+ httpStatus: 401,
291
+ defaultFixes: [fixGenerators.redirectToLogin],
292
+ docsPath: '/errors/auth/session-expired',
293
+ related: [ErrorCodes.AUTH_100_UNAUTHENTICATED],
294
+ },
295
+ [ErrorCodes.AUTH_102_FORBIDDEN]: {
296
+ code: ErrorCodes.AUTH_102_FORBIDDEN,
297
+ tag: 'ForbiddenError',
298
+ category: 'auth',
299
+ title: 'Access Forbidden',
300
+ messageTemplate: 'You do not have permission to access this resource.',
301
+ httpStatus: 403,
302
+ defaultFixes: [],
303
+ docsPath: '/errors/auth/forbidden',
304
+ related: [ErrorCodes.AUTH_100_UNAUTHENTICATED],
305
+ },
306
+ [ErrorCodes.AUTH_103_INVALID_CREDENTIALS]: {
307
+ code: ErrorCodes.AUTH_103_INVALID_CREDENTIALS,
308
+ tag: 'UnauthorizedError',
309
+ category: 'auth',
310
+ title: 'Invalid Credentials',
311
+ messageTemplate: 'The provided credentials are invalid.',
312
+ httpStatus: 401,
313
+ defaultFixes: [],
314
+ docsPath: '/errors/auth/invalid-credentials',
315
+ related: [ErrorCodes.AUTH_100_UNAUTHENTICATED],
316
+ },
317
+ // Resource Errors
318
+ [ErrorCodes.RES_200_NOT_FOUND]: {
319
+ code: ErrorCodes.RES_200_NOT_FOUND,
320
+ tag: 'NotFoundError',
321
+ category: 'resource',
322
+ title: 'Resource Not Found',
323
+ messageTemplate: 'The {resource} was not found.',
324
+ httpStatus: 404,
325
+ defaultFixes: [],
326
+ docsPath: '/errors/resource/not-found',
327
+ related: [ErrorCodes.RES_202_GONE],
328
+ },
329
+ [ErrorCodes.RES_201_ALREADY_EXISTS]: {
330
+ code: ErrorCodes.RES_201_ALREADY_EXISTS,
331
+ tag: 'HttpError',
332
+ category: 'resource',
333
+ title: 'Resource Already Exists',
334
+ messageTemplate: 'A {resource} with this identifier already exists.',
335
+ httpStatus: 409,
336
+ defaultFixes: [],
337
+ docsPath: '/errors/resource/already-exists',
338
+ related: [ErrorCodes.DB_502_CONSTRAINT_VIOLATION],
339
+ },
340
+ [ErrorCodes.RES_202_GONE]: {
341
+ code: ErrorCodes.RES_202_GONE,
342
+ tag: 'HttpError',
343
+ category: 'resource',
344
+ title: 'Resource Gone',
345
+ messageTemplate: 'The {resource} has been permanently deleted.',
346
+ httpStatus: 410,
347
+ defaultFixes: [],
348
+ docsPath: '/errors/resource/gone',
349
+ related: [ErrorCodes.RES_200_NOT_FOUND],
350
+ },
351
+ // Configuration Errors
352
+ [ErrorCodes.CFG_300_DATABASE_NOT_CONFIGURED]: {
353
+ code: ErrorCodes.CFG_300_DATABASE_NOT_CONFIGURED,
354
+ tag: 'HonertiaConfigurationError',
355
+ category: 'configuration',
356
+ title: 'Database Not Configured',
357
+ messageTemplate: 'DatabaseService is not configured. You attempted to use the database in {location}.',
358
+ httpStatus: 500,
359
+ defaultFixes: [() => fixGenerators.addDatabaseConfig()],
360
+ docsPath: '/errors/configuration/database-not-configured',
361
+ related: [ErrorCodes.CFG_301_AUTH_NOT_CONFIGURED, ErrorCodes.CFG_302_SCHEMA_NOT_CONFIGURED],
362
+ },
363
+ [ErrorCodes.CFG_301_AUTH_NOT_CONFIGURED]: {
364
+ code: ErrorCodes.CFG_301_AUTH_NOT_CONFIGURED,
365
+ tag: 'HonertiaConfigurationError',
366
+ category: 'configuration',
367
+ title: 'Auth Not Configured',
368
+ messageTemplate: 'AuthService is not configured. You attempted to use auth in {location}.',
369
+ httpStatus: 500,
370
+ defaultFixes: [() => fixGenerators.addAuthConfig()],
371
+ docsPath: '/errors/configuration/auth-not-configured',
372
+ related: [ErrorCodes.CFG_300_DATABASE_NOT_CONFIGURED],
373
+ },
374
+ [ErrorCodes.CFG_302_SCHEMA_NOT_CONFIGURED]: {
375
+ code: ErrorCodes.CFG_302_SCHEMA_NOT_CONFIGURED,
376
+ tag: 'HonertiaConfigurationError',
377
+ category: 'configuration',
378
+ title: 'Schema Not Configured',
379
+ messageTemplate: 'Schema is not configured for route model binding. Cannot resolve binding "{binding}".',
380
+ httpStatus: 500,
381
+ defaultFixes: [() => fixGenerators.addSchemaConfig()],
382
+ docsPath: '/errors/configuration/schema-not-configured',
383
+ related: [ErrorCodes.RTE_601_TABLE_NOT_FOUND],
384
+ },
385
+ [ErrorCodes.CFG_303_HONERTIA_NOT_CONFIGURED]: {
386
+ code: ErrorCodes.CFG_303_HONERTIA_NOT_CONFIGURED,
387
+ tag: 'HonertiaConfigurationError',
388
+ category: 'configuration',
389
+ title: 'Honertia Not Configured',
390
+ messageTemplate: 'Honertia middleware is not configured. Cannot render Inertia responses.',
391
+ httpStatus: 500,
392
+ defaultFixes: [],
393
+ docsPath: '/errors/configuration/honertia-not-configured',
394
+ related: [ErrorCodes.CFG_300_DATABASE_NOT_CONFIGURED],
395
+ },
396
+ [ErrorCodes.CFG_304_BINDINGS_NOT_CONFIGURED]: {
397
+ code: ErrorCodes.CFG_304_BINDINGS_NOT_CONFIGURED,
398
+ tag: 'HonertiaConfigurationError',
399
+ category: 'configuration',
400
+ title: 'Worker Bindings Not Available',
401
+ messageTemplate: 'Cloudflare Worker bindings are not available in this context.',
402
+ httpStatus: 500,
403
+ defaultFixes: [],
404
+ docsPath: '/errors/configuration/bindings-not-configured',
405
+ related: [ErrorCodes.CFG_300_DATABASE_NOT_CONFIGURED],
406
+ },
407
+ [ErrorCodes.CFG_305_INVALID_CONFIG]: {
408
+ code: ErrorCodes.CFG_305_INVALID_CONFIG,
409
+ tag: 'HonertiaConfigurationError',
410
+ category: 'configuration',
411
+ title: 'Invalid Configuration',
412
+ messageTemplate: 'Invalid configuration: {reason}',
413
+ httpStatus: 500,
414
+ defaultFixes: [],
415
+ docsPath: '/errors/configuration/invalid-config',
416
+ related: [],
417
+ },
418
+ // HTTP Errors
419
+ [ErrorCodes.HTTP_400_BAD_REQUEST]: {
420
+ code: ErrorCodes.HTTP_400_BAD_REQUEST,
421
+ tag: 'HttpError',
422
+ category: 'http',
423
+ title: 'Bad Request',
424
+ messageTemplate: 'The request could not be understood: {reason}',
425
+ httpStatus: 400,
426
+ defaultFixes: [],
427
+ docsPath: '/errors/http/bad-request',
428
+ related: [ErrorCodes.VAL_003_BODY_PARSE_FAILED],
429
+ },
430
+ [ErrorCodes.HTTP_429_RATE_LIMITED]: {
431
+ code: ErrorCodes.HTTP_429_RATE_LIMITED,
432
+ tag: 'HttpError',
433
+ category: 'http',
434
+ title: 'Rate Limited',
435
+ messageTemplate: 'Too many requests. Please try again in {retryAfter} seconds.',
436
+ httpStatus: 429,
437
+ defaultFixes: [],
438
+ docsPath: '/errors/http/rate-limited',
439
+ related: [],
440
+ },
441
+ [ErrorCodes.HTTP_500_INTERNAL_ERROR]: {
442
+ code: ErrorCodes.HTTP_500_INTERNAL_ERROR,
443
+ tag: 'HttpError',
444
+ category: 'http',
445
+ title: 'Internal Server Error',
446
+ messageTemplate: 'An unexpected error occurred.',
447
+ httpStatus: 500,
448
+ defaultFixes: [],
449
+ docsPath: '/errors/http/internal-error',
450
+ related: [ErrorCodes.INT_800_UNEXPECTED],
451
+ },
452
+ [ErrorCodes.HTTP_502_BAD_GATEWAY]: {
453
+ code: ErrorCodes.HTTP_502_BAD_GATEWAY,
454
+ tag: 'HttpError',
455
+ category: 'http',
456
+ title: 'Bad Gateway',
457
+ messageTemplate: 'The upstream server returned an invalid response.',
458
+ httpStatus: 502,
459
+ defaultFixes: [],
460
+ docsPath: '/errors/http/bad-gateway',
461
+ related: [ErrorCodes.HTTP_503_SERVICE_UNAVAILABLE],
462
+ },
463
+ [ErrorCodes.HTTP_503_SERVICE_UNAVAILABLE]: {
464
+ code: ErrorCodes.HTTP_503_SERVICE_UNAVAILABLE,
465
+ tag: 'HttpError',
466
+ category: 'http',
467
+ title: 'Service Unavailable',
468
+ messageTemplate: 'The service is temporarily unavailable. Please try again later.',
469
+ httpStatus: 503,
470
+ defaultFixes: [],
471
+ docsPath: '/errors/http/service-unavailable',
472
+ related: [ErrorCodes.HTTP_502_BAD_GATEWAY],
473
+ },
474
+ // Database Errors
475
+ [ErrorCodes.DB_500_CONNECTION_FAILED]: {
476
+ code: ErrorCodes.DB_500_CONNECTION_FAILED,
477
+ tag: 'HttpError',
478
+ category: 'database',
479
+ title: 'Database Connection Failed',
480
+ messageTemplate: 'Could not connect to the database: {reason}',
481
+ httpStatus: 500,
482
+ defaultFixes: [],
483
+ docsPath: '/errors/database/connection-failed',
484
+ related: [ErrorCodes.CFG_300_DATABASE_NOT_CONFIGURED],
485
+ },
486
+ [ErrorCodes.DB_501_QUERY_FAILED]: {
487
+ code: ErrorCodes.DB_501_QUERY_FAILED,
488
+ tag: 'HttpError',
489
+ category: 'database',
490
+ title: 'Database Query Failed',
491
+ messageTemplate: 'Database query failed: {reason}',
492
+ httpStatus: 500,
493
+ defaultFixes: [],
494
+ docsPath: '/errors/database/query-failed',
495
+ related: [ErrorCodes.DB_502_CONSTRAINT_VIOLATION],
496
+ },
497
+ [ErrorCodes.DB_502_CONSTRAINT_VIOLATION]: {
498
+ code: ErrorCodes.DB_502_CONSTRAINT_VIOLATION,
499
+ tag: 'HttpError',
500
+ category: 'database',
501
+ title: 'Constraint Violation',
502
+ messageTemplate: 'Database constraint violation: {constraint}',
503
+ httpStatus: 409,
504
+ defaultFixes: [],
505
+ docsPath: '/errors/database/constraint-violation',
506
+ related: [ErrorCodes.RES_201_ALREADY_EXISTS],
507
+ },
508
+ [ErrorCodes.DB_503_TRANSACTION_FAILED]: {
509
+ code: ErrorCodes.DB_503_TRANSACTION_FAILED,
510
+ tag: 'HttpError',
511
+ category: 'database',
512
+ title: 'Transaction Failed',
513
+ messageTemplate: 'Database transaction failed and was rolled back: {reason}',
514
+ httpStatus: 500,
515
+ defaultFixes: [],
516
+ docsPath: '/errors/database/transaction-failed',
517
+ related: [ErrorCodes.DB_501_QUERY_FAILED],
518
+ },
519
+ // Routing Errors
520
+ [ErrorCodes.RTE_600_BINDING_NOT_FOUND]: {
521
+ code: ErrorCodes.RTE_600_BINDING_NOT_FOUND,
522
+ tag: 'RouteConfigurationError',
523
+ category: 'routing',
524
+ title: 'Route Binding Not Found',
525
+ messageTemplate: 'Route binding "{binding}" was not found in the request context.',
526
+ httpStatus: 500,
527
+ defaultFixes: [],
528
+ docsPath: '/errors/routing/binding-not-found',
529
+ related: [ErrorCodes.RTE_601_TABLE_NOT_FOUND],
530
+ },
531
+ [ErrorCodes.RTE_601_TABLE_NOT_FOUND]: {
532
+ code: ErrorCodes.RTE_601_TABLE_NOT_FOUND,
533
+ tag: 'RouteConfigurationError',
534
+ category: 'routing',
535
+ title: 'Schema Table Not Found',
536
+ messageTemplate: 'No table "{table}" found in schema for route model binding.',
537
+ httpStatus: 500,
538
+ defaultFixes: [() => fixGenerators.addSchemaConfig()],
539
+ docsPath: '/errors/routing/table-not-found',
540
+ related: [ErrorCodes.CFG_302_SCHEMA_NOT_CONFIGURED],
541
+ },
542
+ [ErrorCodes.RTE_602_PARAM_VALIDATION]: {
543
+ code: ErrorCodes.RTE_602_PARAM_VALIDATION,
544
+ tag: 'NotFoundError',
545
+ category: 'routing',
546
+ title: 'Invalid Route Parameter',
547
+ messageTemplate: 'Route parameter "{param}" has invalid value: {value}',
548
+ httpStatus: 404,
549
+ defaultFixes: [],
550
+ docsPath: '/errors/routing/param-validation',
551
+ related: [ErrorCodes.RES_200_NOT_FOUND],
552
+ },
553
+ [ErrorCodes.RTE_603_RELATION_NOT_FOUND]: {
554
+ code: ErrorCodes.RTE_603_RELATION_NOT_FOUND,
555
+ tag: 'RouteConfigurationError',
556
+ category: 'routing',
557
+ title: 'Relation Not Found',
558
+ messageTemplate: 'No relation found between "{parent}" and "{child}" for nested binding.',
559
+ httpStatus: 500,
560
+ defaultFixes: [],
561
+ docsPath: '/errors/routing/relation-not-found',
562
+ related: [ErrorCodes.RTE_601_TABLE_NOT_FOUND],
563
+ },
564
+ // Service Errors
565
+ [ErrorCodes.SVC_700_SERVICE_UNAVAILABLE]: {
566
+ code: ErrorCodes.SVC_700_SERVICE_UNAVAILABLE,
567
+ tag: 'HttpError',
568
+ category: 'service',
569
+ title: 'Service Unavailable',
570
+ messageTemplate: 'The "{service}" service is not available.',
571
+ httpStatus: 500,
572
+ defaultFixes: [],
573
+ docsPath: '/errors/service/unavailable',
574
+ related: [ErrorCodes.CFG_300_DATABASE_NOT_CONFIGURED],
575
+ },
576
+ [ErrorCodes.SVC_701_SERVICE_ERROR]: {
577
+ code: ErrorCodes.SVC_701_SERVICE_ERROR,
578
+ tag: 'HttpError',
579
+ category: 'service',
580
+ title: 'Service Error',
581
+ messageTemplate: 'The "{service}" service encountered an error: {reason}',
582
+ httpStatus: 500,
583
+ defaultFixes: [],
584
+ docsPath: '/errors/service/error',
585
+ related: [ErrorCodes.INT_800_UNEXPECTED],
586
+ },
587
+ // Internal Errors
588
+ [ErrorCodes.INT_800_UNEXPECTED]: {
589
+ code: ErrorCodes.INT_800_UNEXPECTED,
590
+ tag: 'HttpError',
591
+ category: 'internal',
592
+ title: 'Unexpected Error',
593
+ messageTemplate: 'An unexpected error occurred: {reason}',
594
+ httpStatus: 500,
595
+ defaultFixes: [],
596
+ docsPath: '/errors/internal/unexpected',
597
+ related: [],
598
+ },
599
+ [ErrorCodes.INT_801_EFFECT_DEFECT]: {
600
+ code: ErrorCodes.INT_801_EFFECT_DEFECT,
601
+ tag: 'HttpError',
602
+ category: 'internal',
603
+ title: 'Effect Defect',
604
+ messageTemplate: 'An unhandled Effect defect occurred: {reason}',
605
+ httpStatus: 500,
606
+ defaultFixes: [],
607
+ docsPath: '/errors/internal/effect-defect',
608
+ related: [ErrorCodes.INT_800_UNEXPECTED],
609
+ },
610
+ };
611
+ /**
612
+ * Base URL for error documentation.
613
+ */
614
+ const DOCS_BASE_URL = 'https://honertia.dev';
615
+ /**
616
+ * Create a structured error from an error code and parameters.
617
+ *
618
+ * @param code - The error code (from ErrorCodes) or any string. Unknown codes fallback to INT_800_UNEXPECTED.
619
+ * @param params - Parameters to interpolate into the message template.
620
+ * @param context - The error context (route, request, handler info).
621
+ * @returns A fully structured error with fix suggestions.
622
+ *
623
+ * @example
624
+ * ```ts
625
+ * const error = createStructuredError(
626
+ * ErrorCodes.VAL_001_FIELD_REQUIRED,
627
+ * { field: 'email' },
628
+ * captureErrorContext(c)
629
+ * )
630
+ * ```
631
+ */
632
+ export function createStructuredError(code, params, context) {
633
+ // Check if code is a valid ErrorCode
634
+ const isValidCode = Object.values(ErrorCodes).includes(code);
635
+ const definition = isValidCode ? ErrorCatalog[code] : undefined;
636
+ if (!definition) {
637
+ // Fallback for unknown codes
638
+ return createStructuredError(ErrorCodes.INT_800_UNEXPECTED, { reason: `Unknown error code: ${code}` }, context);
639
+ }
640
+ // Interpolate message template
641
+ let message = definition.messageTemplate;
642
+ for (const [key, value] of Object.entries(params)) {
643
+ message = message.replace(new RegExp(`\\{${key}\\}`, 'g'), String(value));
644
+ }
645
+ // Generate fixes
646
+ const fixes = definition.defaultFixes
647
+ .map((gen) => gen(context, params))
648
+ .filter((f) => f !== null);
649
+ return {
650
+ code: definition.code,
651
+ tag: definition.tag,
652
+ category: definition.category,
653
+ title: definition.title,
654
+ message,
655
+ httpStatus: definition.httpStatus,
656
+ context,
657
+ fixes,
658
+ docs: {
659
+ url: `${DOCS_BASE_URL}${definition.docsPath}`,
660
+ related: definition.related,
661
+ },
662
+ timestamp: new Date().toISOString(),
663
+ };
664
+ }
665
+ /**
666
+ * Get an error definition by code.
667
+ */
668
+ export function getErrorDefinition(code) {
669
+ return ErrorCatalog[code];
670
+ }
671
+ /**
672
+ * Get all error codes for a category.
673
+ */
674
+ export function getErrorsByCategory(category) {
675
+ return Object.values(ErrorCodes).filter((code) => ErrorCatalog[code]?.category === category);
676
+ }
677
+ /**
678
+ * Mapping of service name patterns to configuration error codes.
679
+ */
680
+ const SERVICE_ERROR_CODE_MAP = [
681
+ { pattern: /database/i, code: ErrorCodes.CFG_300_DATABASE_NOT_CONFIGURED },
682
+ { pattern: /auth/i, code: ErrorCodes.CFG_301_AUTH_NOT_CONFIGURED },
683
+ { pattern: /schema/i, code: ErrorCodes.CFG_302_SCHEMA_NOT_CONFIGURED },
684
+ ];
685
+ /**
686
+ * Determine the appropriate configuration error code from a service name or message.
687
+ *
688
+ * @param serviceName - The service name (e.g., 'DatabaseService').
689
+ * @param message - Optional message to check if service name doesn't match.
690
+ * @returns The appropriate error code, defaulting to CFG_305_INVALID_CONFIG.
691
+ */
692
+ export function getConfigErrorCode(serviceName, message) {
693
+ const searchText = `${serviceName ?? ''} ${message ?? ''}`;
694
+ for (const { pattern, code } of SERVICE_ERROR_CODE_MAP) {
695
+ if (pattern.test(searchText)) {
696
+ return code;
697
+ }
698
+ }
699
+ return ErrorCodes.CFG_305_INVALID_CONFIG;
700
+ }