@vertz/errors 0.2.14 → 0.2.16

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.
Files changed (3) hide show
  1. package/dist/index.d.ts +478 -478
  2. package/dist/index.js +259 -259
  3. package/package.json +1 -1
package/dist/index.d.ts CHANGED
@@ -156,13 +156,13 @@ declare function isRateLimitedError(error: {
156
156
  interface AuthValidationError {
157
157
  readonly code: "AUTH_VALIDATION_ERROR";
158
158
  readonly message: string;
159
- readonly field: "email" | "password";
159
+ readonly field: "email" | "password" | "general";
160
160
  readonly constraint?: string;
161
161
  }
162
162
  /**
163
163
  * Creates an AuthValidationError.
164
164
  */
165
- declare function createAuthValidationError(message: string, field: "email" | "password", constraint?: string): AuthValidationError;
165
+ declare function createAuthValidationError(message: string, field: "email" | "password" | "general", constraint?: string): AuthValidationError;
166
166
  /**
167
167
  * Type guard for AuthValidationError.
168
168
  */
@@ -713,380 +713,219 @@ declare function isValidationError2(error: {
713
713
  readonly code: string;
714
714
  }): error is ValidationError3;
715
715
  /**
716
- * Infrastructure errors.
716
+ * Entity error classes.
717
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.
718
+ * These errors mirror server HTTP error codes and are used at the HTTP boundary.
724
719
  */
725
- declare class InfraError extends Error {
726
- constructor(message: string);
727
- }
728
720
  /**
729
- * Database connection error.
721
+ * Base class for entity errors.
730
722
  *
731
- * Thrown when the database is unreachable.
723
+ * @example
724
+ * import { EntityError } from '@ *
725
+ * // Checkvertz/errors';
726
+ error type
727
+ * if (error instanceof EntityError) {
728
+ * console.log(error.code); // e.g., 'NOT_FOUND'
729
+ * console.log(error.message);
730
+ * }
732
731
  */
733
- declare class ConnectionError extends InfraError {
734
- constructor(message?: string);
732
+ declare abstract class EntityError extends Error {
733
+ /**
734
+ * The error code - a string literal for type-safe discrimination.
735
+ */
736
+ readonly code: string;
737
+ constructor(code: string, message: string);
735
738
  }
736
739
  /**
737
- * Connection pool exhausted error.
738
- *
739
- * Thrown when no connections are available in the pool.
740
+ * Bad request error - 400.
740
741
  */
741
- declare class PoolExhaustedError extends InfraError {
742
+ declare class BadRequestError extends EntityError {
743
+ readonly code: "BadRequest";
742
744
  constructor(message?: string);
743
745
  }
744
746
  /**
745
- * Query error.
746
- *
747
- * Thrown when a query fails (malformed, syntax error, etc.).
747
+ * Type guard for BadRequestError.
748
748
  */
749
- declare class QueryError extends InfraError {
750
- constructor(message?: string);
751
- }
749
+ declare function isBadRequestError(error: unknown): error is BadRequestError;
752
750
  /**
753
- * Timeout error.
754
- *
755
- * Thrown when an operation takes too long.
751
+ * Unauthorized error - 401.
756
752
  */
757
- declare class TimeoutError2 extends InfraError {
753
+ declare class EntityUnauthorizedError extends EntityError {
754
+ readonly code: "Unauthorized";
758
755
  constructor(message?: string);
759
756
  }
760
757
  /**
761
- * Network error.
762
- *
763
- * Thrown when HTTP client can't reach the server.
758
+ * Type guard for EntityUnauthorizedError.
764
759
  */
765
- declare class NetworkError2 extends InfraError {
766
- constructor(message?: string);
767
- }
760
+ declare function isEntityUnauthorizedError(error: unknown): error is EntityUnauthorizedError;
768
761
  /**
769
- * Serialization error.
770
- *
771
- * Thrown when response couldn't be decoded.
762
+ * Forbidden error - 403.
772
763
  */
773
- declare class SerializationError extends InfraError {
764
+ declare class EntityForbiddenError extends EntityError {
765
+ readonly code: "Forbidden";
774
766
  constructor(message?: string);
775
767
  }
776
768
  /**
777
- * Union type for all infrastructure errors.
769
+ * Type guard for EntityForbiddenError.
778
770
  */
779
- type InfraErrors = ConnectionError | PoolExhaustedError | QueryError | TimeoutError2 | NetworkError2 | SerializationError;
771
+ declare function isEntityForbiddenError(error: unknown): error is EntityForbiddenError;
780
772
  /**
781
- * Maps a database error to an HTTP status code.
773
+ * Not found error - 404.
782
774
  *
783
- * @param error - A database domain error
784
- * @returns HTTP status code
775
+ * @example
776
+ * // Using in matchError for server-side error handling
777
+ * const result = await db.users.get(userId);
778
+ * if (!result.ok) {
779
+ * return matchError(result.error, {
780
+ * NOT_FOUND: (e) => Response.json(
781
+ * { error: { code: 'NOT_FOUND', message: `User ${userId} not found` } },
782
+ * { status: 404 }
783
+ * ),
784
+ * // ... other handlers
785
+ * });
786
+ * }
785
787
  *
786
788
  * @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: NotFoundError2): 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.
789
+ * // With resource info
790
+ * throw new EntityNotFoundError('User not found', 'User', userId);
817
791
  */
818
- interface UnknownError {
819
- readonly code: "UNKNOWN";
820
- readonly message: string;
821
- readonly status: number;
792
+ declare class EntityNotFoundError extends EntityError {
793
+ readonly code: "NotFound";
794
+ /**
795
+ * The type of resource that wasn't found.
796
+ */
797
+ readonly resource?: string;
798
+ /**
799
+ * The ID of the resource that wasn't found.
800
+ */
801
+ readonly resourceId?: string;
802
+ constructor(message?: string, resource?: string, resourceId?: string);
822
803
  }
823
804
  /**
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' }
805
+ * Type guard for EntityNotFoundError.
833
806
  */
834
- declare function httpToClientError(status: number, body: unknown): ApiError | UnknownError;
807
+ declare function isEntityNotFoundError(error: unknown): error is EntityNotFoundError;
835
808
  /**
836
- * Checks if an error is an UnknownError.
809
+ * Method not allowed error - 405.
837
810
  */
838
- declare function isUnknownError(error: ApiError | UnknownError): error is UnknownError;
811
+ declare class MethodNotAllowedError extends EntityError {
812
+ readonly code: "MethodNotAllowed";
813
+ /**
814
+ * Allowed HTTP methods.
815
+ */
816
+ readonly allowedMethods?: string;
817
+ constructor(allowedMethods?: string, message?: string);
818
+ }
839
819
  /**
840
- * Result type and utilities for errors-as-values pattern.
841
- *
842
- * This module provides a type-safe alternative to throwing exceptions.
843
- * Every operation that can fail returns a Result<T, E> instead of throwing.
844
- *
845
- * @example
846
- * import { ok, err, unwrap, map, flatMap, match, matchErr } from '@vertz/errors';
847
- *
848
- * // Creating results
849
- * const success = ok({ name: 'Alice' });
850
- * const failure = err({ code: 'NOT_FOUND', message: 'User not found' });
851
- *
852
- * // Transforming
853
- * const doubled = map(ok(5), x => x * 2);
854
- *
855
- * // Chaining
856
- * const result = await flatMap(ok(5), async x => ok(x * 2));
857
- *
858
- * // Pattern matching
859
- * const message = match(result, {
860
- * ok: (data) => `Success: ${data}`,
861
- * err: (error) => `Error: ${error.message}`
862
- * });
820
+ * Type guard for MethodNotAllowedError.
863
821
  */
822
+ declare function isMethodNotAllowedError(error: unknown): error is MethodNotAllowedError;
864
823
  /**
865
- * Represents a successful result.
866
- *
867
- * @example
868
- * { ok: true, data: { name: 'Alice' } }
824
+ * Conflict error - 409.
869
825
  */
870
- interface Ok<T> {
871
- /** Always true for successful results */
872
- readonly ok: true;
873
- /** The successful value */
874
- readonly data: T;
826
+ declare class EntityConflictError extends EntityError {
827
+ readonly code: "Conflict";
828
+ /**
829
+ * The field that caused the conflict.
830
+ */
831
+ readonly field?: string;
832
+ constructor(message?: string, field?: string);
875
833
  }
876
834
  /**
877
- * Represents an erroneous result.
878
- *
879
- * @example
880
- * { ok: false, error: { code: 'NOT_FOUND', message: 'User not found' } }
835
+ * Type guard for EntityConflictError.
881
836
  */
882
- interface Err<E> {
883
- /** Always false for error results */
884
- readonly ok: false;
885
- /** The error value */
886
- readonly error: E;
887
- }
837
+ declare function isEntityConflictError(error: unknown): error is EntityConflictError;
888
838
  /**
889
- * A discriminated union representing success or failure.
839
+ * Entity validation error - 422.
890
840
  *
891
841
  * @example
892
- * type UserResult = Result<User, ValidationError>;
893
- * type UsersResult = Result<User[], NotFoundError>;
842
+ * // Server-side: throwing validation errors
843
+ * throw new EntityValidationError([
844
+ * { path: ['email'], message: 'Invalid email format', code: 'INVALID_FORMAT' },
845
+ * { path: ['age'], message: 'Must be positive', code: 'MIN_VALUE' },
846
+ * ]);
894
847
  *
895
848
  * @example
896
- * const result: Result<string, Error> = ok('hello');
897
- * if (result.ok) {
898
- * console.log(result.data); // TypeScript knows this is string
899
- * } else {
900
- * console.log(result.error); // TypeScript knows this is Error
849
+ * // Server-side: handling in HTTP response
850
+ * if (!result.ok) {
851
+ * return matchError(result.error, {
852
+ * ENTITY_VALIDATION_ERROR: (e) => Response.json(
853
+ * { error: { code: 'VALIDATION_ERROR', message: 'Validation failed', errors: e.errors } },
854
+ * { status: 422 }
855
+ * ),
856
+ * // ... other handlers
857
+ * });
901
858
  * }
902
859
  */
903
- type Result<
904
- T,
905
- E = unknown
906
- > = Ok<T> | Err<E>;
907
- /**
908
- * Creates a successful Result.
909
- *
910
- * @example
911
- * const result = ok({ name: 'Alice' });
912
- * // { ok: true, data: { name: 'Alice' } }
913
- *
914
- * @example
915
- * // With type inference
916
- * const result = ok(42); // Ok<number>
917
- */
918
- declare const ok: <T>(data: T) => Ok<T>;
919
- /**
920
- * Creates an error Result.
921
- *
922
- * @example
923
- * const result = err({ code: 'VALIDATION_FAILED', message: 'Invalid email', issues: [] });
924
- * // { ok: false, error: { code: 'VALIDATION_FAILED', ... } }
925
- *
926
- * @example
927
- * // With simple string errors
928
- * const result = err('Something went wrong');
929
- * // { ok: false, error: 'Something went wrong' }
930
- */
931
- declare const err: <E>(error: E) => Err<E>;
932
- /**
933
- * Unwraps a Result, throwing if error.
934
- *
935
- * Use only in tests, scripts, or when failure is truly exceptional.
936
- *
937
- * @example
938
- * // Tests
939
- * const user = unwrap(await repo.findOneRequired(id));
940
- *
941
- * @example
942
- * // Scripts
943
- * const config = unwrap(parseConfig());
944
- *
945
- * @throws The error value if the Result is an error
946
- */
947
- declare function unwrap<
948
- T,
949
- E
950
- >(result: Result<T, E>): T;
951
- /**
952
- * Unwraps a Result, returning a default value if error.
953
- *
954
- * @example
955
- * const user = await findById(id) ?? { name: 'Guest' };
956
- */
957
- declare function unwrapOr<
958
- T,
959
- E
960
- >(result: Result<T, E>, defaultValue: T): T;
961
- /**
962
- * Maps the success value to a new type.
963
- *
964
- * @example
965
- * const userName = map(userResult, u => u.name);
966
- * // Result<string, Error> if userResult was Result<User, Error>
967
- *
968
- * @example
969
- * // Transform while preserving error type
970
- * const id = map(ok({ userId: 5 }), u => u.userId);
971
- * // Ok<number>
972
- */
973
- declare function map<
974
- T,
975
- E,
976
- U
977
- >(result: Result<T, E>, fn: (data: T) => U): Result<U, E>;
860
+ declare class EntityValidationError extends EntityError {
861
+ readonly code: "ValidationError";
862
+ /**
863
+ * Validation errors.
864
+ */
865
+ readonly errors: readonly {
866
+ readonly path: readonly (string | number)[];
867
+ readonly message: string;
868
+ readonly code: string;
869
+ }[];
870
+ constructor(errors: readonly {
871
+ readonly path: readonly (string | number)[];
872
+ readonly message: string;
873
+ readonly code: string;
874
+ }[]);
875
+ }
978
876
  /**
979
- * Chains Result-returning functions.
980
- *
981
- * Allows chaining operations that can fail without nested try/catch.
982
- *
983
- * @example
984
- * // Synchronous
985
- * const profile = flatMap(
986
- * await repo.findOne(userId),
987
- * (user) => profileRepo.findOne(user.profileId)
988
- * );
989
- *
990
- * @example
991
- * // Asynchronous
992
- * const finalResult = await flatMap(
993
- * await repo.findOne(userId),
994
- * async (user) => await profileRepo.findOne(user.profileId)
995
- * );
877
+ * Type guard for EntityValidationError.
996
878
  */
997
- declare function flatMap<
998
- T,
999
- E,
1000
- U,
1001
- F
1002
- >(result: Result<T, E>, fn: (data: T) => Result<U, F>): Result<U, E | F>;
1003
- declare function flatMap<
1004
- T,
1005
- E,
1006
- U,
1007
- F
1008
- >(result: Result<T, E>, fn: (data: T) => Promise<Result<U, F>>): Promise<Result<U, E | F>>;
879
+ declare function isEntityValidationError(error: unknown): error is EntityValidationError;
1009
880
  /**
1010
- * Pattern matching on Result.
1011
- *
1012
- * @example
1013
- * const message = match(result, {
1014
- * ok: (user) => \`Hello, \${user.name}!\`,
1015
- * err: (e) => \`Error: \${e.message}\`
1016
- * });
881
+ * Internal server error - 500.
1017
882
  */
1018
- declare function match<
1019
- T,
1020
- E,
1021
- OkR,
1022
- ErrR
1023
- >(result: Result<T, E>, handlers: {
1024
- ok: (data: T) => OkR;
1025
- err: (error: E) => ErrR;
1026
- }): OkR | ErrR;
883
+ declare class InternalError2 extends EntityError {
884
+ readonly code: "InternalError";
885
+ constructor(message?: string);
886
+ }
1027
887
  /**
1028
- * Type for error handlers in matchErr.
1029
- * Extracts error codes from an error union and creates a handler map.
888
+ * Type guard for InternalError.
1030
889
  */
1031
- type ErrorHandlers<
1032
- E,
1033
- R
1034
- > = { [K in E as K extends {
1035
- readonly code: infer C extends string;
1036
- } ? C : never] : (error: K) => R };
890
+ declare function isInternalError(error: unknown): error is InternalError2;
1037
891
  /**
1038
- * Exhaustive pattern matching on Result errors.
1039
- *
1040
- * Unlike `match()` which gives you a single `err` handler,
1041
- * `matchErr` requires a handler for every error type in the union.
1042
- * The compiler enforces exhaustiveness — add a new error type to the union
1043
- * and every callsite lights up until you handle it.
1044
- *
1045
- * Errors are discriminated by their `code` string literal field.
1046
- *
1047
- * @example
1048
- * const response = matchErr(result, {
1049
- * ok: (user) => json({ data: user }, 201),
1050
- * NOT_FOUND: (e) => json({ error: 'NOT_FOUND', message: e.message }, 404),
1051
- * UNIQUE_VIOLATION: (e) => json({ error: 'EMAIL_EXISTS', field: e.column }, 409),
1052
- * });
1053
- * // ^ Compile error if error union adds a new member you didn't handle
1054
- *
1055
- * @throws Error if an error code is not handled
892
+ * Service unavailable error - 503.
1056
893
  */
1057
- declare function matchErr<
1058
- T,
1059
- E extends {
1060
- readonly code: string;
1061
- },
1062
- R
1063
- >(result: Result<T, E>, handlers: {
1064
- ok: (data: T) => R;
1065
- } & ErrorHandlers<E, R>): R;
894
+ declare class ServiceUnavailableError extends EntityError {
895
+ readonly code: "ServiceUnavailable";
896
+ /**
897
+ * Seconds until retry.
898
+ */
899
+ readonly retryAfter?: number;
900
+ constructor(message?: string, retryAfter?: number);
901
+ }
1066
902
  /**
1067
- * Type guard for Ok results.
1068
- *
1069
- * @example
1070
- * if (isOk(result)) {
1071
- * console.log(result.data); // TypeScript knows this is T
1072
- * }
903
+ * Type guard for ServiceUnavailableError.
1073
904
  */
1074
- declare function isOk<
1075
- T,
1076
- E
1077
- >(result: Result<T, E>): result is Ok<T>;
905
+ declare function isServiceUnavailableError(error: unknown): error is ServiceUnavailableError;
1078
906
  /**
1079
- * Type guard for Err results.
907
+ * Union type for all entity errors.
1080
908
  *
1081
909
  * @example
1082
- * if (isErr(result)) {
1083
- * console.log(result.error); // TypeScript knows this is E
910
+ * import { matchError, EntityErrorType } from '@vertz/errors';
911
+ *
912
+ * // Server-side: handling database errors
913
+ * const result = await db.users.create(data);
914
+ * if (!result.ok) {
915
+ * return matchError(result.error, {
916
+ * BAD_REQUEST: (e) => Response.json({ error: e.message }, { status: 400 }),
917
+ * UNAUTHORIZED: () => Response.json({ error: 'Unauthorized' }, { status: 401 }),
918
+ * FORBIDDEN: () => Response.json({ error: 'Forbidden' }, { status: 403 }),
919
+ * NOT_FOUND: (e) => Response.json({ error: e.message }, { status: 404 }),
920
+ * METHOD_NOT_ALLOWED: () => Response.json({ error: 'Method not allowed' }, { status: 405 }),
921
+ * CONFLICT: (e) => Response.json({ error: e.message }, { status: 409 }),
922
+ * ENTITY_VALIDATION_ERROR: (e) => Response.json({ error: e.errors }, { status: 422 }),
923
+ * INTERNAL_ERROR: () => Response.json({ error: 'Internal error' }, { status: 500 }),
924
+ * SERVICE_UNAVAILABLE: () => Response.json({ error: 'Service unavailable' }, { status: 503 }),
925
+ * });
1084
926
  * }
1085
927
  */
1086
- declare function isErr<
1087
- T,
1088
- E
1089
- >(result: Result<T, E>): result is Err<E>;
928
+ type EntityErrorType = BadRequestError | EntityUnauthorizedError | EntityForbiddenError | EntityNotFoundError | MethodNotAllowedError | EntityConflictError | EntityValidationError | InternalError2 | ServiceUnavailableError;
1090
929
  /**
1091
930
  * Fetch error classes.
1092
931
  *
@@ -1383,219 +1222,129 @@ declare function createHttpError(status: number, message: string, serverCode?: s
1383
1222
  */
1384
1223
  type FetchErrorType = FetchNetworkError | HttpError2 | FetchBadRequestError | FetchUnauthorizedError | FetchForbiddenError | FetchNotFoundError | FetchConflictError | FetchGoneError | FetchUnprocessableEntityError | FetchRateLimitError | FetchInternalServerError | FetchServiceUnavailableError | FetchTimeoutError | ParseError2 | FetchValidationError;
1385
1224
  /**
1386
- * Entity error classes.
1225
+ * Infrastructure errors.
1387
1226
  *
1388
- * These errors mirror server HTTP error codes and are used at the HTTP boundary.
1227
+ * These are operational failures that the application developer never handles
1228
+ * in business logic. They're caught by global middleware/error boundaries
1229
+ * and result in 500/503 responses.
1389
1230
  */
1390
1231
  /**
1391
- * Base class for entity errors.
1392
- *
1393
- * @example
1394
- * import { EntityError } from '@ *
1395
- * // Checkvertz/errors';
1396
- error type
1397
- * if (error instanceof EntityError) {
1398
- * console.log(error.code); // e.g., 'NOT_FOUND'
1399
- * console.log(error.message);
1400
- * }
1232
+ * Base class for infrastructure errors.
1401
1233
  */
1402
- declare abstract class EntityError extends Error {
1403
- /**
1404
- * The error code - a string literal for type-safe discrimination.
1405
- */
1406
- readonly code: string;
1407
- constructor(code: string, message: string);
1234
+ declare class InfraError extends Error {
1235
+ constructor(message: string);
1408
1236
  }
1409
1237
  /**
1410
- * Bad request error - 400.
1238
+ * Database connection error.
1239
+ *
1240
+ * Thrown when the database is unreachable.
1411
1241
  */
1412
- declare class BadRequestError extends EntityError {
1413
- readonly code: "BadRequest";
1242
+ declare class ConnectionError extends InfraError {
1414
1243
  constructor(message?: string);
1415
1244
  }
1416
1245
  /**
1417
- * Type guard for BadRequestError.
1418
- */
1419
- declare function isBadRequestError(error: unknown): error is BadRequestError;
1420
- /**
1421
- * Unauthorized error - 401.
1246
+ * Connection pool exhausted error.
1247
+ *
1248
+ * Thrown when no connections are available in the pool.
1422
1249
  */
1423
- declare class EntityUnauthorizedError extends EntityError {
1424
- readonly code: "Unauthorized";
1250
+ declare class PoolExhaustedError extends InfraError {
1425
1251
  constructor(message?: string);
1426
1252
  }
1427
1253
  /**
1428
- * Type guard for EntityUnauthorizedError.
1429
- */
1430
- declare function isEntityUnauthorizedError(error: unknown): error is EntityUnauthorizedError;
1431
- /**
1432
- * Forbidden error - 403.
1254
+ * Query error.
1255
+ *
1256
+ * Thrown when a query fails (malformed, syntax error, etc.).
1433
1257
  */
1434
- declare class EntityForbiddenError extends EntityError {
1435
- readonly code: "Forbidden";
1258
+ declare class QueryError extends InfraError {
1436
1259
  constructor(message?: string);
1437
1260
  }
1438
1261
  /**
1439
- * Type guard for EntityForbiddenError.
1440
- */
1441
- declare function isEntityForbiddenError(error: unknown): error is EntityForbiddenError;
1442
- /**
1443
- * Not found error - 404.
1444
- *
1445
- * @example
1446
- * // Using in matchError for server-side error handling
1447
- * const result = await db.users.get(userId);
1448
- * if (!result.ok) {
1449
- * return matchError(result.error, {
1450
- * NOT_FOUND: (e) => Response.json(
1451
- * { error: { code: 'NOT_FOUND', message: `User ${userId} not found` } },
1452
- * { status: 404 }
1453
- * ),
1454
- * // ... other handlers
1455
- * });
1456
- * }
1262
+ * Timeout error.
1457
1263
  *
1458
- * @example
1459
- * // With resource info
1460
- * throw new EntityNotFoundError('User not found', 'User', userId);
1264
+ * Thrown when an operation takes too long.
1461
1265
  */
1462
- declare class EntityNotFoundError extends EntityError {
1463
- readonly code: "NotFound";
1464
- /**
1465
- * The type of resource that wasn't found.
1466
- */
1467
- readonly resource?: string;
1468
- /**
1469
- * The ID of the resource that wasn't found.
1470
- */
1471
- readonly resourceId?: string;
1472
- constructor(message?: string, resource?: string, resourceId?: string);
1266
+ declare class TimeoutError2 extends InfraError {
1267
+ constructor(message?: string);
1473
1268
  }
1474
1269
  /**
1475
- * Type guard for EntityNotFoundError.
1476
- */
1477
- declare function isEntityNotFoundError(error: unknown): error is EntityNotFoundError;
1478
- /**
1479
- * Method not allowed error - 405.
1270
+ * Network error.
1271
+ *
1272
+ * Thrown when HTTP client can't reach the server.
1480
1273
  */
1481
- declare class MethodNotAllowedError extends EntityError {
1482
- readonly code: "MethodNotAllowed";
1483
- /**
1484
- * Allowed HTTP methods.
1485
- */
1486
- readonly allowedMethods?: string;
1487
- constructor(allowedMethods?: string, message?: string);
1274
+ declare class NetworkError2 extends InfraError {
1275
+ constructor(message?: string);
1488
1276
  }
1489
1277
  /**
1490
- * Type guard for MethodNotAllowedError.
1491
- */
1492
- declare function isMethodNotAllowedError(error: unknown): error is MethodNotAllowedError;
1493
- /**
1494
- * Conflict error - 409.
1278
+ * Serialization error.
1279
+ *
1280
+ * Thrown when response couldn't be decoded.
1495
1281
  */
1496
- declare class EntityConflictError extends EntityError {
1497
- readonly code: "Conflict";
1498
- /**
1499
- * The field that caused the conflict.
1500
- */
1501
- readonly field?: string;
1502
- constructor(message?: string, field?: string);
1282
+ declare class SerializationError extends InfraError {
1283
+ constructor(message?: string);
1503
1284
  }
1504
1285
  /**
1505
- * Type guard for EntityConflictError.
1286
+ * Union type for all infrastructure errors.
1506
1287
  */
1507
- declare function isEntityConflictError(error: unknown): error is EntityConflictError;
1288
+ type InfraErrors = ConnectionError | PoolExhaustedError | QueryError | TimeoutError2 | NetworkError2 | SerializationError;
1508
1289
  /**
1509
- * Entity validation error - 422.
1290
+ * Maps a database error to an HTTP status code.
1510
1291
  *
1511
- * @example
1512
- * // Server-side: throwing validation errors
1513
- * throw new EntityValidationError([
1514
- * { path: ['email'], message: 'Invalid email format', code: 'INVALID_FORMAT' },
1515
- * { path: ['age'], message: 'Must be positive', code: 'MIN_VALUE' },
1516
- * ]);
1292
+ * @param error - A database domain error
1293
+ * @returns HTTP status code
1517
1294
  *
1518
1295
  * @example
1519
- * // Server-side: handling in HTTP response
1520
- * if (!result.ok) {
1521
- * return matchError(result.error, {
1522
- * ENTITY_VALIDATION_ERROR: (e) => Response.json(
1523
- * { error: { code: 'VALIDATION_ERROR', message: 'Validation failed', errors: e.errors } },
1524
- * { status: 422 }
1525
- * ),
1526
- * // ... other handlers
1527
- * });
1528
- * }
1296
+ * const status = dbErrorToHttpStatus(error);
1297
+ * // NOT_FOUND → 404
1298
+ * // UNIQUE_VIOLATION → 409
1299
+ * // FK_VIOLATION → 422
1300
+ * // NOT_NULL_VIOLATION 422
1301
+ * // CHECK_VIOLATION → 422
1529
1302
  */
1530
- declare class EntityValidationError extends EntityError {
1531
- readonly code: "ValidationError";
1532
- /**
1533
- * Validation errors.
1534
- */
1535
- readonly errors: readonly {
1536
- readonly path: readonly (string | number)[];
1537
- readonly message: string;
1538
- readonly code: string;
1539
- }[];
1540
- constructor(errors: readonly {
1541
- readonly path: readonly (string | number)[];
1542
- readonly message: string;
1543
- readonly code: string;
1544
- }[]);
1545
- }
1303
+ declare function dbErrorToHttpStatus(error: ReadError | WriteError): number;
1546
1304
  /**
1547
- * Type guard for EntityValidationError.
1305
+ * Maps a NotFoundError to HTTP status.
1548
1306
  */
1549
- declare function isEntityValidationError(error: unknown): error is EntityValidationError;
1307
+ declare function notFoundErrorToHttpStatus(_error: NotFoundError2): number;
1550
1308
  /**
1551
- * Internal server error - 500.
1309
+ * Maps a UniqueViolation to HTTP status.
1552
1310
  */
1553
- declare class InternalError2 extends EntityError {
1554
- readonly code: "InternalError";
1555
- constructor(message?: string);
1556
- }
1311
+ declare function uniqueViolationToHttpStatus(_error: UniqueViolation): number;
1557
1312
  /**
1558
- * Type guard for InternalError.
1313
+ * Maps a FKViolation to HTTP status.
1559
1314
  */
1560
- declare function isInternalError(error: unknown): error is InternalError2;
1315
+ declare function fkViolationToHttpStatus(_error: FKViolation): number;
1561
1316
  /**
1562
- * Service unavailable error - 503.
1317
+ * Maps a NotNullViolation to HTTP status.
1563
1318
  */
1564
- declare class ServiceUnavailableError extends EntityError {
1565
- readonly code: "ServiceUnavailable";
1566
- /**
1567
- * Seconds until retry.
1568
- */
1569
- readonly retryAfter?: number;
1570
- constructor(message?: string, retryAfter?: number);
1571
- }
1319
+ declare function notNullViolationToHttpStatus(_error: NotNullViolation): number;
1572
1320
  /**
1573
- * Type guard for ServiceUnavailableError.
1321
+ * Maps a CheckViolation to HTTP status.
1574
1322
  */
1575
- declare function isServiceUnavailableError(error: unknown): error is ServiceUnavailableError;
1323
+ declare function checkViolationToHttpStatus(_error: CheckViolation): number;
1576
1324
  /**
1577
- * Union type for all entity errors.
1325
+ * Unknown error response from server.
1326
+ */
1327
+ interface UnknownError {
1328
+ readonly code: "UNKNOWN";
1329
+ readonly message: string;
1330
+ readonly status: number;
1331
+ }
1332
+ /**
1333
+ * Maps an HTTP response to a client domain error.
1578
1334
  *
1579
- * @example
1580
- * import { matchError, EntityErrorType } from '@vertz/errors';
1335
+ * @param status - HTTP status code
1336
+ * @param body - Response body
1337
+ * @returns Client domain error
1581
1338
  *
1582
- * // Server-side: handling database errors
1583
- * const result = await db.users.create(data);
1584
- * if (!result.ok) {
1585
- * return matchError(result.error, {
1586
- * BAD_REQUEST: (e) => Response.json({ error: e.message }, { status: 400 }),
1587
- * UNAUTHORIZED: () => Response.json({ error: 'Unauthorized' }, { status: 401 }),
1588
- * FORBIDDEN: () => Response.json({ error: 'Forbidden' }, { status: 403 }),
1589
- * NOT_FOUND: (e) => Response.json({ error: e.message }, { status: 404 }),
1590
- * METHOD_NOT_ALLOWED: () => Response.json({ error: 'Method not allowed' }, { status: 405 }),
1591
- * CONFLICT: (e) => Response.json({ error: e.message }, { status: 409 }),
1592
- * ENTITY_VALIDATION_ERROR: (e) => Response.json({ error: e.errors }, { status: 422 }),
1593
- * INTERNAL_ERROR: () => Response.json({ error: 'Internal error' }, { status: 500 }),
1594
- * SERVICE_UNAVAILABLE: () => Response.json({ error: 'Service unavailable' }, { status: 503 }),
1595
- * });
1596
- * }
1339
+ * @example
1340
+ * const error = httpToClientError(404, { message: 'User not found' });
1341
+ * // { code: 'NOT_FOUND', message: 'User not found', resource: 'user' }
1342
+ */
1343
+ declare function httpToClientError(status: number, body: unknown): ApiError | UnknownError;
1344
+ /**
1345
+ * Checks if an error is an UnknownError.
1597
1346
  */
1598
- type EntityErrorType = BadRequestError | EntityUnauthorizedError | EntityForbiddenError | EntityNotFoundError | MethodNotAllowedError | EntityConflictError | EntityValidationError | InternalError2 | ServiceUnavailableError;
1347
+ declare function isUnknownError(error: ApiError | UnknownError): error is UnknownError;
1599
1348
  /**
1600
1349
  * Handler map type for FetchError.
1601
1350
  * Each key corresponds to an error code, and the value is a handler function.
@@ -1666,4 +1415,255 @@ type EntityErrorHandlers<R> = {
1666
1415
  */
1667
1416
  declare function matchError<R>(error: FetchErrorType, handlers: FetchErrorHandlers<R>): R;
1668
1417
  declare function matchError<R>(error: EntityErrorType, handlers: EntityErrorHandlers<R>): R;
1418
+ /**
1419
+ * Result type and utilities for errors-as-values pattern.
1420
+ *
1421
+ * This module provides a type-safe alternative to throwing exceptions.
1422
+ * Every operation that can fail returns a Result<T, E> instead of throwing.
1423
+ *
1424
+ * @example
1425
+ * import { ok, err, unwrap, map, flatMap, match, matchErr } from '@vertz/errors';
1426
+ *
1427
+ * // Creating results
1428
+ * const success = ok({ name: 'Alice' });
1429
+ * const failure = err({ code: 'NOT_FOUND', message: 'User not found' });
1430
+ *
1431
+ * // Transforming
1432
+ * const doubled = map(ok(5), x => x * 2);
1433
+ *
1434
+ * // Chaining
1435
+ * const result = await flatMap(ok(5), async x => ok(x * 2));
1436
+ *
1437
+ * // Pattern matching
1438
+ * const message = match(result, {
1439
+ * ok: (data) => `Success: ${data}`,
1440
+ * err: (error) => `Error: ${error.message}`
1441
+ * });
1442
+ */
1443
+ /**
1444
+ * Represents a successful result.
1445
+ *
1446
+ * @example
1447
+ * { ok: true, data: { name: 'Alice' } }
1448
+ */
1449
+ interface Ok<T> {
1450
+ /** Always true for successful results */
1451
+ readonly ok: true;
1452
+ /** The successful value */
1453
+ readonly data: T;
1454
+ }
1455
+ /**
1456
+ * Represents an erroneous result.
1457
+ *
1458
+ * @example
1459
+ * { ok: false, error: { code: 'NOT_FOUND', message: 'User not found' } }
1460
+ */
1461
+ interface Err<E> {
1462
+ /** Always false for error results */
1463
+ readonly ok: false;
1464
+ /** The error value */
1465
+ readonly error: E;
1466
+ }
1467
+ /**
1468
+ * A discriminated union representing success or failure.
1469
+ *
1470
+ * @example
1471
+ * type UserResult = Result<User, ValidationError>;
1472
+ * type UsersResult = Result<User[], NotFoundError>;
1473
+ *
1474
+ * @example
1475
+ * const result: Result<string, Error> = ok('hello');
1476
+ * if (result.ok) {
1477
+ * console.log(result.data); // TypeScript knows this is string
1478
+ * } else {
1479
+ * console.log(result.error); // TypeScript knows this is Error
1480
+ * }
1481
+ */
1482
+ type Result<
1483
+ T,
1484
+ E = unknown
1485
+ > = Ok<T> | Err<E>;
1486
+ /**
1487
+ * Creates a successful Result.
1488
+ *
1489
+ * @example
1490
+ * const result = ok({ name: 'Alice' });
1491
+ * // { ok: true, data: { name: 'Alice' } }
1492
+ *
1493
+ * @example
1494
+ * // With type inference
1495
+ * const result = ok(42); // Ok<number>
1496
+ */
1497
+ declare const ok: <T>(data: T) => Ok<T>;
1498
+ /**
1499
+ * Creates an error Result.
1500
+ *
1501
+ * @example
1502
+ * const result = err({ code: 'VALIDATION_FAILED', message: 'Invalid email', issues: [] });
1503
+ * // { ok: false, error: { code: 'VALIDATION_FAILED', ... } }
1504
+ *
1505
+ * @example
1506
+ * // With simple string errors
1507
+ * const result = err('Something went wrong');
1508
+ * // { ok: false, error: 'Something went wrong' }
1509
+ */
1510
+ declare const err: <E>(error: E) => Err<E>;
1511
+ /**
1512
+ * Unwraps a Result, throwing if error.
1513
+ *
1514
+ * Use only in tests, scripts, or when failure is truly exceptional.
1515
+ *
1516
+ * @example
1517
+ * // Tests
1518
+ * const user = unwrap(await repo.findOneRequired(id));
1519
+ *
1520
+ * @example
1521
+ * // Scripts
1522
+ * const config = unwrap(parseConfig());
1523
+ *
1524
+ * @throws The error value if the Result is an error
1525
+ */
1526
+ declare function unwrap<
1527
+ T,
1528
+ E
1529
+ >(result: Result<T, E>): T;
1530
+ /**
1531
+ * Unwraps a Result, returning a default value if error.
1532
+ *
1533
+ * @example
1534
+ * const user = await findById(id) ?? { name: 'Guest' };
1535
+ */
1536
+ declare function unwrapOr<
1537
+ T,
1538
+ E
1539
+ >(result: Result<T, E>, defaultValue: T): T;
1540
+ /**
1541
+ * Maps the success value to a new type.
1542
+ *
1543
+ * @example
1544
+ * const userName = map(userResult, u => u.name);
1545
+ * // Result<string, Error> if userResult was Result<User, Error>
1546
+ *
1547
+ * @example
1548
+ * // Transform while preserving error type
1549
+ * const id = map(ok({ userId: 5 }), u => u.userId);
1550
+ * // Ok<number>
1551
+ */
1552
+ declare function map<
1553
+ T,
1554
+ E,
1555
+ U
1556
+ >(result: Result<T, E>, fn: (data: T) => U): Result<U, E>;
1557
+ /**
1558
+ * Chains Result-returning functions.
1559
+ *
1560
+ * Allows chaining operations that can fail without nested try/catch.
1561
+ *
1562
+ * @example
1563
+ * // Synchronous
1564
+ * const profile = flatMap(
1565
+ * await repo.findOne(userId),
1566
+ * (user) => profileRepo.findOne(user.profileId)
1567
+ * );
1568
+ *
1569
+ * @example
1570
+ * // Asynchronous
1571
+ * const finalResult = await flatMap(
1572
+ * await repo.findOne(userId),
1573
+ * async (user) => await profileRepo.findOne(user.profileId)
1574
+ * );
1575
+ */
1576
+ declare function flatMap<
1577
+ T,
1578
+ E,
1579
+ U,
1580
+ F
1581
+ >(result: Result<T, E>, fn: (data: T) => Result<U, F>): Result<U, E | F>;
1582
+ declare function flatMap<
1583
+ T,
1584
+ E,
1585
+ U,
1586
+ F
1587
+ >(result: Result<T, E>, fn: (data: T) => Promise<Result<U, F>>): Promise<Result<U, E | F>>;
1588
+ /**
1589
+ * Pattern matching on Result.
1590
+ *
1591
+ * @example
1592
+ * const message = match(result, {
1593
+ * ok: (user) => \`Hello, \${user.name}!\`,
1594
+ * err: (e) => \`Error: \${e.message}\`
1595
+ * });
1596
+ */
1597
+ declare function match<
1598
+ T,
1599
+ E,
1600
+ OkR,
1601
+ ErrR
1602
+ >(result: Result<T, E>, handlers: {
1603
+ ok: (data: T) => OkR;
1604
+ err: (error: E) => ErrR;
1605
+ }): OkR | ErrR;
1606
+ /**
1607
+ * Type for error handlers in matchErr.
1608
+ * Extracts error codes from an error union and creates a handler map.
1609
+ */
1610
+ type ErrorHandlers<
1611
+ E,
1612
+ R
1613
+ > = { [K in E as K extends {
1614
+ readonly code: infer C extends string;
1615
+ } ? C : never] : (error: K) => R };
1616
+ /**
1617
+ * Exhaustive pattern matching on Result errors.
1618
+ *
1619
+ * Unlike `match()` which gives you a single `err` handler,
1620
+ * `matchErr` requires a handler for every error type in the union.
1621
+ * The compiler enforces exhaustiveness — add a new error type to the union
1622
+ * and every callsite lights up until you handle it.
1623
+ *
1624
+ * Errors are discriminated by their `code` string literal field.
1625
+ *
1626
+ * @example
1627
+ * const response = matchErr(result, {
1628
+ * ok: (user) => json({ data: user }, 201),
1629
+ * NOT_FOUND: (e) => json({ error: 'NOT_FOUND', message: e.message }, 404),
1630
+ * UNIQUE_VIOLATION: (e) => json({ error: 'EMAIL_EXISTS', field: e.column }, 409),
1631
+ * });
1632
+ * // ^ Compile error if error union adds a new member you didn't handle
1633
+ *
1634
+ * @throws Error if an error code is not handled
1635
+ */
1636
+ declare function matchErr<
1637
+ T,
1638
+ E extends {
1639
+ readonly code: string;
1640
+ },
1641
+ R
1642
+ >(result: Result<T, E>, handlers: {
1643
+ ok: (data: T) => R;
1644
+ } & ErrorHandlers<E, R>): R;
1645
+ /**
1646
+ * Type guard for Ok results.
1647
+ *
1648
+ * @example
1649
+ * if (isOk(result)) {
1650
+ * console.log(result.data); // TypeScript knows this is T
1651
+ * }
1652
+ */
1653
+ declare function isOk<
1654
+ T,
1655
+ E
1656
+ >(result: Result<T, E>): result is Ok<T>;
1657
+ /**
1658
+ * Type guard for Err results.
1659
+ *
1660
+ * @example
1661
+ * if (isErr(result)) {
1662
+ * console.log(result.error); // TypeScript knows this is E
1663
+ * }
1664
+ */
1665
+ declare function isErr<
1666
+ T,
1667
+ E
1668
+ >(result: Result<T, E>): result is Err<E>;
1669
1669
  export { unwrapOr, unwrap, uniqueViolationToHttpStatus, ok, notNullViolationToHttpStatus, notFoundErrorToHttpStatus, matchError, matchErr, match, map, isUserExistsError, isUnknownError, isUniqueViolation, isUnauthorizedError, isTokenInvalidError, isTokenExpiredError, isSessionNotFoundError, isSessionExpiredError, isServiceUnavailableError, isValidationError2 as isSchemaValidationError, isPermissionDeniedError, isParseError, isOk, isOAuthError, isNotNullViolation, isMigrationQueryError, isMigrationHistoryNotFound, isMigrationChecksumMismatch, isMfaRequiredError, isMfaNotEnabledError, isMfaInvalidCodeError, isMfaAlreadyEnabledError, isMethodNotAllowedError, isInvalidCredentialsError, isInternalError, isHttpError, isForbiddenError, isFetchValidationError, isFetchUnprocessableEntityError, isFetchUnauthorizedError, isFetchTimeoutError, isFetchServiceUnavailableError, isFetchRateLimitError, isFetchNotFoundError, isFetchNetworkError, isFetchInternalServerError, isFetchGoneError, isFetchForbiddenError, isFetchConflictError, isFetchBadRequestError, isFKViolation, isErr, isEntityValidationError, isEntityUnauthorizedError, isEntityNotFoundError, isEntityForbiddenError, isEntityConflictError, isNotFoundError2 as isDBNotFoundError, isConflictError, isValidationError as isClientValidationError, isRateLimitedError2 as isClientRateLimitedError, isNotFoundError as isClientNotFoundError, isCheckViolation, isBadRequestError, isAuthValidationError, isRateLimitedError as isAuthRateLimitedError, httpToClientError, flatMap, fkViolationToHttpStatus, err, dbErrorToHttpStatus, createUserExistsError, createUniqueViolation, createUnauthorizedError, createTokenInvalidError, createTokenExpiredError, createSessionNotFoundError, createSessionExpiredError, createValidationError2 as createSchemaValidationError, createPermissionDeniedError, createOAuthError, createNotNullViolation, createMigrationQueryError, createMigrationHistoryNotFound, createMigrationChecksumMismatch, createMfaRequiredError, createMfaNotEnabledError, createMfaInvalidCodeError, createMfaAlreadyEnabledError, createInvalidCredentialsError, createHttpError, createForbiddenError, createFKViolation, createNotFoundError2 as createDBNotFoundError, createConflictError, createValidationError as createClientValidationError, createRateLimitedError2 as createClientRateLimitedError, createNotFoundError as createClientNotFoundError, createCheckViolation, createAuthValidationError, createRateLimitedError as createAuthRateLimitedError, checkViolationToHttpStatus, WriteError, ValidationIssue, UserExistsError, UnknownError, UniqueViolation, UnauthorizedError, TokenInvalidError, TokenExpiredError, TimeoutError2 as TimeoutError, SessionNotFoundError, SessionExpiredError, ServiceUnavailableError, SerializationError, ValidationError3 as SchemaValidationError, Result, ReadError, QueryError, PoolExhaustedError, PermissionDeniedError, ParseError2 as ParseError, Ok, OAuthError, NotNullViolation, NetworkError2 as NetworkError, MigrationQueryError, MigrationHistoryNotFound, MigrationError, MigrationChecksumMismatch, MfaRequiredError, MfaNotEnabledError, MfaInvalidCodeError, MfaAlreadyEnabledError, MethodNotAllowedError, InvalidCredentialsError, InternalError2 as InternalError, InfraErrors, InfraError, HttpError2 as HttpError, ForbiddenError, FetchValidationError, FetchUnprocessableEntityError, FetchUnauthorizedError, FetchTimeoutError, FetchServiceUnavailableError, FetchRateLimitError, FetchNotFoundError, FetchNetworkError, FetchInternalServerError, FetchGoneError, FetchForbiddenError, FetchErrorType, FetchError, FetchConflictError, FetchBadRequestError, FKViolation, Err, EntityValidationError, EntityUnauthorizedError, EntityNotFoundError, EntityForbiddenError, EntityErrorType, EntityError, EntityConflictError, NotFoundError2 as DBNotFoundError, ConnectionError, ConflictError, ValidationError2 as ClientValidationError, RateLimitedError2 as ClientRateLimitedError, NotFoundError as ClientNotFoundError, CheckViolation, BadRequestError, AuthValidationError, RateLimitedError as AuthRateLimitedError, AuthError, AppError, ApiError };