nestjs-drizzle-crud 1.0.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.
Files changed (73) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +22 -0
  3. package/dist/core/abstract/sql-base-crud.service.d.ts +52 -0
  4. package/dist/core/abstract/sql-base-crud.service.js +536 -0
  5. package/dist/core/abstract/sql-base-crud.service.js.map +1 -0
  6. package/dist/core/interfaces/crud-service.interface.d.ts +46 -0
  7. package/dist/core/interfaces/crud-service.interface.js +3 -0
  8. package/dist/core/interfaces/crud-service.interface.js.map +1 -0
  9. package/dist/core/interfaces/drizzle-crud-config.interface.d.ts +21 -0
  10. package/dist/core/interfaces/drizzle-crud-config.interface.js +3 -0
  11. package/dist/core/interfaces/drizzle-crud-config.interface.js.map +1 -0
  12. package/dist/core/interfaces/sql-crud-config.interface.d.ts +37 -0
  13. package/dist/core/interfaces/sql-crud-config.interface.js +3 -0
  14. package/dist/core/interfaces/sql-crud-config.interface.js.map +1 -0
  15. package/dist/core/types/sql.types.d.ts +13 -0
  16. package/dist/core/types/sql.types.js +3 -0
  17. package/dist/core/types/sql.types.js.map +1 -0
  18. package/dist/decorators/crud-service.decorator.d.ts +4 -0
  19. package/dist/decorators/crud-service.decorator.js +10 -0
  20. package/dist/decorators/crud-service.decorator.js.map +1 -0
  21. package/dist/decorators/crud.decorator.d.ts +1 -0
  22. package/dist/decorators/crud.decorator.js +9 -0
  23. package/dist/decorators/crud.decorator.js.map +1 -0
  24. package/dist/decorators/entity-config.decorator.d.ts +13 -0
  25. package/dist/decorators/entity-config.decorator.js +12 -0
  26. package/dist/decorators/entity-config.decorator.js.map +1 -0
  27. package/dist/decorators/index.d.ts +2 -0
  28. package/dist/decorators/index.js +9 -0
  29. package/dist/decorators/index.js.map +1 -0
  30. package/dist/exceptions/crud.exceptions.d.ts +27 -0
  31. package/dist/exceptions/crud.exceptions.js +48 -0
  32. package/dist/exceptions/crud.exceptions.js.map +1 -0
  33. package/dist/index.d.ts +12 -0
  34. package/dist/index.js +40 -0
  35. package/dist/index.js.map +1 -0
  36. package/dist/interfaces/crud-config.interface.d.ts +18 -0
  37. package/dist/interfaces/crud-config.interface.js +3 -0
  38. package/dist/interfaces/crud-config.interface.js.map +1 -0
  39. package/dist/interfaces/crud-options.interface.d.ts +5 -0
  40. package/dist/interfaces/crud-options.interface.js +18 -0
  41. package/dist/interfaces/crud-options.interface.js.map +1 -0
  42. package/dist/interfaces/crud-service.interface.d.ts +46 -0
  43. package/dist/interfaces/crud-service.interface.js +3 -0
  44. package/dist/interfaces/crud-service.interface.js.map +1 -0
  45. package/dist/interfaces/operation-options.interface.d.ts +23 -0
  46. package/dist/interfaces/operation-options.interface.js +3 -0
  47. package/dist/interfaces/operation-options.interface.js.map +1 -0
  48. package/dist/modules/crud.module.d.ts +5 -0
  49. package/dist/modules/crud.module.js +33 -0
  50. package/dist/modules/crud.module.js.map +1 -0
  51. package/dist/modules/drizzle-crud.module.d.ts +13 -0
  52. package/dist/modules/drizzle-crud.module.js +84 -0
  53. package/dist/modules/drizzle-crud.module.js.map +1 -0
  54. package/dist/services/base-crud.service.d.ts +49 -0
  55. package/dist/services/base-crud.service.js +398 -0
  56. package/dist/services/base-crud.service.js.map +1 -0
  57. package/dist/services/crud.service.d.ts +5 -0
  58. package/dist/services/crud.service.js +29 -0
  59. package/dist/services/crud.service.js.map +1 -0
  60. package/dist/test-utils/base-crud.spec-helper.d.ts +64 -0
  61. package/dist/test-utils/base-crud.spec-helper.js +119 -0
  62. package/dist/test-utils/base-crud.spec-helper.js.map +1 -0
  63. package/dist/test-utils/index.d.ts +2 -0
  64. package/dist/test-utils/index.js +8 -0
  65. package/dist/test-utils/index.js.map +1 -0
  66. package/dist/test-utils/test-factory.d.ts +29 -0
  67. package/dist/test-utils/test-factory.js +81 -0
  68. package/dist/test-utils/test-factory.js.map +1 -0
  69. package/dist/tsconfig.build.tsbuildinfo +1 -0
  70. package/dist/utils/sql-dialect.utils.d.ts +4 -0
  71. package/dist/utils/sql-dialect.utils.js +18 -0
  72. package/dist/utils/sql-dialect.utils.js.map +1 -0
  73. package/package.json +70 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Suman Bonakurthi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,22 @@
1
+ # Drizzle CRUD Nest
2
+
3
+ A complete, type-safe CRUD abstraction layer for Drizzle ORM in NestJS applications.
4
+ Supports PostgreSQL and MySQL with advanced features like soft delete, transactions,
5
+ bulk operations, and full-text search.
6
+
7
+ ## Features
8
+
9
+ - 🚀 **Complete CRUD Operations** - find, create, update, delete, and more
10
+ - 🗃️ **SQL Database Support** - PostgreSQL & MySQL with dialect-specific optimizations
11
+ - ⚡ **Type-Safe** - Full TypeScript support with generics
12
+ - 🔄 **Soft Delete** - Built-in soft delete with restore functionality
13
+ - 📦 **Bulk Operations** - Mass create, update, delete with transaction support
14
+ - 🔍 **Advanced Querying** - Filtering, pagination, sorting, full-text search
15
+ - 🎯 **NestJS Native** - Seamless integration with NestJS dependency injection
16
+ - 🧪 **Test Utilities** - Comprehensive testing helpers
17
+ - 🛡️ **Production Ready** - Error handling, transactions, and validation hooks
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ npm install drizzle-crud-nest
@@ -0,0 +1,52 @@
1
+ import { ICrudService, PaginationOptions } from "../interfaces/crud-service.interface";
2
+ import { SqlCrudConfig, SqlOperationOptions } from "../interfaces/sql-crud-config.interface";
3
+ export declare abstract class SqlBaseCrudService<T extends Record<string, any>, CreateDto, UpdateDto, FilterDto> implements ICrudService<T, CreateDto, UpdateDto, FilterDto> {
4
+ protected readonly config: SqlCrudConfig;
5
+ protected readonly defaultConfig: SqlCrudConfig;
6
+ protected buildWhereConditionsFromPartial(where: Partial<T>): any[];
7
+ constructor(config: SqlCrudConfig);
8
+ private validateConfiguration;
9
+ protected abstract validateCreate(data: CreateDto): Promise<void>;
10
+ protected abstract validateUpdate(id: any, data: UpdateDto): Promise<void>;
11
+ protected abstract mapCreateDtoToEntity(data: CreateDto): Record<string, any>;
12
+ protected abstract mapUpdateDtoToEntity(data: UpdateDto): Record<string, any>;
13
+ protected beforeCreate(data: CreateDto): Promise<CreateDto>;
14
+ protected afterCreate(entity: T): Promise<void>;
15
+ protected beforeUpdate(id: any, data: UpdateDto): Promise<UpdateDto>;
16
+ protected afterUpdate(entity: T): Promise<void>;
17
+ protected beforeDelete(id: any): Promise<void>;
18
+ protected afterDelete(id: any): Promise<void>;
19
+ protected beforeSoftDelete(id: any): Promise<void>;
20
+ protected afterSoftDelete(id: any): Promise<void>;
21
+ protected beforeRestore(id: any): Promise<void>;
22
+ protected afterRestore(entity: T): Promise<void>;
23
+ find(id: any, options?: SqlOperationOptions): Promise<T | null>;
24
+ findOne(where: Partial<T>, options?: SqlOperationOptions): Promise<T | null>;
25
+ findAll(filters?: FilterDto, pagination?: PaginationOptions, options?: SqlOperationOptions): Promise<{
26
+ data: T[];
27
+ total: number;
28
+ page: number;
29
+ limit: number;
30
+ }>;
31
+ create(data: CreateDto, options?: SqlOperationOptions): Promise<T>;
32
+ update(id: any, data: UpdateDto, options?: SqlOperationOptions): Promise<T>;
33
+ softDelete(id: any, options?: SqlOperationOptions): Promise<boolean>;
34
+ restore(id: any, options?: SqlOperationOptions): Promise<T>;
35
+ delete(id: any, options?: SqlOperationOptions): Promise<boolean>;
36
+ massCreate(data: CreateDto[], options?: SqlOperationOptions): Promise<T[]>;
37
+ massUpdate(ids: any[], data: UpdateDto, options?: SqlOperationOptions): Promise<T[]>;
38
+ massSoftDelete(ids: any[], options?: SqlOperationOptions): Promise<boolean>;
39
+ massRestore(ids: any[], options?: SqlOperationOptions): Promise<T[]>;
40
+ massDelete(ids: any[], options?: SqlOperationOptions): Promise<boolean>;
41
+ exists(id: any, options?: SqlOperationOptions): Promise<boolean>;
42
+ count(filters?: FilterDto, options?: SqlOperationOptions): Promise<number>;
43
+ protected buildWhereConditions(filters?: any): any[];
44
+ protected applyComplexFilter(conditions: any[], column: any, filterObj: any): void;
45
+ protected applyRelations(query: any, relations: string[]): any;
46
+ protected executeSqlTransaction<R>(operation: (tx: any) => Promise<R>, existingTransaction?: any): Promise<R>;
47
+ protected getEntityName(): string;
48
+ fullTextSearch(searchTerm: string, searchColumns: string[], pagination?: PaginationOptions, options?: SqlOperationOptions): Promise<{
49
+ data: T[];
50
+ total: number;
51
+ }>;
52
+ }
@@ -0,0 +1,536 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SqlBaseCrudService = void 0;
4
+ const drizzle_orm_1 = require("drizzle-orm");
5
+ const crud_exceptions_1 = require("../../exceptions/crud.exceptions");
6
+ class SqlBaseCrudService {
7
+ config;
8
+ defaultConfig = {
9
+ dialect: "postgresql",
10
+ db: null,
11
+ table: null,
12
+ primaryKey: "id",
13
+ primaryKeyType: "serial",
14
+ softDelete: { enabled: true, column: "deleted_at" },
15
+ timestamps: { createdAt: "created_at", updatedAt: "updated_at" },
16
+ pagination: { defaultLimit: 20, maxLimit: 100 },
17
+ sql: {
18
+ caseSensitive: false,
19
+ useReturning: true,
20
+ jsonSupport: true,
21
+ enableFullTextSearch: false,
22
+ },
23
+ };
24
+ buildWhereConditionsFromPartial(where) {
25
+ if (!where)
26
+ return [];
27
+ const conditions = [];
28
+ for (const [key, value] of Object.entries(where)) {
29
+ if (value === undefined || value === null)
30
+ continue;
31
+ const column = this.config.table[key];
32
+ if (!column)
33
+ continue;
34
+ conditions.push((0, drizzle_orm_1.eq)(column, value));
35
+ }
36
+ return conditions;
37
+ }
38
+ constructor(config) {
39
+ this.config = config;
40
+ this.config = { ...this.defaultConfig, ...config };
41
+ this.validateConfiguration();
42
+ }
43
+ validateConfiguration() {
44
+ if (!this.config.db)
45
+ throw new Error("Database instance is required");
46
+ if (!this.config.table)
47
+ throw new Error("Table configuration is required");
48
+ if (this.config.dialect === "mysql" && this.config.sql?.useReturning) {
49
+ console.warn("MySQL does not support RETURNING clause, disabling useReturning");
50
+ this.config.sql.useReturning = false;
51
+ }
52
+ }
53
+ async beforeCreate(data) {
54
+ return data;
55
+ }
56
+ async afterCreate(entity) { }
57
+ async beforeUpdate(id, data) {
58
+ return data;
59
+ }
60
+ async afterUpdate(entity) { }
61
+ async beforeDelete(id) { }
62
+ async afterDelete(id) { }
63
+ async beforeSoftDelete(id) { }
64
+ async afterSoftDelete(id) { }
65
+ async beforeRestore(id) { }
66
+ async afterRestore(entity) { }
67
+ async find(id, options) {
68
+ const { transaction, relations = [], select = [] } = options || {};
69
+ const db = transaction || this.config.db;
70
+ let query = db.select().from(this.config.table);
71
+ const conditions = [(0, drizzle_orm_1.eq)(this.config.table[this.config.primaryKey], id)];
72
+ if (this.config.softDelete?.enabled) {
73
+ conditions.push((0, drizzle_orm_1.isNull)(this.config.table[this.config.softDelete.column]));
74
+ }
75
+ query = query.where((0, drizzle_orm_1.and)(...conditions));
76
+ if (select.length > 0) {
77
+ const fields = select.reduce((acc, field) => {
78
+ acc[field] = this.config.table[field];
79
+ return acc;
80
+ }, {});
81
+ query = query.fields(fields);
82
+ }
83
+ if (relations.length > 0) {
84
+ query = this.applyRelations(query, relations);
85
+ }
86
+ query = query.limit(1);
87
+ const result = await query;
88
+ return result[0] || null;
89
+ }
90
+ async findOne(where, options) {
91
+ const { transaction, relations = [], select = [] } = options || {};
92
+ const db = transaction || this.config.db;
93
+ let query = db.select().from(this.config.table);
94
+ const conditions = this.buildWhereConditionsFromPartial(where);
95
+ if (this.config.softDelete?.enabled) {
96
+ conditions.push((0, drizzle_orm_1.isNull)(this.config.table[this.config.softDelete.column]));
97
+ }
98
+ query = query.where((0, drizzle_orm_1.and)(...conditions));
99
+ if (select.length > 0) {
100
+ const fields = select.reduce((acc, field) => {
101
+ acc[field] = this.config.table[field];
102
+ return acc;
103
+ }, {});
104
+ query = query.fields(fields);
105
+ }
106
+ if (relations.length > 0) {
107
+ query = this.applyRelations(query, relations);
108
+ }
109
+ query = query.limit(1);
110
+ const result = await query;
111
+ return result[0] || null;
112
+ }
113
+ async findAll(filters, pagination, options) {
114
+ const { transaction, relations = [], select = [] } = options || {};
115
+ const { page = 1, limit = this.config.pagination.defaultLimit, sortBy, sortOrder = "desc", } = pagination || {};
116
+ const safeLimit = Math.min(limit, this.config.pagination.maxLimit);
117
+ const offset = (page - 1) * safeLimit;
118
+ const db = transaction || this.config.db;
119
+ let dataQuery = db.select().from(this.config.table);
120
+ const conditions = this.buildWhereConditions(filters);
121
+ if (this.config.softDelete?.enabled) {
122
+ conditions.push((0, drizzle_orm_1.isNull)(this.config.table[this.config.softDelete.column]));
123
+ }
124
+ if (conditions.length > 0) {
125
+ dataQuery = dataQuery.where((0, drizzle_orm_1.and)(...conditions));
126
+ }
127
+ if (sortBy && this.config.table[sortBy]) {
128
+ dataQuery = dataQuery.orderBy(sortOrder === "desc"
129
+ ? (0, drizzle_orm_1.desc)(this.config.table[sortBy])
130
+ : (0, drizzle_orm_1.asc)(this.config.table[sortBy]));
131
+ }
132
+ dataQuery = dataQuery.limit(safeLimit).offset(offset);
133
+ if (relations.length > 0) {
134
+ dataQuery = this.applyRelations(dataQuery, relations);
135
+ }
136
+ if (select.length > 0) {
137
+ const fields = select.reduce((acc, field) => {
138
+ acc[field] = this.config.table[field];
139
+ return acc;
140
+ }, {});
141
+ dataQuery = dataQuery.fields(fields);
142
+ }
143
+ let countQuery = db
144
+ .select({ count: (0, drizzle_orm_1.sql) `count(*)` })
145
+ .from(this.config.table);
146
+ if (conditions.length > 0) {
147
+ countQuery = countQuery.where((0, drizzle_orm_1.and)(...conditions));
148
+ }
149
+ const [data, totalResult] = await Promise.all([dataQuery, countQuery]);
150
+ const total = parseInt(totalResult[0]?.count?.toString() || "0");
151
+ return { data, total, page, limit: safeLimit };
152
+ }
153
+ async create(data, options) {
154
+ const { transaction, hooks = {} } = options || {};
155
+ await this.validateCreate(data);
156
+ const processedData = hooks.skipBefore
157
+ ? data
158
+ : await this.beforeCreate(data);
159
+ const entityData = this.mapCreateDtoToEntity(processedData);
160
+ const now = new Date();
161
+ if (this.config.timestamps?.createdAt)
162
+ entityData[this.config.timestamps.createdAt] = now;
163
+ if (this.config.timestamps?.updatedAt)
164
+ entityData[this.config.timestamps.updatedAt] = now;
165
+ const db = transaction || this.config.db;
166
+ let insertQuery = db.insert(this.config.table).values(entityData);
167
+ if (this.config.sql?.useReturning) {
168
+ insertQuery = insertQuery.returning();
169
+ const result = await insertQuery;
170
+ const createdEntity = result[0];
171
+ if (!hooks.skipAfter)
172
+ await this.afterCreate(createdEntity);
173
+ return createdEntity;
174
+ }
175
+ else {
176
+ const result = await insertQuery;
177
+ const lastInsertId = result[0].insertId;
178
+ const createdEntity = await this.find(lastInsertId, options);
179
+ if (!createdEntity)
180
+ throw new Error("Failed to create entity");
181
+ if (!hooks.skipAfter)
182
+ await this.afterCreate(createdEntity);
183
+ return createdEntity;
184
+ }
185
+ }
186
+ async update(id, data, options) {
187
+ const { transaction, hooks = {} } = options || {};
188
+ const existing = await this.find(id, options);
189
+ if (!existing)
190
+ throw new crud_exceptions_1.EntityNotFoundException(this.getEntityName(), id);
191
+ await this.validateUpdate(id, data);
192
+ const processedData = hooks.skipBefore
193
+ ? data
194
+ : await this.beforeUpdate(id, data);
195
+ const entityData = this.mapUpdateDtoToEntity(processedData);
196
+ if (this.config.timestamps?.updatedAt) {
197
+ entityData[this.config.timestamps.updatedAt] = new Date();
198
+ }
199
+ const db = transaction || this.config.db;
200
+ let updateQuery = db
201
+ .update(this.config.table)
202
+ .set(entityData)
203
+ .where((0, drizzle_orm_1.eq)(this.config.table[this.config.primaryKey], id));
204
+ if (this.config.sql?.useReturning) {
205
+ updateQuery = updateQuery.returning();
206
+ const result = await updateQuery;
207
+ const updatedEntity = result[0];
208
+ if (!hooks.skipAfter)
209
+ await this.afterUpdate(updatedEntity);
210
+ return updatedEntity;
211
+ }
212
+ else {
213
+ await updateQuery;
214
+ const updatedEntity = await this.find(id, options);
215
+ if (!updatedEntity)
216
+ throw new Error("Failed to update entity");
217
+ if (!hooks.skipAfter)
218
+ await this.afterUpdate(updatedEntity);
219
+ return updatedEntity;
220
+ }
221
+ }
222
+ async softDelete(id, options) {
223
+ if (!this.config.softDelete?.enabled) {
224
+ throw new Error("Soft delete is not enabled for this entity");
225
+ }
226
+ const { transaction, hooks = {} } = options || {};
227
+ const existing = await this.find(id, options);
228
+ if (!existing)
229
+ throw new crud_exceptions_1.EntityNotFoundException(this.getEntityName(), id);
230
+ if (!hooks.skipBefore)
231
+ await this.beforeSoftDelete(id);
232
+ const db = transaction || this.config.db;
233
+ const updateData = {
234
+ [this.config.softDelete.column]: new Date(),
235
+ ...(this.config.timestamps?.updatedAt && {
236
+ [this.config.timestamps.updatedAt]: new Date(),
237
+ }),
238
+ };
239
+ let updateQuery = db
240
+ .update(this.config.table)
241
+ .set(updateData)
242
+ .where((0, drizzle_orm_1.eq)(this.config.table[this.config.primaryKey], id));
243
+ if (this.config.sql?.useReturning)
244
+ updateQuery = updateQuery.returning();
245
+ const result = await updateQuery;
246
+ const success = Array.isArray(result)
247
+ ? result.length > 0
248
+ : result[0].affectedRows > 0;
249
+ if (success && !hooks.skipAfter)
250
+ await this.afterSoftDelete(id);
251
+ return success;
252
+ }
253
+ async restore(id, options) {
254
+ if (!this.config.softDelete?.enabled) {
255
+ throw new Error("Soft delete is not enabled for this entity");
256
+ }
257
+ const { transaction, hooks = {} } = options || {};
258
+ if (!hooks.skipBefore)
259
+ await this.beforeRestore(id);
260
+ const db = transaction || this.config.db;
261
+ const updateData = {
262
+ [this.config.softDelete.column]: null,
263
+ ...(this.config.timestamps?.updatedAt && {
264
+ [this.config.timestamps.updatedAt]: new Date(),
265
+ }),
266
+ };
267
+ let updateQuery = db
268
+ .update(this.config.table)
269
+ .set(updateData)
270
+ .where((0, drizzle_orm_1.eq)(this.config.table[this.config.primaryKey], id));
271
+ if (this.config.sql?.useReturning) {
272
+ updateQuery = updateQuery.returning();
273
+ const result = await updateQuery;
274
+ if (result.length === 0)
275
+ throw new crud_exceptions_1.EntityNotFoundException(this.getEntityName(), id);
276
+ const restoredEntity = result[0];
277
+ if (!hooks.skipAfter)
278
+ await this.afterRestore(restoredEntity);
279
+ return restoredEntity;
280
+ }
281
+ else {
282
+ await updateQuery;
283
+ const restoredEntity = await this.find(id, options);
284
+ if (!restoredEntity)
285
+ throw new crud_exceptions_1.EntityNotFoundException(this.getEntityName(), id);
286
+ if (!hooks.skipAfter)
287
+ await this.afterRestore(restoredEntity);
288
+ return restoredEntity;
289
+ }
290
+ }
291
+ async delete(id, options) {
292
+ const { transaction, hooks = {} } = options || {};
293
+ const existing = await this.find(id, options);
294
+ if (!existing)
295
+ throw new crud_exceptions_1.EntityNotFoundException(this.getEntityName(), id);
296
+ if (!hooks.skipBefore)
297
+ await this.beforeDelete(id);
298
+ const db = transaction || this.config.db;
299
+ const deleteQuery = db
300
+ .delete(this.config.table)
301
+ .where((0, drizzle_orm_1.eq)(this.config.table[this.config.primaryKey], id));
302
+ const result = await deleteQuery;
303
+ const success = Array.isArray(result)
304
+ ? result.length > 0
305
+ : result[0].affectedRows > 0;
306
+ if (success && !hooks.skipAfter)
307
+ await this.afterDelete(id);
308
+ return success;
309
+ }
310
+ async massCreate(data, options) {
311
+ return this.executeSqlTransaction(async (tx) => {
312
+ const results = [];
313
+ const errors = [];
314
+ for (let i = 0; i < data.length; i++) {
315
+ try {
316
+ const result = await this.create(data[i], {
317
+ ...options,
318
+ transaction: tx,
319
+ });
320
+ results.push(result);
321
+ }
322
+ catch (error) {
323
+ errors.push({ index: i, error: error });
324
+ }
325
+ }
326
+ if (errors.length > 0)
327
+ throw new crud_exceptions_1.BulkOperationException("Mass create errors", errors);
328
+ return results;
329
+ }, options?.transaction);
330
+ }
331
+ async massUpdate(ids, data, options) {
332
+ return this.executeSqlTransaction(async (tx) => {
333
+ const results = [];
334
+ const errors = [];
335
+ for (const id of ids) {
336
+ try {
337
+ const result = await this.update(id, data, {
338
+ ...options,
339
+ transaction: tx,
340
+ });
341
+ results.push(result);
342
+ }
343
+ catch (error) {
344
+ errors.push({ id, error: error });
345
+ }
346
+ }
347
+ if (errors.length > 0)
348
+ throw new crud_exceptions_1.BulkOperationException("Mass update errors", errors);
349
+ return results;
350
+ }, options?.transaction);
351
+ }
352
+ async massSoftDelete(ids, options) {
353
+ if (!this.config.softDelete?.enabled)
354
+ throw new Error("Soft delete not enabled");
355
+ return this.executeSqlTransaction(async (tx) => {
356
+ const errors = [];
357
+ for (const id of ids) {
358
+ try {
359
+ await this.softDelete(id, { ...options, transaction: tx });
360
+ }
361
+ catch (error) {
362
+ errors.push({ id, error: error });
363
+ }
364
+ }
365
+ if (errors.length > 0)
366
+ throw new crud_exceptions_1.BulkOperationException("Mass soft delete errors", errors);
367
+ return true;
368
+ }, options?.transaction);
369
+ }
370
+ async massRestore(ids, options) {
371
+ if (!this.config.softDelete?.enabled)
372
+ throw new Error("Soft delete not enabled");
373
+ return this.executeSqlTransaction(async (tx) => {
374
+ const results = [];
375
+ const errors = [];
376
+ for (const id of ids) {
377
+ try {
378
+ const result = await this.restore(id, {
379
+ ...options,
380
+ transaction: tx,
381
+ });
382
+ results.push(result);
383
+ }
384
+ catch (error) {
385
+ errors.push({ id, error: error });
386
+ }
387
+ }
388
+ if (errors.length > 0)
389
+ throw new crud_exceptions_1.BulkOperationException("Mass restore errors", errors);
390
+ return results;
391
+ }, options?.transaction);
392
+ }
393
+ async massDelete(ids, options) {
394
+ return this.executeSqlTransaction(async (tx) => {
395
+ const errors = [];
396
+ for (const id of ids) {
397
+ try {
398
+ await this.delete(id, { ...options, transaction: tx });
399
+ }
400
+ catch (error) {
401
+ errors.push({ id, error: error });
402
+ }
403
+ }
404
+ if (errors.length > 0)
405
+ throw new crud_exceptions_1.BulkOperationException("Mass delete errors", errors);
406
+ return true;
407
+ }, options?.transaction);
408
+ }
409
+ async exists(id, options) {
410
+ const entity = await this.find(id, {
411
+ ...options,
412
+ select: [this.config.primaryKey],
413
+ });
414
+ return !!entity;
415
+ }
416
+ async count(filters, options) {
417
+ const { transaction } = options || {};
418
+ const db = transaction || this.config.db;
419
+ let query = db
420
+ .select({ count: (0, drizzle_orm_1.sql) `count(*)` })
421
+ .from(this.config.table);
422
+ const conditions = this.buildWhereConditions(filters);
423
+ if (this.config.softDelete?.enabled) {
424
+ conditions.push((0, drizzle_orm_1.isNull)(this.config.table[this.config.softDelete.column]));
425
+ }
426
+ if (conditions.length > 0)
427
+ query = query.where((0, drizzle_orm_1.and)(...conditions));
428
+ const result = await query;
429
+ return parseInt(result[0]?.count?.toString() || "0");
430
+ }
431
+ buildWhereConditions(filters) {
432
+ if (!filters)
433
+ return [];
434
+ const conditions = [];
435
+ for (const [key, value] of Object.entries(filters)) {
436
+ if (value === undefined || value === null)
437
+ continue;
438
+ const column = this.config.table[key];
439
+ if (!column)
440
+ continue;
441
+ if (Array.isArray(value)) {
442
+ conditions.push((0, drizzle_orm_1.inArray)(column, value));
443
+ }
444
+ else if (typeof value === "string" &&
445
+ this.config.sql?.caseSensitive === false) {
446
+ conditions.push((0, drizzle_orm_1.ilike)(column, `%${value}%`));
447
+ }
448
+ else if (typeof value === "object" && value !== null) {
449
+ this.applyComplexFilter(conditions, column, value);
450
+ }
451
+ else {
452
+ conditions.push((0, drizzle_orm_1.eq)(column, value));
453
+ }
454
+ }
455
+ return conditions;
456
+ }
457
+ applyComplexFilter(conditions, column, filterObj) {
458
+ for (const [op, value] of Object.entries(filterObj)) {
459
+ switch (op) {
460
+ case "gt":
461
+ conditions.push((0, drizzle_orm_1.gt)(column, value));
462
+ break;
463
+ case "gte":
464
+ conditions.push((0, drizzle_orm_1.gte)(column, value));
465
+ break;
466
+ case "lt":
467
+ conditions.push((0, drizzle_orm_1.lt)(column, value));
468
+ break;
469
+ case "lte":
470
+ conditions.push((0, drizzle_orm_1.lte)(column, value));
471
+ break;
472
+ case "neq":
473
+ conditions.push((0, drizzle_orm_1.ne)(column, value));
474
+ break;
475
+ case "like":
476
+ conditions.push((0, drizzle_orm_1.like)(column, `%${value}%`));
477
+ break;
478
+ case "ilike":
479
+ conditions.push((0, drizzle_orm_1.ilike)(column, `%${value}%`));
480
+ break;
481
+ case "in":
482
+ conditions.push((0, drizzle_orm_1.inArray)(column, value));
483
+ break;
484
+ case "isNull":
485
+ conditions.push((0, drizzle_orm_1.isNull)(column));
486
+ break;
487
+ case "isNotNull":
488
+ conditions.push((0, drizzle_orm_1.isNotNull)(column));
489
+ break;
490
+ }
491
+ }
492
+ }
493
+ applyRelations(query, relations) {
494
+ return query;
495
+ }
496
+ async executeSqlTransaction(operation, existingTransaction) {
497
+ if (existingTransaction)
498
+ return operation(existingTransaction);
499
+ return this.config.db.transaction(operation);
500
+ }
501
+ getEntityName() {
502
+ return this.config.table.constructor.name || "Entity";
503
+ }
504
+ async fullTextSearch(searchTerm, searchColumns, pagination, options) {
505
+ if (this.config.dialect !== "postgresql") {
506
+ throw new Error("Full-text search only supported in PostgreSQL");
507
+ }
508
+ const { transaction } = options || {};
509
+ const db = transaction || this.config.db;
510
+ const tsVectorColumns = searchColumns
511
+ .map((col) => (0, drizzle_orm_1.sql) `to_tsvector('english', ${this.config.table[col]})`)
512
+ .join(" || ");
513
+ const tsQuery = (0, drizzle_orm_1.sql) `plainto_tsquery('english', ${searchTerm})`;
514
+ let query = db
515
+ .select()
516
+ .from(this.config.table)
517
+ .where((0, drizzle_orm_1.sql) `${drizzle_orm_1.sql.raw(tsVectorColumns)} @@ ${tsQuery}`)
518
+ .orderBy((0, drizzle_orm_1.sql) `ts_rank(${drizzle_orm_1.sql.raw(tsVectorColumns)}, ${tsQuery}) DESC`);
519
+ if (pagination) {
520
+ const { page = 1, limit = this.config.pagination.defaultLimit } = pagination;
521
+ const safeLimit = Math.min(limit, this.config.pagination.maxLimit);
522
+ const offset = (page - 1) * safeLimit;
523
+ query = query.limit(safeLimit).offset(offset);
524
+ }
525
+ const data = await query;
526
+ const countQuery = db
527
+ .select({ count: (0, drizzle_orm_1.sql) `count(*)` })
528
+ .from(this.config.table)
529
+ .where((0, drizzle_orm_1.sql) `${drizzle_orm_1.sql.raw(tsVectorColumns)} @@ ${tsQuery}`);
530
+ const totalResult = await countQuery;
531
+ const total = parseInt(totalResult[0]?.count?.toString() || "0");
532
+ return { data, total };
533
+ }
534
+ }
535
+ exports.SqlBaseCrudService = SqlBaseCrudService;
536
+ //# sourceMappingURL=sql-base-crud.service.js.map