@vertz/errors 0.1.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/dist/index.d.ts +839 -0
- package/dist/index.js +451 -0
- package/package.json +43 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,839 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Result type and utilities for errors-as-values pattern.
|
|
3
|
+
*
|
|
4
|
+
* This module provides a type-safe alternative to throwing exceptions.
|
|
5
|
+
* Every operation that can fail returns a Result<T, E> instead of throwing.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* import { ok, err, unwrap, map, flatMap, match, matchErr } from '@vertz/errors';
|
|
9
|
+
*
|
|
10
|
+
* // Creating results
|
|
11
|
+
* const success = ok({ name: 'Alice' });
|
|
12
|
+
* const failure = err({ code: 'NOT_FOUND', message: 'User not found' });
|
|
13
|
+
*
|
|
14
|
+
* // Transforming
|
|
15
|
+
* const doubled = map(ok(5), x => x * 2);
|
|
16
|
+
*
|
|
17
|
+
* // Chaining
|
|
18
|
+
* const result = await flatMap(ok(5), async x => ok(x * 2));
|
|
19
|
+
*
|
|
20
|
+
* // Pattern matching
|
|
21
|
+
* const message = match(result, {
|
|
22
|
+
* ok: (data) => `Success: ${data}`,
|
|
23
|
+
* err: (error) => `Error: ${error.message}`
|
|
24
|
+
* });
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* Represents a successful result.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* { ok: true, data: { name: 'Alice' } }
|
|
31
|
+
*/
|
|
32
|
+
interface Ok<T> {
|
|
33
|
+
/** Always true for successful results */
|
|
34
|
+
readonly ok: true;
|
|
35
|
+
/** The successful value */
|
|
36
|
+
readonly data: T;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Represents an erroneous result.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* { ok: false, error: { code: 'NOT_FOUND', message: 'User not found' } }
|
|
43
|
+
*/
|
|
44
|
+
interface Err<E> {
|
|
45
|
+
/** Always false for error results */
|
|
46
|
+
readonly ok: false;
|
|
47
|
+
/** The error value */
|
|
48
|
+
readonly error: E;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* A discriminated union representing success or failure.
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* type UserResult = Result<User, ValidationError>;
|
|
55
|
+
* type UsersResult = Result<User[], NotFoundError>;
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* const result: Result<string, Error> = ok('hello');
|
|
59
|
+
* if (result.ok) {
|
|
60
|
+
* console.log(result.data); // TypeScript knows this is string
|
|
61
|
+
* } else {
|
|
62
|
+
* console.log(result.error); // TypeScript knows this is Error
|
|
63
|
+
* }
|
|
64
|
+
*/
|
|
65
|
+
type Result<
|
|
66
|
+
T,
|
|
67
|
+
E = unknown
|
|
68
|
+
> = Ok<T> | Err<E>;
|
|
69
|
+
/**
|
|
70
|
+
* Creates a successful Result.
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* const result = ok({ name: 'Alice' });
|
|
74
|
+
* // { ok: true, data: { name: 'Alice' } }
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* // With type inference
|
|
78
|
+
* const result = ok(42); // Ok<number>
|
|
79
|
+
*/
|
|
80
|
+
declare const ok: <T>(data: T) => Ok<T>;
|
|
81
|
+
/**
|
|
82
|
+
* Creates an error Result.
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* const result = err({ code: 'VALIDATION_FAILED', message: 'Invalid email', issues: [] });
|
|
86
|
+
* // { ok: false, error: { code: 'VALIDATION_FAILED', ... } }
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* // With simple string errors
|
|
90
|
+
* const result = err('Something went wrong');
|
|
91
|
+
* // { ok: false, error: 'Something went wrong' }
|
|
92
|
+
*/
|
|
93
|
+
declare const err: <E>(error: E) => Err<E>;
|
|
94
|
+
/**
|
|
95
|
+
* Unwraps a Result, throwing if error.
|
|
96
|
+
*
|
|
97
|
+
* Use only in tests, scripts, or when failure is truly exceptional.
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* // Tests
|
|
101
|
+
* const user = unwrap(await repo.findOneRequired(id));
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* // Scripts
|
|
105
|
+
* const config = unwrap(parseConfig());
|
|
106
|
+
*
|
|
107
|
+
* @throws The error value if the Result is an error
|
|
108
|
+
*/
|
|
109
|
+
declare function unwrap<
|
|
110
|
+
T,
|
|
111
|
+
E
|
|
112
|
+
>(result: Result<T, E>): T;
|
|
113
|
+
/**
|
|
114
|
+
* Unwraps a Result, returning a default value if error.
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* const user = await findById(id) ?? { name: 'Guest' };
|
|
118
|
+
*/
|
|
119
|
+
declare function unwrapOr<
|
|
120
|
+
T,
|
|
121
|
+
E
|
|
122
|
+
>(result: Result<T, E>, defaultValue: T): T;
|
|
123
|
+
/**
|
|
124
|
+
* Maps the success value to a new type.
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* const userName = map(userResult, u => u.name);
|
|
128
|
+
* // Result<string, Error> if userResult was Result<User, Error>
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* // Transform while preserving error type
|
|
132
|
+
* const id = map(ok({ userId: 5 }), u => u.userId);
|
|
133
|
+
* // Ok<number>
|
|
134
|
+
*/
|
|
135
|
+
declare function map<
|
|
136
|
+
T,
|
|
137
|
+
E,
|
|
138
|
+
U
|
|
139
|
+
>(result: Result<T, E>, fn: (data: T) => U): Result<U, E>;
|
|
140
|
+
/**
|
|
141
|
+
* Chains Result-returning functions.
|
|
142
|
+
*
|
|
143
|
+
* Allows chaining operations that can fail without nested try/catch.
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* // Synchronous
|
|
147
|
+
* const profile = flatMap(
|
|
148
|
+
* await repo.findOne(userId),
|
|
149
|
+
* (user) => profileRepo.findOne(user.profileId)
|
|
150
|
+
* );
|
|
151
|
+
*
|
|
152
|
+
* @example
|
|
153
|
+
* // Asynchronous
|
|
154
|
+
* const finalResult = await flatMap(
|
|
155
|
+
* await repo.findOne(userId),
|
|
156
|
+
* async (user) => await profileRepo.findOne(user.profileId)
|
|
157
|
+
* );
|
|
158
|
+
*/
|
|
159
|
+
declare function flatMap<
|
|
160
|
+
T,
|
|
161
|
+
E,
|
|
162
|
+
U,
|
|
163
|
+
F
|
|
164
|
+
>(result: Result<T, E>, fn: (data: T) => Result<U, F>): Result<U, E | F>;
|
|
165
|
+
declare function flatMap<
|
|
166
|
+
T,
|
|
167
|
+
E,
|
|
168
|
+
U,
|
|
169
|
+
F
|
|
170
|
+
>(result: Result<T, E>, fn: (data: T) => Promise<Result<U, F>>): Promise<Result<U, E | F>>;
|
|
171
|
+
/**
|
|
172
|
+
* Pattern matching on Result.
|
|
173
|
+
*
|
|
174
|
+
* @example
|
|
175
|
+
* const message = match(result, {
|
|
176
|
+
* ok: (user) => \`Hello, \${user.name}!\`,
|
|
177
|
+
* err: (e) => \`Error: \${e.message}\`
|
|
178
|
+
* });
|
|
179
|
+
*/
|
|
180
|
+
declare function match<
|
|
181
|
+
T,
|
|
182
|
+
E,
|
|
183
|
+
OkR,
|
|
184
|
+
ErrR
|
|
185
|
+
>(result: Result<T, E>, handlers: {
|
|
186
|
+
ok: (data: T) => OkR;
|
|
187
|
+
err: (error: E) => ErrR;
|
|
188
|
+
}): OkR | ErrR;
|
|
189
|
+
/**
|
|
190
|
+
* Type for error handlers in matchErr.
|
|
191
|
+
* Extracts error codes from an error union and creates a handler map.
|
|
192
|
+
*/
|
|
193
|
+
type ErrorHandlers<
|
|
194
|
+
E,
|
|
195
|
+
R
|
|
196
|
+
> = { [K in E as K extends {
|
|
197
|
+
readonly code: infer C extends string;
|
|
198
|
+
} ? C : never] : (error: K) => R };
|
|
199
|
+
/**
|
|
200
|
+
* Exhaustive pattern matching on Result errors.
|
|
201
|
+
*
|
|
202
|
+
* Unlike `match()` which gives you a single `err` handler,
|
|
203
|
+
* `matchErr` requires a handler for every error type in the union.
|
|
204
|
+
* The compiler enforces exhaustiveness — add a new error type to the union
|
|
205
|
+
* and every callsite lights up until you handle it.
|
|
206
|
+
*
|
|
207
|
+
* Errors are discriminated by their `code` string literal field.
|
|
208
|
+
*
|
|
209
|
+
* @example
|
|
210
|
+
* const response = matchErr(result, {
|
|
211
|
+
* ok: (user) => json({ data: user }, 201),
|
|
212
|
+
* NOT_FOUND: (e) => json({ error: 'NOT_FOUND', message: e.message }, 404),
|
|
213
|
+
* UNIQUE_VIOLATION: (e) => json({ error: 'EMAIL_EXISTS', field: e.column }, 409),
|
|
214
|
+
* });
|
|
215
|
+
* // ^ Compile error if error union adds a new member you didn't handle
|
|
216
|
+
*
|
|
217
|
+
* @throws Error if an error code is not handled
|
|
218
|
+
*/
|
|
219
|
+
declare function matchErr<
|
|
220
|
+
T,
|
|
221
|
+
E extends {
|
|
222
|
+
readonly code: string;
|
|
223
|
+
},
|
|
224
|
+
R
|
|
225
|
+
>(result: Result<T, E>, handlers: {
|
|
226
|
+
ok: (data: T) => R;
|
|
227
|
+
} & ErrorHandlers<E, R>): R;
|
|
228
|
+
/**
|
|
229
|
+
* Type guard for Ok results.
|
|
230
|
+
*
|
|
231
|
+
* @example
|
|
232
|
+
* if (isOk(result)) {
|
|
233
|
+
* console.log(result.data); // TypeScript knows this is T
|
|
234
|
+
* }
|
|
235
|
+
*/
|
|
236
|
+
declare function isOk<
|
|
237
|
+
T,
|
|
238
|
+
E
|
|
239
|
+
>(result: Result<T, E>): result is Ok<T>;
|
|
240
|
+
/**
|
|
241
|
+
* Type guard for Err results.
|
|
242
|
+
*
|
|
243
|
+
* @example
|
|
244
|
+
* if (isErr(result)) {
|
|
245
|
+
* console.log(result.error); // TypeScript knows this is E
|
|
246
|
+
* }
|
|
247
|
+
*/
|
|
248
|
+
declare function isErr<
|
|
249
|
+
T,
|
|
250
|
+
E
|
|
251
|
+
>(result: Result<T, E>): result is Err<E>;
|
|
252
|
+
/**
|
|
253
|
+
* AppError - Base class for custom domain errors thrown by app developers.
|
|
254
|
+
*
|
|
255
|
+
* This is the standard way for app developers to define and throw domain errors.
|
|
256
|
+
* The server boundary catches these errors and maps them to HTTP responses.
|
|
257
|
+
*
|
|
258
|
+
* @example
|
|
259
|
+
* class InsufficientBalanceError extends AppError<'INSUFFICIENT_BALANCE'> {
|
|
260
|
+
* constructor(public readonly required: number, public readonly available: number) {
|
|
261
|
+
* super('INSUFFICIENT_BALANCE', `Need ${required}, have ${available}`);
|
|
262
|
+
* }
|
|
263
|
+
*
|
|
264
|
+
* toJSON() {
|
|
265
|
+
* return { ...super.toJSON(), required: this.required, available: this.available };
|
|
266
|
+
* }
|
|
267
|
+
* }
|
|
268
|
+
*
|
|
269
|
+
* throw new InsufficientBalanceError(500, 50);
|
|
270
|
+
*/
|
|
271
|
+
/**
|
|
272
|
+
* Base class for application domain errors.
|
|
273
|
+
*
|
|
274
|
+
* Extends Error and adds a typed code property for discrimination.
|
|
275
|
+
* Subclasses can add custom fields and override toJSON() for serialization.
|
|
276
|
+
*/
|
|
277
|
+
declare class AppError<C extends string = string> extends Error {
|
|
278
|
+
/**
|
|
279
|
+
* The error code - a string literal for type-safe discrimination.
|
|
280
|
+
*/
|
|
281
|
+
readonly code: C;
|
|
282
|
+
/**
|
|
283
|
+
* Creates a new AppError.
|
|
284
|
+
*
|
|
285
|
+
* @param code - The error code (string literal type)
|
|
286
|
+
* @param message - Human-readable error message
|
|
287
|
+
*/
|
|
288
|
+
constructor(code: C, message: string);
|
|
289
|
+
/**
|
|
290
|
+
* Serializes the error to a plain object for HTTP responses.
|
|
291
|
+
* Override in subclasses to include additional fields.
|
|
292
|
+
*/
|
|
293
|
+
toJSON(): Record<string, unknown>;
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Schema validation errors.
|
|
297
|
+
*
|
|
298
|
+
* These errors are returned when schema validation fails.
|
|
299
|
+
*/
|
|
300
|
+
/**
|
|
301
|
+
* Issue within a validation error.
|
|
302
|
+
*/
|
|
303
|
+
interface ValidationIssue {
|
|
304
|
+
readonly path: readonly (string | number)[];
|
|
305
|
+
readonly message: string;
|
|
306
|
+
readonly code: string;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Validation failed error.
|
|
310
|
+
*
|
|
311
|
+
* Returned when input doesn't match schema expectations.
|
|
312
|
+
*/
|
|
313
|
+
interface ValidationError {
|
|
314
|
+
readonly code: "VALIDATION_FAILED";
|
|
315
|
+
readonly message: string;
|
|
316
|
+
readonly issues: readonly ValidationIssue[];
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Creates a ValidationError.
|
|
320
|
+
*/
|
|
321
|
+
declare function createValidationError(message: string, issues: readonly ValidationIssue[]): ValidationError;
|
|
322
|
+
/**
|
|
323
|
+
* Type guard for ValidationError.
|
|
324
|
+
*/
|
|
325
|
+
declare function isValidationError(error: {
|
|
326
|
+
readonly code: string;
|
|
327
|
+
}): error is ValidationError;
|
|
328
|
+
/**
|
|
329
|
+
* Database domain errors.
|
|
330
|
+
*
|
|
331
|
+
* These errors are returned from DB operations and represent
|
|
332
|
+
* expected runtime failures that require case-by-case handling.
|
|
333
|
+
*/
|
|
334
|
+
/**
|
|
335
|
+
* Record not found error.
|
|
336
|
+
*
|
|
337
|
+
* Returned when a required record is not found (from findOneRequired).
|
|
338
|
+
* Note: findOne() returns Result<T | null, never> - null is success.
|
|
339
|
+
*/
|
|
340
|
+
interface NotFoundError {
|
|
341
|
+
readonly code: "NOT_FOUND";
|
|
342
|
+
readonly message: string;
|
|
343
|
+
readonly table: string;
|
|
344
|
+
readonly key?: Record<string, unknown>;
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Creates a NotFoundError.
|
|
348
|
+
*/
|
|
349
|
+
declare function createNotFoundError(table: string, key?: Record<string, unknown>): NotFoundError;
|
|
350
|
+
/**
|
|
351
|
+
* Type guard for NotFoundError.
|
|
352
|
+
*/
|
|
353
|
+
declare function isNotFoundError(error: {
|
|
354
|
+
readonly code: string;
|
|
355
|
+
}): error is NotFoundError;
|
|
356
|
+
/**
|
|
357
|
+
* Union type for all read errors.
|
|
358
|
+
*/
|
|
359
|
+
type ReadError = NotFoundError;
|
|
360
|
+
/**
|
|
361
|
+
* Unique constraint violation.
|
|
362
|
+
*
|
|
363
|
+
* Returned when inserting/updating a record would violate a unique constraint.
|
|
364
|
+
*/
|
|
365
|
+
interface UniqueViolation {
|
|
366
|
+
readonly code: "UNIQUE_VIOLATION";
|
|
367
|
+
readonly message: string;
|
|
368
|
+
readonly constraint?: string;
|
|
369
|
+
readonly table?: string;
|
|
370
|
+
readonly column?: string;
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Creates a UniqueViolation error.
|
|
374
|
+
*/
|
|
375
|
+
declare function createUniqueViolation(message: string, options?: {
|
|
376
|
+
constraint?: string;
|
|
377
|
+
table?: string;
|
|
378
|
+
column?: string;
|
|
379
|
+
}): UniqueViolation;
|
|
380
|
+
/**
|
|
381
|
+
* Type guard for UniqueViolation.
|
|
382
|
+
*/
|
|
383
|
+
declare function isUniqueViolation(error: {
|
|
384
|
+
readonly code: string;
|
|
385
|
+
}): error is UniqueViolation;
|
|
386
|
+
/**
|
|
387
|
+
* Foreign key violation.
|
|
388
|
+
*
|
|
389
|
+
* Returned when inserting/updating a record references a non-existent record.
|
|
390
|
+
*/
|
|
391
|
+
interface FKViolation {
|
|
392
|
+
readonly code: "FK_VIOLATION";
|
|
393
|
+
readonly message: string;
|
|
394
|
+
readonly constraint?: string;
|
|
395
|
+
readonly table?: string;
|
|
396
|
+
readonly column?: string;
|
|
397
|
+
readonly referencedTable?: string;
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Creates a FKViolation error.
|
|
401
|
+
*/
|
|
402
|
+
declare function createFKViolation(message: string, options?: {
|
|
403
|
+
constraint?: string;
|
|
404
|
+
table?: string;
|
|
405
|
+
column?: string;
|
|
406
|
+
referencedTable?: string;
|
|
407
|
+
}): FKViolation;
|
|
408
|
+
/**
|
|
409
|
+
* Type guard for FKViolation.
|
|
410
|
+
*/
|
|
411
|
+
declare function isFKViolation(error: {
|
|
412
|
+
readonly code: string;
|
|
413
|
+
}): error is FKViolation;
|
|
414
|
+
/**
|
|
415
|
+
* Not null violation.
|
|
416
|
+
*
|
|
417
|
+
* Returned when inserting/updating a record would violate a NOT NULL constraint.
|
|
418
|
+
*/
|
|
419
|
+
interface NotNullViolation {
|
|
420
|
+
readonly code: "NOT_NULL_VIOLATION";
|
|
421
|
+
readonly message: string;
|
|
422
|
+
readonly table?: string;
|
|
423
|
+
readonly column?: string;
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Creates a NotNullViolation error.
|
|
427
|
+
*/
|
|
428
|
+
declare function createNotNullViolation(message: string, options?: {
|
|
429
|
+
table?: string;
|
|
430
|
+
column?: string;
|
|
431
|
+
}): NotNullViolation;
|
|
432
|
+
/**
|
|
433
|
+
* Type guard for NotNullViolation.
|
|
434
|
+
*/
|
|
435
|
+
declare function isNotNullViolation(error: {
|
|
436
|
+
readonly code: string;
|
|
437
|
+
}): error is NotNullViolation;
|
|
438
|
+
/**
|
|
439
|
+
* Check constraint violation.
|
|
440
|
+
*
|
|
441
|
+
* Returned when inserting/updating a record would violate a CHECK constraint.
|
|
442
|
+
*/
|
|
443
|
+
interface CheckViolation {
|
|
444
|
+
readonly code: "CHECK_VIOLATION";
|
|
445
|
+
readonly message: string;
|
|
446
|
+
readonly constraint?: string;
|
|
447
|
+
readonly table?: string;
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Creates a CheckViolation error.
|
|
451
|
+
*/
|
|
452
|
+
declare function createCheckViolation(message: string, options?: {
|
|
453
|
+
constraint?: string;
|
|
454
|
+
table?: string;
|
|
455
|
+
}): CheckViolation;
|
|
456
|
+
/**
|
|
457
|
+
* Type guard for CheckViolation.
|
|
458
|
+
*/
|
|
459
|
+
declare function isCheckViolation(error: {
|
|
460
|
+
readonly code: string;
|
|
461
|
+
}): error is CheckViolation;
|
|
462
|
+
/**
|
|
463
|
+
* Union type for all write errors.
|
|
464
|
+
*/
|
|
465
|
+
type WriteError = UniqueViolation | FKViolation | NotNullViolation | CheckViolation;
|
|
466
|
+
/**
|
|
467
|
+
* Authentication and authorization domain errors.
|
|
468
|
+
*
|
|
469
|
+
* These errors are returned from auth operations.
|
|
470
|
+
*/
|
|
471
|
+
/**
|
|
472
|
+
* Invalid credentials error.
|
|
473
|
+
*
|
|
474
|
+
* Returned when authentication fails due to wrong email/password.
|
|
475
|
+
*/
|
|
476
|
+
interface InvalidCredentialsError {
|
|
477
|
+
readonly code: "INVALID_CREDENTIALS";
|
|
478
|
+
readonly message: string;
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Creates an InvalidCredentialsError.
|
|
482
|
+
*/
|
|
483
|
+
declare function createInvalidCredentialsError(message?: string): InvalidCredentialsError;
|
|
484
|
+
/**
|
|
485
|
+
* Type guard for InvalidCredentialsError.
|
|
486
|
+
*/
|
|
487
|
+
declare function isInvalidCredentialsError(error: {
|
|
488
|
+
readonly code: string;
|
|
489
|
+
}): error is InvalidCredentialsError;
|
|
490
|
+
/**
|
|
491
|
+
* User already exists error.
|
|
492
|
+
*
|
|
493
|
+
* Returned when attempting to sign up with an existing email.
|
|
494
|
+
*/
|
|
495
|
+
interface UserExistsError {
|
|
496
|
+
readonly code: "USER_EXISTS";
|
|
497
|
+
readonly message: string;
|
|
498
|
+
readonly email?: string;
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Creates a UserExistsError.
|
|
502
|
+
*/
|
|
503
|
+
declare function createUserExistsError(message?: string, email?: string): UserExistsError;
|
|
504
|
+
/**
|
|
505
|
+
* Type guard for UserExistsError.
|
|
506
|
+
*/
|
|
507
|
+
declare function isUserExistsError(error: {
|
|
508
|
+
readonly code: string;
|
|
509
|
+
}): error is UserExistsError;
|
|
510
|
+
/**
|
|
511
|
+
* Session expired error.
|
|
512
|
+
*
|
|
513
|
+
* Returned when a token is no longer valid (expired, revoked, etc.).
|
|
514
|
+
*/
|
|
515
|
+
interface SessionExpiredError {
|
|
516
|
+
readonly code: "SESSION_EXPIRED";
|
|
517
|
+
readonly message: string;
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Creates a SessionExpiredError.
|
|
521
|
+
*/
|
|
522
|
+
declare function createSessionExpiredError(message?: string): SessionExpiredError;
|
|
523
|
+
/**
|
|
524
|
+
* Type guard for SessionExpiredError.
|
|
525
|
+
*/
|
|
526
|
+
declare function isSessionExpiredError(error: {
|
|
527
|
+
readonly code: string;
|
|
528
|
+
}): error is SessionExpiredError;
|
|
529
|
+
/**
|
|
530
|
+
* Permission denied error.
|
|
531
|
+
*
|
|
532
|
+
* Returned when authenticated but not authorized to perform an action.
|
|
533
|
+
*/
|
|
534
|
+
interface PermissionDeniedError {
|
|
535
|
+
readonly code: "PERMISSION_DENIED";
|
|
536
|
+
readonly message: string;
|
|
537
|
+
readonly resource?: string;
|
|
538
|
+
readonly action?: string;
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Creates a PermissionDeniedError.
|
|
542
|
+
*/
|
|
543
|
+
declare function createPermissionDeniedError(message?: string, options?: {
|
|
544
|
+
resource?: string;
|
|
545
|
+
action?: string;
|
|
546
|
+
}): PermissionDeniedError;
|
|
547
|
+
/**
|
|
548
|
+
* Type guard for PermissionDeniedError.
|
|
549
|
+
*/
|
|
550
|
+
declare function isPermissionDeniedError(error: {
|
|
551
|
+
readonly code: string;
|
|
552
|
+
}): error is PermissionDeniedError;
|
|
553
|
+
/**
|
|
554
|
+
* Rate limited error.
|
|
555
|
+
*
|
|
556
|
+
* Returned when too many attempts have been made.
|
|
557
|
+
*/
|
|
558
|
+
interface RateLimitedError {
|
|
559
|
+
readonly code: "RATE_LIMITED";
|
|
560
|
+
readonly message: string;
|
|
561
|
+
readonly retryAfter?: number;
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* Creates a RateLimitedError.
|
|
565
|
+
*/
|
|
566
|
+
declare function createRateLimitedError(message?: string, retryAfter?: number): RateLimitedError;
|
|
567
|
+
/**
|
|
568
|
+
* Type guard for RateLimitedError.
|
|
569
|
+
*/
|
|
570
|
+
declare function isRateLimitedError(error: {
|
|
571
|
+
readonly code: string;
|
|
572
|
+
}): error is RateLimitedError;
|
|
573
|
+
/**
|
|
574
|
+
* Union type for all auth errors.
|
|
575
|
+
*/
|
|
576
|
+
type AuthError = InvalidCredentialsError | UserExistsError | SessionExpiredError | PermissionDeniedError | RateLimitedError;
|
|
577
|
+
/**
|
|
578
|
+
* Client domain errors.
|
|
579
|
+
*
|
|
580
|
+
* These errors are used at the HTTP boundary and use client-native vocabulary
|
|
581
|
+
* (not DB internals). They map from server HTTP responses.
|
|
582
|
+
*/
|
|
583
|
+
/**
|
|
584
|
+
* Validation error (client-facing).
|
|
585
|
+
*
|
|
586
|
+
* Maps from server's VALIDATION_FAILED.
|
|
587
|
+
*/
|
|
588
|
+
interface ValidationError2 {
|
|
589
|
+
readonly code: "VALIDATION_ERROR";
|
|
590
|
+
readonly message: string;
|
|
591
|
+
readonly issues?: readonly {
|
|
592
|
+
readonly path: readonly (string | number)[];
|
|
593
|
+
readonly message: string;
|
|
594
|
+
readonly code: string;
|
|
595
|
+
}[];
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Creates a ValidationError.
|
|
599
|
+
*/
|
|
600
|
+
declare function createValidationError2(message: string, issues?: readonly {
|
|
601
|
+
readonly path: readonly (string | number)[];
|
|
602
|
+
readonly message: string;
|
|
603
|
+
readonly code: string;
|
|
604
|
+
}[]): ValidationError2;
|
|
605
|
+
/**
|
|
606
|
+
* Type guard for ValidationError.
|
|
607
|
+
*/
|
|
608
|
+
declare function isValidationError2(error: {
|
|
609
|
+
readonly code: string;
|
|
610
|
+
}): error is ValidationError2;
|
|
611
|
+
/**
|
|
612
|
+
* Not found error (client-facing).
|
|
613
|
+
*
|
|
614
|
+
* Maps from server's NOT_FOUND.
|
|
615
|
+
*/
|
|
616
|
+
interface NotFoundError2 {
|
|
617
|
+
readonly code: "NOT_FOUND";
|
|
618
|
+
readonly message: string;
|
|
619
|
+
readonly resource?: string;
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Creates a NotFoundError.
|
|
623
|
+
*/
|
|
624
|
+
declare function createNotFoundError2(message?: string, resource?: string): NotFoundError2;
|
|
625
|
+
/**
|
|
626
|
+
* Type guard for NotFoundError.
|
|
627
|
+
*/
|
|
628
|
+
declare function isNotFoundError2(error: {
|
|
629
|
+
readonly code: string;
|
|
630
|
+
}): error is NotFoundError2;
|
|
631
|
+
/**
|
|
632
|
+
* Conflict error (client-facing).
|
|
633
|
+
*
|
|
634
|
+
* Maps from server's UNIQUE_VIOLATION.
|
|
635
|
+
*/
|
|
636
|
+
interface ConflictError {
|
|
637
|
+
readonly code: "CONFLICT";
|
|
638
|
+
readonly message: string;
|
|
639
|
+
readonly field?: string;
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Creates a ConflictError.
|
|
643
|
+
*/
|
|
644
|
+
declare function createConflictError(message?: string, field?: string): ConflictError;
|
|
645
|
+
/**
|
|
646
|
+
* Type guard for ConflictError.
|
|
647
|
+
*/
|
|
648
|
+
declare function isConflictError(error: {
|
|
649
|
+
readonly code: string;
|
|
650
|
+
}): error is ConflictError;
|
|
651
|
+
/**
|
|
652
|
+
* Unauthorized error.
|
|
653
|
+
*
|
|
654
|
+
* Returned when the user is not authenticated.
|
|
655
|
+
*/
|
|
656
|
+
interface UnauthorizedError {
|
|
657
|
+
readonly code: "UNAUTHORIZED";
|
|
658
|
+
readonly message: string;
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* Creates an UnauthorizedError.
|
|
662
|
+
*/
|
|
663
|
+
declare function createUnauthorizedError(message?: string): UnauthorizedError;
|
|
664
|
+
/**
|
|
665
|
+
* Type guard for UnauthorizedError.
|
|
666
|
+
*/
|
|
667
|
+
declare function isUnauthorizedError(error: {
|
|
668
|
+
readonly code: string;
|
|
669
|
+
}): error is UnauthorizedError;
|
|
670
|
+
/**
|
|
671
|
+
* Forbidden error.
|
|
672
|
+
*
|
|
673
|
+
* Returned when the user is authenticated but not authorized.
|
|
674
|
+
*/
|
|
675
|
+
interface ForbiddenError {
|
|
676
|
+
readonly code: "FORBIDDEN";
|
|
677
|
+
readonly message: string;
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* Creates a ForbiddenError.
|
|
681
|
+
*/
|
|
682
|
+
declare function createForbiddenError(message?: string): ForbiddenError;
|
|
683
|
+
/**
|
|
684
|
+
* Type guard for ForbiddenError.
|
|
685
|
+
*/
|
|
686
|
+
declare function isForbiddenError(error: {
|
|
687
|
+
readonly code: string;
|
|
688
|
+
}): error is ForbiddenError;
|
|
689
|
+
/**
|
|
690
|
+
* Rate limited error (client-facing).
|
|
691
|
+
*
|
|
692
|
+
* Maps from server's RATE_LIMITED.
|
|
693
|
+
*/
|
|
694
|
+
interface RateLimitedError2 {
|
|
695
|
+
readonly code: "RATE_LIMITED";
|
|
696
|
+
readonly message: string;
|
|
697
|
+
readonly retryAfter?: number;
|
|
698
|
+
}
|
|
699
|
+
/**
|
|
700
|
+
* Creates a RateLimitedError.
|
|
701
|
+
*/
|
|
702
|
+
declare function createRateLimitedError2(message?: string, retryAfter?: number): RateLimitedError2;
|
|
703
|
+
/**
|
|
704
|
+
* Type guard for RateLimitedError.
|
|
705
|
+
*/
|
|
706
|
+
declare function isRateLimitedError2(error: {
|
|
707
|
+
readonly code: string;
|
|
708
|
+
}): error is RateLimitedError2;
|
|
709
|
+
/**
|
|
710
|
+
* Union type for all client errors.
|
|
711
|
+
*
|
|
712
|
+
* These are the errors that clients receive from API calls.
|
|
713
|
+
*/
|
|
714
|
+
type ApiError = ValidationError2 | NotFoundError2 | ConflictError | UnauthorizedError | ForbiddenError | RateLimitedError2;
|
|
715
|
+
/**
|
|
716
|
+
* Infrastructure errors.
|
|
717
|
+
*
|
|
718
|
+
* These are operational failures that the application developer never handles
|
|
719
|
+
* in business logic. They're caught by global middleware/error boundaries
|
|
720
|
+
* and result in 500/503 responses.
|
|
721
|
+
*/
|
|
722
|
+
/**
|
|
723
|
+
* Base class for infrastructure errors.
|
|
724
|
+
*/
|
|
725
|
+
declare class InfraError extends Error {
|
|
726
|
+
constructor(message: string);
|
|
727
|
+
}
|
|
728
|
+
/**
|
|
729
|
+
* Database connection error.
|
|
730
|
+
*
|
|
731
|
+
* Thrown when the database is unreachable.
|
|
732
|
+
*/
|
|
733
|
+
declare class ConnectionError extends InfraError {
|
|
734
|
+
constructor(message?: string);
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* Connection pool exhausted error.
|
|
738
|
+
*
|
|
739
|
+
* Thrown when no connections are available in the pool.
|
|
740
|
+
*/
|
|
741
|
+
declare class PoolExhaustedError extends InfraError {
|
|
742
|
+
constructor(message?: string);
|
|
743
|
+
}
|
|
744
|
+
/**
|
|
745
|
+
* Query error.
|
|
746
|
+
*
|
|
747
|
+
* Thrown when a query fails (malformed, syntax error, etc.).
|
|
748
|
+
*/
|
|
749
|
+
declare class QueryError extends InfraError {
|
|
750
|
+
constructor(message?: string);
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* Timeout error.
|
|
754
|
+
*
|
|
755
|
+
* Thrown when an operation takes too long.
|
|
756
|
+
*/
|
|
757
|
+
declare class TimeoutError extends InfraError {
|
|
758
|
+
constructor(message?: string);
|
|
759
|
+
}
|
|
760
|
+
/**
|
|
761
|
+
* Network error.
|
|
762
|
+
*
|
|
763
|
+
* Thrown when HTTP client can't reach the server.
|
|
764
|
+
*/
|
|
765
|
+
declare class NetworkError extends InfraError {
|
|
766
|
+
constructor(message?: string);
|
|
767
|
+
}
|
|
768
|
+
/**
|
|
769
|
+
* Serialization error.
|
|
770
|
+
*
|
|
771
|
+
* Thrown when response couldn't be decoded.
|
|
772
|
+
*/
|
|
773
|
+
declare class SerializationError extends InfraError {
|
|
774
|
+
constructor(message?: string);
|
|
775
|
+
}
|
|
776
|
+
/**
|
|
777
|
+
* Union type for all infrastructure errors.
|
|
778
|
+
*/
|
|
779
|
+
type InfraErrors = ConnectionError | PoolExhaustedError | QueryError | TimeoutError | NetworkError | SerializationError;
|
|
780
|
+
/**
|
|
781
|
+
* Maps a database error to an HTTP status code.
|
|
782
|
+
*
|
|
783
|
+
* @param error - A database domain error
|
|
784
|
+
* @returns HTTP status code
|
|
785
|
+
*
|
|
786
|
+
* @example
|
|
787
|
+
* const status = dbErrorToHttpStatus(error);
|
|
788
|
+
* // NOT_FOUND → 404
|
|
789
|
+
* // UNIQUE_VIOLATION → 409
|
|
790
|
+
* // FK_VIOLATION → 422
|
|
791
|
+
* // NOT_NULL_VIOLATION → 422
|
|
792
|
+
* // CHECK_VIOLATION → 422
|
|
793
|
+
*/
|
|
794
|
+
declare function dbErrorToHttpStatus(error: ReadError | WriteError): number;
|
|
795
|
+
/**
|
|
796
|
+
* Maps a NotFoundError to HTTP status.
|
|
797
|
+
*/
|
|
798
|
+
declare function notFoundErrorToHttpStatus(_error: NotFoundError): number;
|
|
799
|
+
/**
|
|
800
|
+
* Maps a UniqueViolation to HTTP status.
|
|
801
|
+
*/
|
|
802
|
+
declare function uniqueViolationToHttpStatus(_error: UniqueViolation): number;
|
|
803
|
+
/**
|
|
804
|
+
* Maps a FKViolation to HTTP status.
|
|
805
|
+
*/
|
|
806
|
+
declare function fkViolationToHttpStatus(_error: FKViolation): number;
|
|
807
|
+
/**
|
|
808
|
+
* Maps a NotNullViolation to HTTP status.
|
|
809
|
+
*/
|
|
810
|
+
declare function notNullViolationToHttpStatus(_error: NotNullViolation): number;
|
|
811
|
+
/**
|
|
812
|
+
* Maps a CheckViolation to HTTP status.
|
|
813
|
+
*/
|
|
814
|
+
declare function checkViolationToHttpStatus(_error: CheckViolation): number;
|
|
815
|
+
/**
|
|
816
|
+
* Unknown error response from server.
|
|
817
|
+
*/
|
|
818
|
+
interface UnknownError {
|
|
819
|
+
readonly code: "UNKNOWN";
|
|
820
|
+
readonly message: string;
|
|
821
|
+
readonly status: number;
|
|
822
|
+
}
|
|
823
|
+
/**
|
|
824
|
+
* Maps an HTTP response to a client domain error.
|
|
825
|
+
*
|
|
826
|
+
* @param status - HTTP status code
|
|
827
|
+
* @param body - Response body
|
|
828
|
+
* @returns Client domain error
|
|
829
|
+
*
|
|
830
|
+
* @example
|
|
831
|
+
* const error = httpToClientError(404, { message: 'User not found' });
|
|
832
|
+
* // { code: 'NOT_FOUND', message: 'User not found', resource: 'user' }
|
|
833
|
+
*/
|
|
834
|
+
declare function httpToClientError(status: number, body: unknown): ApiError | UnknownError;
|
|
835
|
+
/**
|
|
836
|
+
* Checks if an error is an UnknownError.
|
|
837
|
+
*/
|
|
838
|
+
declare function isUnknownError(error: ApiError | UnknownError): error is UnknownError;
|
|
839
|
+
export { unwrapOr, unwrap, uniqueViolationToHttpStatus, ok, notNullViolationToHttpStatus, notFoundErrorToHttpStatus, matchErr, match, map, isUserExistsError, isUnknownError, isUniqueViolation, isUnauthorizedError, isSessionExpiredError, isValidationError as isSchemaValidationError, isPermissionDeniedError, isOk, isNotNullViolation, isInvalidCredentialsError, isForbiddenError, isFKViolation, isErr, isNotFoundError as isDBNotFoundError, isConflictError, isValidationError2 as isClientValidationError, isRateLimitedError2 as isClientRateLimitedError, isNotFoundError2 as isClientNotFoundError, isCheckViolation, isRateLimitedError as isAuthRateLimitedError, httpToClientError, flatMap, fkViolationToHttpStatus, err, dbErrorToHttpStatus, createUserExistsError, createUniqueViolation, createUnauthorizedError, createSessionExpiredError, createValidationError as createSchemaValidationError, createPermissionDeniedError, createNotNullViolation, createInvalidCredentialsError, createForbiddenError, createFKViolation, createNotFoundError as createDBNotFoundError, createConflictError, createValidationError2 as createClientValidationError, createRateLimitedError2 as createClientRateLimitedError, createNotFoundError2 as createClientNotFoundError, createCheckViolation, createRateLimitedError as createAuthRateLimitedError, checkViolationToHttpStatus, WriteError, ValidationIssue, UserExistsError, UnknownError, UniqueViolation, UnauthorizedError, TimeoutError, SessionExpiredError, SerializationError, ValidationError as SchemaValidationError, Result, ReadError, QueryError, PoolExhaustedError, PermissionDeniedError, Ok, NotNullViolation, NetworkError, InvalidCredentialsError, InfraErrors, InfraError, ForbiddenError, FKViolation, Err, NotFoundError as DBNotFoundError, ConnectionError, ConflictError, ValidationError2 as ClientValidationError, RateLimitedError2 as ClientRateLimitedError, NotFoundError2 as ClientNotFoundError, CheckViolation, RateLimitedError as AuthRateLimitedError, AuthError, AppError, ApiError };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
// src/result.ts
|
|
2
|
+
var ok = (data) => ({ ok: true, data });
|
|
3
|
+
var err = (error) => ({ ok: false, error });
|
|
4
|
+
function unwrap(result) {
|
|
5
|
+
if (result.ok) {
|
|
6
|
+
return result.data;
|
|
7
|
+
}
|
|
8
|
+
throw result.error;
|
|
9
|
+
}
|
|
10
|
+
function unwrapOr(result, defaultValue) {
|
|
11
|
+
if (result.ok) {
|
|
12
|
+
return result.data;
|
|
13
|
+
}
|
|
14
|
+
return defaultValue;
|
|
15
|
+
}
|
|
16
|
+
function map(result, fn) {
|
|
17
|
+
if (result.ok) {
|
|
18
|
+
return { ok: true, data: fn(result.data) };
|
|
19
|
+
}
|
|
20
|
+
return result;
|
|
21
|
+
}
|
|
22
|
+
function flatMap(result, fn) {
|
|
23
|
+
if (result.ok) {
|
|
24
|
+
return fn(result.data);
|
|
25
|
+
}
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
function match(result, handlers) {
|
|
29
|
+
return result.ok ? handlers.ok(result.data) : handlers.err(result.error);
|
|
30
|
+
}
|
|
31
|
+
function matchErr(result, handlers) {
|
|
32
|
+
if (result.ok) {
|
|
33
|
+
return handlers.ok(result.data);
|
|
34
|
+
}
|
|
35
|
+
const errorCode = result.error.code;
|
|
36
|
+
const handlersRecord = handlers;
|
|
37
|
+
const handler = handlersRecord[errorCode];
|
|
38
|
+
if (!handler) {
|
|
39
|
+
throw new Error(`Unhandled error code: ${errorCode}`);
|
|
40
|
+
}
|
|
41
|
+
return handler(result.error);
|
|
42
|
+
}
|
|
43
|
+
function isOk(result) {
|
|
44
|
+
return result.ok === true;
|
|
45
|
+
}
|
|
46
|
+
function isErr(result) {
|
|
47
|
+
return result.ok === false;
|
|
48
|
+
}
|
|
49
|
+
// src/app-error.ts
|
|
50
|
+
class AppError extends Error {
|
|
51
|
+
code;
|
|
52
|
+
constructor(code, message) {
|
|
53
|
+
super(message);
|
|
54
|
+
this.code = code;
|
|
55
|
+
this.name = "AppError";
|
|
56
|
+
}
|
|
57
|
+
toJSON() {
|
|
58
|
+
return {
|
|
59
|
+
code: this.code,
|
|
60
|
+
message: this.message
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// src/domain/schema.ts
|
|
65
|
+
function createValidationError(message, issues) {
|
|
66
|
+
return {
|
|
67
|
+
code: "VALIDATION_FAILED",
|
|
68
|
+
message,
|
|
69
|
+
issues
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function isValidationError(error) {
|
|
73
|
+
return error.code === "VALIDATION_FAILED";
|
|
74
|
+
}
|
|
75
|
+
// src/domain/db.ts
|
|
76
|
+
function createNotFoundError(table, key) {
|
|
77
|
+
const keyStr = key ? JSON.stringify(key) : "";
|
|
78
|
+
return {
|
|
79
|
+
code: "NOT_FOUND",
|
|
80
|
+
message: `Record not found in ${table}${keyStr ? `: ${keyStr}` : ""}`,
|
|
81
|
+
table,
|
|
82
|
+
key
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function isNotFoundError(error) {
|
|
86
|
+
return error.code === "NOT_FOUND";
|
|
87
|
+
}
|
|
88
|
+
function createUniqueViolation(message, options) {
|
|
89
|
+
return {
|
|
90
|
+
code: "UNIQUE_VIOLATION",
|
|
91
|
+
message,
|
|
92
|
+
...options
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function isUniqueViolation(error) {
|
|
96
|
+
return error.code === "UNIQUE_VIOLATION";
|
|
97
|
+
}
|
|
98
|
+
function createFKViolation(message, options) {
|
|
99
|
+
return {
|
|
100
|
+
code: "FK_VIOLATION",
|
|
101
|
+
message,
|
|
102
|
+
...options
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function isFKViolation(error) {
|
|
106
|
+
return error.code === "FK_VIOLATION";
|
|
107
|
+
}
|
|
108
|
+
function createNotNullViolation(message, options) {
|
|
109
|
+
return {
|
|
110
|
+
code: "NOT_NULL_VIOLATION",
|
|
111
|
+
message,
|
|
112
|
+
...options
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function isNotNullViolation(error) {
|
|
116
|
+
return error.code === "NOT_NULL_VIOLATION";
|
|
117
|
+
}
|
|
118
|
+
function createCheckViolation(message, options) {
|
|
119
|
+
return {
|
|
120
|
+
code: "CHECK_VIOLATION",
|
|
121
|
+
message,
|
|
122
|
+
...options
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
function isCheckViolation(error) {
|
|
126
|
+
return error.code === "CHECK_VIOLATION";
|
|
127
|
+
}
|
|
128
|
+
// src/domain/auth.ts
|
|
129
|
+
function createInvalidCredentialsError(message = "Invalid email or password") {
|
|
130
|
+
return {
|
|
131
|
+
code: "INVALID_CREDENTIALS",
|
|
132
|
+
message
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function isInvalidCredentialsError(error) {
|
|
136
|
+
return error.code === "INVALID_CREDENTIALS";
|
|
137
|
+
}
|
|
138
|
+
function createUserExistsError(message = "User already exists", email) {
|
|
139
|
+
return {
|
|
140
|
+
code: "USER_EXISTS",
|
|
141
|
+
message,
|
|
142
|
+
email
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function isUserExistsError(error) {
|
|
146
|
+
return error.code === "USER_EXISTS";
|
|
147
|
+
}
|
|
148
|
+
function createSessionExpiredError(message = "Session has expired") {
|
|
149
|
+
return {
|
|
150
|
+
code: "SESSION_EXPIRED",
|
|
151
|
+
message
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function isSessionExpiredError(error) {
|
|
155
|
+
return error.code === "SESSION_EXPIRED";
|
|
156
|
+
}
|
|
157
|
+
function createPermissionDeniedError(message = "Permission denied", options) {
|
|
158
|
+
return {
|
|
159
|
+
code: "PERMISSION_DENIED",
|
|
160
|
+
message,
|
|
161
|
+
...options
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
function isPermissionDeniedError(error) {
|
|
165
|
+
return error.code === "PERMISSION_DENIED";
|
|
166
|
+
}
|
|
167
|
+
function createRateLimitedError(message = "Too many attempts, please try again later", retryAfter) {
|
|
168
|
+
return {
|
|
169
|
+
code: "RATE_LIMITED",
|
|
170
|
+
message,
|
|
171
|
+
retryAfter
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
function isRateLimitedError(error) {
|
|
175
|
+
return error.code === "RATE_LIMITED";
|
|
176
|
+
}
|
|
177
|
+
// src/domain/client.ts
|
|
178
|
+
function createValidationError2(message, issues) {
|
|
179
|
+
return {
|
|
180
|
+
code: "VALIDATION_ERROR",
|
|
181
|
+
message,
|
|
182
|
+
issues
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
function isValidationError2(error) {
|
|
186
|
+
return error.code === "VALIDATION_ERROR";
|
|
187
|
+
}
|
|
188
|
+
function createNotFoundError2(message = "Resource not found", resource) {
|
|
189
|
+
return {
|
|
190
|
+
code: "NOT_FOUND",
|
|
191
|
+
message,
|
|
192
|
+
resource
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
function isNotFoundError2(error) {
|
|
196
|
+
return error.code === "NOT_FOUND";
|
|
197
|
+
}
|
|
198
|
+
function createConflictError(message = "Resource conflict", field) {
|
|
199
|
+
return {
|
|
200
|
+
code: "CONFLICT",
|
|
201
|
+
message,
|
|
202
|
+
field
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
function isConflictError(error) {
|
|
206
|
+
return error.code === "CONFLICT";
|
|
207
|
+
}
|
|
208
|
+
function createUnauthorizedError(message = "Authentication required") {
|
|
209
|
+
return {
|
|
210
|
+
code: "UNAUTHORIZED",
|
|
211
|
+
message
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
function isUnauthorizedError(error) {
|
|
215
|
+
return error.code === "UNAUTHORIZED";
|
|
216
|
+
}
|
|
217
|
+
function createForbiddenError(message = "Access denied") {
|
|
218
|
+
return {
|
|
219
|
+
code: "FORBIDDEN",
|
|
220
|
+
message
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
function isForbiddenError(error) {
|
|
224
|
+
return error.code === "FORBIDDEN";
|
|
225
|
+
}
|
|
226
|
+
function createRateLimitedError2(message = "Too many requests", retryAfter) {
|
|
227
|
+
return {
|
|
228
|
+
code: "RATE_LIMITED",
|
|
229
|
+
message,
|
|
230
|
+
retryAfter
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
function isRateLimitedError2(error) {
|
|
234
|
+
return error.code === "RATE_LIMITED";
|
|
235
|
+
}
|
|
236
|
+
// src/infra/index.ts
|
|
237
|
+
class InfraError extends Error {
|
|
238
|
+
constructor(message) {
|
|
239
|
+
super(message);
|
|
240
|
+
this.name = this.constructor.name;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
class ConnectionError extends InfraError {
|
|
245
|
+
constructor(message = "Database connection failed") {
|
|
246
|
+
super(message);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
class PoolExhaustedError extends InfraError {
|
|
251
|
+
constructor(message = "Database pool exhausted") {
|
|
252
|
+
super(message);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
class QueryError extends InfraError {
|
|
257
|
+
constructor(message = "Query execution failed") {
|
|
258
|
+
super(message);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
class TimeoutError extends InfraError {
|
|
263
|
+
constructor(message = "Operation timed out") {
|
|
264
|
+
super(message);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
class NetworkError extends InfraError {
|
|
269
|
+
constructor(message = "Network request failed") {
|
|
270
|
+
super(message);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
class SerializationError extends InfraError {
|
|
275
|
+
constructor(message = "Failed to decode response") {
|
|
276
|
+
super(message);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
// src/mapping/db-to-http.ts
|
|
280
|
+
function dbErrorToHttpStatus(error) {
|
|
281
|
+
const code = error.code;
|
|
282
|
+
switch (code) {
|
|
283
|
+
case "NOT_FOUND":
|
|
284
|
+
return 404;
|
|
285
|
+
case "UNIQUE_VIOLATION":
|
|
286
|
+
return 409;
|
|
287
|
+
case "FK_VIOLATION":
|
|
288
|
+
return 422;
|
|
289
|
+
case "NOT_NULL_VIOLATION":
|
|
290
|
+
return 422;
|
|
291
|
+
case "CHECK_VIOLATION":
|
|
292
|
+
return 422;
|
|
293
|
+
default:
|
|
294
|
+
return 500;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
function notFoundErrorToHttpStatus(_error) {
|
|
298
|
+
return 404;
|
|
299
|
+
}
|
|
300
|
+
function uniqueViolationToHttpStatus(_error) {
|
|
301
|
+
return 409;
|
|
302
|
+
}
|
|
303
|
+
function fkViolationToHttpStatus(_error) {
|
|
304
|
+
return 422;
|
|
305
|
+
}
|
|
306
|
+
function notNullViolationToHttpStatus(_error) {
|
|
307
|
+
return 422;
|
|
308
|
+
}
|
|
309
|
+
function checkViolationToHttpStatus(_error) {
|
|
310
|
+
return 422;
|
|
311
|
+
}
|
|
312
|
+
// src/mapping/http-to-client.ts
|
|
313
|
+
function parseUnknownError(status, body) {
|
|
314
|
+
const message = typeof body === "object" && body !== null && "message" in body ? String(body.message) : "Request failed";
|
|
315
|
+
return {
|
|
316
|
+
code: "UNKNOWN",
|
|
317
|
+
message,
|
|
318
|
+
status
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
function httpToClientError(status, body) {
|
|
322
|
+
if (body === null || body === undefined || body === "") {
|
|
323
|
+
return parseUnknownError(status, body);
|
|
324
|
+
}
|
|
325
|
+
if (typeof body !== "object") {
|
|
326
|
+
return parseUnknownError(status, body);
|
|
327
|
+
}
|
|
328
|
+
const bodyObj = body;
|
|
329
|
+
const message = typeof bodyObj.message === "string" ? bodyObj.message : "Request failed";
|
|
330
|
+
switch (status) {
|
|
331
|
+
case 400:
|
|
332
|
+
if (bodyObj.code === "VALIDATION_FAILED" || bodyObj.issues) {
|
|
333
|
+
const error = {
|
|
334
|
+
code: "VALIDATION_ERROR",
|
|
335
|
+
message,
|
|
336
|
+
issues: Array.isArray(bodyObj.issues) ? bodyObj.issues : undefined
|
|
337
|
+
};
|
|
338
|
+
return error;
|
|
339
|
+
}
|
|
340
|
+
return parseUnknownError(status, body);
|
|
341
|
+
case 401:
|
|
342
|
+
return {
|
|
343
|
+
code: "UNAUTHORIZED",
|
|
344
|
+
message
|
|
345
|
+
};
|
|
346
|
+
case 403:
|
|
347
|
+
return {
|
|
348
|
+
code: "FORBIDDEN",
|
|
349
|
+
message
|
|
350
|
+
};
|
|
351
|
+
case 404:
|
|
352
|
+
return {
|
|
353
|
+
code: "NOT_FOUND",
|
|
354
|
+
message,
|
|
355
|
+
resource: typeof bodyObj.resource === "string" ? bodyObj.resource : undefined
|
|
356
|
+
};
|
|
357
|
+
case 409:
|
|
358
|
+
return {
|
|
359
|
+
code: "CONFLICT",
|
|
360
|
+
message,
|
|
361
|
+
field: typeof bodyObj.field === "string" ? bodyObj.field : undefined
|
|
362
|
+
};
|
|
363
|
+
case 422:
|
|
364
|
+
if (bodyObj.code === "VALIDATION_FAILED" || bodyObj.issues) {
|
|
365
|
+
const error = {
|
|
366
|
+
code: "VALIDATION_ERROR",
|
|
367
|
+
message,
|
|
368
|
+
issues: Array.isArray(bodyObj.issues) ? bodyObj.issues : undefined
|
|
369
|
+
};
|
|
370
|
+
return error;
|
|
371
|
+
}
|
|
372
|
+
case 429:
|
|
373
|
+
return {
|
|
374
|
+
code: "RATE_LIMITED",
|
|
375
|
+
message,
|
|
376
|
+
retryAfter: typeof bodyObj.retryAfter === "number" ? bodyObj.retryAfter : undefined
|
|
377
|
+
};
|
|
378
|
+
case 500:
|
|
379
|
+
case 502:
|
|
380
|
+
case 503:
|
|
381
|
+
case 504:
|
|
382
|
+
return parseUnknownError(status, body);
|
|
383
|
+
default:
|
|
384
|
+
return parseUnknownError(status, body);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
function isUnknownError(error) {
|
|
388
|
+
return error.code === "UNKNOWN";
|
|
389
|
+
}
|
|
390
|
+
export {
|
|
391
|
+
unwrapOr,
|
|
392
|
+
unwrap,
|
|
393
|
+
uniqueViolationToHttpStatus,
|
|
394
|
+
ok,
|
|
395
|
+
notNullViolationToHttpStatus,
|
|
396
|
+
notFoundErrorToHttpStatus,
|
|
397
|
+
matchErr,
|
|
398
|
+
match,
|
|
399
|
+
map,
|
|
400
|
+
isUserExistsError,
|
|
401
|
+
isUnknownError,
|
|
402
|
+
isUniqueViolation,
|
|
403
|
+
isUnauthorizedError,
|
|
404
|
+
isSessionExpiredError,
|
|
405
|
+
isValidationError as isSchemaValidationError,
|
|
406
|
+
isPermissionDeniedError,
|
|
407
|
+
isOk,
|
|
408
|
+
isNotNullViolation,
|
|
409
|
+
isInvalidCredentialsError,
|
|
410
|
+
isForbiddenError,
|
|
411
|
+
isFKViolation,
|
|
412
|
+
isErr,
|
|
413
|
+
isNotFoundError as isDBNotFoundError,
|
|
414
|
+
isConflictError,
|
|
415
|
+
isValidationError2 as isClientValidationError,
|
|
416
|
+
isRateLimitedError2 as isClientRateLimitedError,
|
|
417
|
+
isNotFoundError2 as isClientNotFoundError,
|
|
418
|
+
isCheckViolation,
|
|
419
|
+
isRateLimitedError as isAuthRateLimitedError,
|
|
420
|
+
httpToClientError,
|
|
421
|
+
flatMap,
|
|
422
|
+
fkViolationToHttpStatus,
|
|
423
|
+
err,
|
|
424
|
+
dbErrorToHttpStatus,
|
|
425
|
+
createUserExistsError,
|
|
426
|
+
createUniqueViolation,
|
|
427
|
+
createUnauthorizedError,
|
|
428
|
+
createSessionExpiredError,
|
|
429
|
+
createValidationError as createSchemaValidationError,
|
|
430
|
+
createPermissionDeniedError,
|
|
431
|
+
createNotNullViolation,
|
|
432
|
+
createInvalidCredentialsError,
|
|
433
|
+
createForbiddenError,
|
|
434
|
+
createFKViolation,
|
|
435
|
+
createNotFoundError as createDBNotFoundError,
|
|
436
|
+
createConflictError,
|
|
437
|
+
createValidationError2 as createClientValidationError,
|
|
438
|
+
createRateLimitedError2 as createClientRateLimitedError,
|
|
439
|
+
createNotFoundError2 as createClientNotFoundError,
|
|
440
|
+
createCheckViolation,
|
|
441
|
+
createRateLimitedError as createAuthRateLimitedError,
|
|
442
|
+
checkViolationToHttpStatus,
|
|
443
|
+
TimeoutError,
|
|
444
|
+
SerializationError,
|
|
445
|
+
QueryError,
|
|
446
|
+
PoolExhaustedError,
|
|
447
|
+
NetworkError,
|
|
448
|
+
InfraError,
|
|
449
|
+
ConnectionError,
|
|
450
|
+
AppError
|
|
451
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vertz/errors",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"description": "Unified error taxonomy for Vertz - Result types, domain errors, and mapping utilities",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/vertz-dev/vertz.git",
|
|
10
|
+
"directory": "packages/errors"
|
|
11
|
+
},
|
|
12
|
+
"publishConfig": {
|
|
13
|
+
"access": "public",
|
|
14
|
+
"provenance": true
|
|
15
|
+
},
|
|
16
|
+
"main": "dist/index.js",
|
|
17
|
+
"types": "dist/index.d.ts",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"import": "./dist/index.js",
|
|
21
|
+
"types": "./dist/index.d.ts"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist"
|
|
26
|
+
],
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "bunup",
|
|
29
|
+
"test": "vitest run",
|
|
30
|
+
"test:watch": "vitest",
|
|
31
|
+
"typecheck": "tsc --noEmit"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
35
|
+
"bunup": "latest",
|
|
36
|
+
"typescript": "^5.7.0",
|
|
37
|
+
"vitest": "^4.0.18"
|
|
38
|
+
},
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=22"
|
|
41
|
+
},
|
|
42
|
+
"sideEffects": false
|
|
43
|
+
}
|