@@ -78,6 +78,156 @@ function isModelClass(
7878 ) ;
7979}
8080
81+ import { DatabaseDriverError } from '../errors' ;
82+
83+ function handleDatabaseDriverError (
84+ this : DefaultCrudRepository < Entity , unknown , AnyObject > ,
85+ err : unknown ,
86+ ) : never {
87+ const error = err as AnyObject ;
88+ if ( err === null || err === undefined ) {
89+ throw new Error ( 'An unknown database execution error occurred.' ) ;
90+ }
91+
92+ // Handling existing already mapped errors
93+ if ( error . statusCode && error . statusCode >= 400 && error . statusCode < 500 ) {
94+ throw error ;
95+ }
96+
97+ const parsedCode = Number ( error . code ) ;
98+ const rawCode = ! isNaN ( parsedCode ) ? error . code : error . errno ; // error.code for posgres while errno for mysql/mongodb
99+
100+ const codeStr = String ( rawCode ) ;
101+
102+ // Initialize with default values
103+ let statusCode = 500 ;
104+ let errorCode = 'DATABASE_ERROR' ;
105+ let message = error . message || 'An unexpected database error occurred.' ;
106+
107+ // Evaluate database signatures and re-map properties dynamically
108+ switch ( codeStr ) {
109+ // 1. Unique Key / Duplicate Entries
110+ case '23505' : // Postgres
111+ case '1062' : // MySQL
112+ case '11000' : // MongoDB
113+ case '11001' :
114+ statusCode = 409 ;
115+ errorCode = 'DB_UNIQUE_CONSTRAINT_VIOLATION' ;
116+ message =
117+ 'The operation conflicts with an existing record unique constraint.' ;
118+ break ;
119+ // 2. Foreign Key Constraints (Missing Parents / Existing Children)
120+ case '23503' : // Postgres
121+ case '1216' : // MySQL
122+ case '1217' :
123+ case '1451' :
124+ case '1452' :
125+ statusCode = 422 ;
126+ errorCode = 'DB_FOREIGN_KEY_VIOLATION' ;
127+ message =
128+ 'Relational integrity validation failed. Referenced parent record not found.' ;
129+ break ;
130+ // 3. Null / Required Fields Omissions
131+ case '23502' : // Postgres
132+ case '1048' : // MySQL
133+ case '1364' :
134+ case '121' : // MongoDB Document Validation Failed
135+ statusCode = 400 ;
136+ errorCode = 'DB_NOT_NULL_VIOLATION' ;
137+ message = 'Required database schema properties are missing or null.' ;
138+ break ;
139+ // 4. Bad Casts / Truncation / Data Type Mismatch
140+ case '22P02' : // Postgres Invalid Text Representation (e.g. Bad UUID format)
141+ case '22001' : // Postgres String Data Right Truncation
142+ case '1265' : // MySQL Data Truncated
143+ case '1366' :
144+ statusCode = 400 ;
145+ errorCode = 'DB_DATA_TYPE_MISMATCH' ;
146+ message =
147+ 'The query properties contain unexpected formatting types or overflows.' ;
148+ break ;
149+ case '3105' : // MySQL Server Generated column value ignored/disallowed
150+ case '1906' : // MariaDB Generated column value ignored/disallowed
151+ statusCode = 400 ;
152+ errorCode = 'DB_GENERATED_COLUMN_VIOLATION' ;
153+ message =
154+ 'Cannot manually assign or update values on a database-generated computed column.' ;
155+ break ;
156+ // 5. Missing Table / Schema Definition Errors
157+ case '1146' : // MySQL errno for missing table
158+ case '42P01' : // Postgres error code for undefined_table
159+ statusCode = 400 ; // Setting as 400 because 500 is too generic and doesn't provide enough context for the client
160+ errorCode = 'DB_SCHEMA_MISSING_TABLE' ;
161+ message = 'The requested database table or relation does not exist.' ;
162+ break ;
163+ // 6. Query Timeout Errors
164+ case '57014' : // Postgres query_canceled
165+ case '1907' : // MySQL query_timeout
166+ case '3024' : // MongoDB query_timeout
167+ statusCode = 504 ;
168+ errorCode = 'DB_QUERY_TIMEOUT' ;
169+ message = 'The database operation took too long and was aborted.' ;
170+ break ;
171+ // 7. Concurrency / Locking Conflicts
172+ case '40001' : // Postgres serialization_failure
173+ case '40P01' : // Postgres deadlock_detected
174+ case '1213' : // MySQL deadlock found when trying to get lock
175+ case '1205' : // MySQL lock wait timeout exceeded
176+ statusCode = 409 ;
177+ errorCode = 'DB_LOCK_CONFLICT' ;
178+ message =
179+ 'A concurrency lock conflict occurred. Please retry the operation.' ;
180+ break ;
181+ // 8. Check Constraint / Business Logic Violations
182+ case '23514' : // Postgres check_violation
183+ case '23P01' : // Postgres exclusion_violation
184+ case '3819' : // MySQL check constraint violation
185+ statusCode = 400 ;
186+ errorCode = 'DB_CHECK_CONSTRAINT_VIOLATION' ;
187+ message =
188+ 'The data violates database business logic or range constraints.' ;
189+ break ;
190+ // 9. Connection / Availability Issues
191+ case '08003' : // Postgres connection_does_not_exist
192+ case '08006' : // Postgres connection_failure
193+ case '53300' : // Postgres too_many_connections
194+ case '1040' : // MySQL too many connections
195+ case '2002' : // MySQL connection refused
196+ case '2003' : // MySQL can't connect to MySQL server
197+ case '2006' : // MySQL server has gone away
198+ case '2013' : // MySQL lost connection to MySQL server during query
199+ case '8000' : // MongoDB network error
200+ case '8001' : // MongoDB connection closed
201+ case '8002' : // MongoDB connection timeout
202+ statusCode = 503 ;
203+ errorCode = 'DB_CONNECTION_FAILURE' ;
204+ message =
205+ 'The database is temporarily unavailable or overloaded. Please try again later.' ;
206+ break ;
207+ // 10. Numeric Overflow / Out of Range Values
208+ case '22003' : // Postgres numeric_value_out_of_range
209+ case '1264' : // MySQL Out of range value for column
210+ statusCode = 400 ;
211+ errorCode = 'DB_NUMERIC_OUT_OF_RANGE' ;
212+ message =
213+ 'A numeric or string value exceeds the maximum allowable size for the field.' ;
214+ break ;
215+ }
216+ if ( error . stack ) console . error ( error . stack ) ;
217+
218+ // If we matched a standard driver rule, throw our clean uniform class
219+ if ( statusCode !== 500 ) {
220+ throw new DatabaseDriverError ( this . entityClass , message , {
221+ code : errorCode ,
222+ statusCode : statusCode ,
223+ nativeCode : rawCode ,
224+ } ) ;
225+ }
226+
227+ // Otherwise, bubble up the original error safely to protect core connection strings/etc.
228+ throw err ;
229+ }
230+
81231/**
82232 * This is a bridge to the legacy DAO class. The function mixes DAO methods
83233 * into a model class and attach it to a given data source
@@ -488,7 +638,9 @@ export class DefaultCrudRepository<
488638 async create ( entity : DataObject < T > , options ?: Options ) : Promise < T > {
489639 // perform persist hook
490640 const data = await this . entityToData ( entity , options ) ;
491- const model = await ensurePromise ( this . modelClass . create ( data , options ) ) ;
641+ const model = await ensurePromise (
642+ this . modelClass . create ( data , options ) ,
643+ ) . catch ( handleDatabaseDriverError ) ;
492644 return this . toEntity ( model ) ;
493645 }
494646
@@ -499,7 +651,7 @@ export class DefaultCrudRepository<
499651 ) ;
500652 const models = await ensurePromise (
501653 this . modelClass . createAll ( data , options ) ,
502- ) ;
654+ ) . catch ( handleDatabaseDriverError ) ;
503655 return this . toEntities ( models ) ;
504656 }
505657
@@ -520,7 +672,7 @@ export class DefaultCrudRepository<
520672 const include = filter ?. include ;
521673 const models = await ensurePromise (
522674 this . modelClass . find ( this . normalizeFilter ( filter ) , options ) ,
523- ) ;
675+ ) . catch ( handleDatabaseDriverError ) ;
524676 const entities = this . toEntities ( models ) ;
525677 return this . includeRelatedModels ( entities , include , options ) ;
526678 }
@@ -531,7 +683,7 @@ export class DefaultCrudRepository<
531683 ) : Promise < ( T & Relations ) | null > {
532684 const model = await ensurePromise (
533685 this . modelClass . findOne ( this . normalizeFilter ( filter ) , options ) ,
534- ) ;
686+ ) . catch ( handleDatabaseDriverError ) ;
535687 if ( ! model ) return null ;
536688 const entity = this . toEntity ( model ) ;
537689 const include = filter ?. include ;
@@ -551,7 +703,7 @@ export class DefaultCrudRepository<
551703 const include = filter ?. include ;
552704 const model = await ensurePromise (
553705 this . modelClass . findById ( id , this . normalizeFilter ( filter ) , options ) ,
554- ) ;
706+ ) . catch ( handleDatabaseDriverError ) ;
555707 if ( ! model ) {
556708 throw new EntityNotFoundError ( this . entityClass , id ) ;
557709 }
@@ -583,7 +735,7 @@ export class DefaultCrudRepository<
583735 const persistedData = await this . entityToData ( data , options ) ;
584736 const result = await ensurePromise (
585737 this . modelClass . updateAll ( where , persistedData , options ) ,
586- ) ;
738+ ) . catch ( handleDatabaseDriverError ) ;
587739 return { count : result . count } ;
588740 }
589741
@@ -614,7 +766,9 @@ export class DefaultCrudRepository<
614766 ) : Promise < void > {
615767 try {
616768 const payload = await this . entityToData ( data , options ) ;
617- await ensurePromise ( this . modelClass . replaceById ( id , payload , options ) ) ;
769+ await ensurePromise (
770+ this . modelClass . replaceById ( id , payload , options ) ,
771+ ) . catch ( handleDatabaseDriverError ) ;
618772 } catch ( err ) {
619773 if ( err . statusCode === 404 ) {
620774 throw new EntityNotFoundError ( this . entityClass , id ) ;
@@ -626,24 +780,30 @@ export class DefaultCrudRepository<
626780 async deleteAll ( where ?: Where < T > , options ?: Options ) : Promise < Count > {
627781 const result = await ensurePromise (
628782 this . modelClass . deleteAll ( where , options ) ,
629- ) ;
783+ ) . catch ( handleDatabaseDriverError ) ;
630784 return { count : result . count } ;
631785 }
632786
633787 async deleteById ( id : ID , options ?: Options ) : Promise < void > {
634- const result = await ensurePromise ( this . modelClass . deleteById ( id , options ) ) ;
788+ const result = await ensurePromise (
789+ this . modelClass . deleteById ( id , options ) ,
790+ ) . catch ( handleDatabaseDriverError ) ;
635791 if ( result . count === 0 ) {
636792 throw new EntityNotFoundError ( this . entityClass , id ) ;
637793 }
638794 }
639795
640796 async count ( where ?: Where < T > , options ?: Options ) : Promise < Count > {
641- const result = await ensurePromise ( this . modelClass . count ( where , options ) ) ;
797+ const result = await ensurePromise (
798+ this . modelClass . count ( where , options ) ,
799+ ) . catch ( handleDatabaseDriverError ) ;
642800 return { count : result } ;
643801 }
644802
645803 exists ( id : ID , options ?: Options ) : Promise < boolean > {
646- return ensurePromise ( this . modelClass . exists ( id , options ) ) ;
804+ return ensurePromise ( this . modelClass . exists ( id , options ) ) . catch (
805+ handleDatabaseDriverError ,
806+ ) ;
647807 }
648808
649809 /**
0 commit comments