@nest-util/nest-crud 0.0.3 → 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 (48) hide show
  1. package/README.md +13 -3
  2. package/dist/index.d.ts +11 -1
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +11 -4
  5. package/dist/lib/controllers/nest-crud.controller.d.ts +2 -1
  6. package/dist/lib/controllers/nest-crud.controller.d.ts.map +1 -1
  7. package/dist/lib/controllers/nest-crud.controller.js +41 -5
  8. package/dist/lib/decorators/audit-log.decorator.d.ts +7 -0
  9. package/dist/lib/decorators/audit-log.decorator.d.ts.map +1 -0
  10. package/dist/lib/decorators/audit-log.decorator.js +7 -0
  11. package/dist/lib/dtos/cursor-pagination.dto.d.ts +6 -0
  12. package/dist/lib/dtos/cursor-pagination.dto.d.ts.map +1 -0
  13. package/dist/lib/dtos/cursor-pagination.dto.js +47 -0
  14. package/dist/lib/dtos/list-audit-logs.dto.d.ts +8 -0
  15. package/dist/lib/dtos/list-audit-logs.dto.d.ts.map +1 -0
  16. package/dist/lib/dtos/list-audit-logs.dto.js +65 -0
  17. package/dist/lib/entities/audit-log.entity.d.ts +13 -0
  18. package/dist/lib/entities/audit-log.entity.d.ts.map +1 -0
  19. package/dist/lib/entities/audit-log.entity.js +58 -0
  20. package/dist/lib/helpers/cursor-pagination.helper.d.ts +9 -0
  21. package/dist/lib/helpers/cursor-pagination.helper.d.ts.map +1 -0
  22. package/dist/lib/helpers/cursor-pagination.helper.js +93 -0
  23. package/dist/lib/interceptors/audit-log.interceptor.d.ts +12 -0
  24. package/dist/lib/interceptors/audit-log.interceptor.d.ts.map +1 -0
  25. package/dist/lib/interceptors/audit-log.interceptor.js +72 -0
  26. package/dist/lib/interfaces/audit-log.interface.d.ts +11 -0
  27. package/dist/lib/interfaces/audit-log.interface.d.ts.map +1 -0
  28. package/dist/lib/interfaces/audit-log.interface.js +2 -0
  29. package/dist/lib/interfaces/crud.interface.d.ts +16 -1
  30. package/dist/lib/interfaces/crud.interface.d.ts.map +1 -1
  31. package/dist/lib/interfaces/cursor-strategy.interface.d.ts +30 -0
  32. package/dist/lib/interfaces/cursor-strategy.interface.d.ts.map +1 -0
  33. package/dist/lib/interfaces/cursor-strategy.interface.js +2 -0
  34. package/dist/lib/interfaces/find-mine.interface.d.ts +6 -0
  35. package/dist/lib/interfaces/find-mine.interface.d.ts.map +1 -0
  36. package/dist/lib/interfaces/find-mine.interface.js +2 -0
  37. package/dist/lib/interfaces/hooks.interface.d.ts +52 -0
  38. package/dist/lib/interfaces/hooks.interface.d.ts.map +1 -0
  39. package/dist/lib/interfaces/hooks.interface.js +2 -0
  40. package/dist/lib/nest-crud.module.d.ts.map +1 -1
  41. package/dist/lib/nest-crud.module.js +6 -3
  42. package/dist/lib/services/audit-log.service.d.ts +43 -0
  43. package/dist/lib/services/audit-log.service.d.ts.map +1 -0
  44. package/dist/lib/services/audit-log.service.js +154 -0
  45. package/dist/lib/services/nest-crud.service.d.ts +23 -4
  46. package/dist/lib/services/nest-crud.service.d.ts.map +1 -1
  47. package/dist/lib/services/nest-crud.service.js +165 -9
  48. package/package.json +14 -5
@@ -3,9 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.NestCrudService = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const common_1 = require("@nestjs/common");
6
- const nest_audit_1 = require("@nest-util/nest-audit");
6
+ const audit_log_entity_1 = require("../entities/audit-log.entity");
7
7
  const filter_helper_1 = require("../helpers/filter.helper");
8
8
  const pagination_helper_1 = require("../helpers/pagination.helper");
9
+ const cursor_pagination_helper_1 = require("../helpers/cursor-pagination.helper");
9
10
  let NestCrudService = class NestCrudService {
10
11
  constructor(options) {
11
12
  this.repo = options.repository;
@@ -17,6 +18,12 @@ let NestCrudService = class NestCrudService {
17
18
  this.createDtoClass = options.createDtoClass;
18
19
  this.updateDtoClass = options.updateDtoClass;
19
20
  this.disabledEndpoints = options.disabledEndpoints ?? [];
21
+ this.cursorStrategy =
22
+ options.cursorStrategy ?? (0, cursor_pagination_helper_1.detectCursorStrategy)(this.repo);
23
+ this.hooks = options.hooks ?? {};
24
+ this.transactionConfig = options.transactionConfig ?? {};
25
+ this.userOwnershipField = options.userOwnershipField;
26
+ this.findMineQuery = options.findMineQuery;
20
27
  }
21
28
  async resolveRelations(payload) {
22
29
  if (!this.relations.length)
@@ -39,6 +46,32 @@ let NestCrudService = class NestCrudService {
39
46
  }
40
47
  return payload;
41
48
  }
49
+ async executeInTransaction(fn, isolationLevel) {
50
+ const queryRunner = this.repo.manager.connection.createQueryRunner();
51
+ await queryRunner.startTransaction(isolationLevel);
52
+ try {
53
+ const result = await fn();
54
+ await queryRunner.commitTransaction();
55
+ return result;
56
+ }
57
+ catch (error) {
58
+ await queryRunner.rollbackTransaction();
59
+ throw error;
60
+ }
61
+ finally {
62
+ await queryRunner.release();
63
+ }
64
+ }
65
+ async executeHook(hook, context) {
66
+ if (!hook)
67
+ return;
68
+ if (hook.transaction) {
69
+ await this.executeInTransaction(() => hook.handler(context), this.transactionConfig?.isolationLevel);
70
+ }
71
+ else {
72
+ await hook.handler(context);
73
+ }
74
+ }
42
75
  async findAll(query) {
43
76
  const qb = this.repo.createQueryBuilder('e');
44
77
  if (this.include.length > 0) {
@@ -72,7 +105,115 @@ let NestCrudService = class NestCrudService {
72
105
  ? { data, meta: { ...paginationMeta, total } }
73
106
  : { data };
74
107
  }
108
+ async findMine(userId, query) {
109
+ if (!this.userOwnershipField && !this.findMineQuery) {
110
+ throw new common_1.BadRequestException('findMine not configured');
111
+ }
112
+ const qb = this.repo.createQueryBuilder('e');
113
+ if (this.findMineQuery) {
114
+ this.findMineQuery(qb, userId);
115
+ }
116
+ else if (this.userOwnershipField) {
117
+ qb.where(`e.${String(this.userOwnershipField)} = :userId`, { userId });
118
+ }
119
+ if (this.include.length > 0) {
120
+ this.include.forEach((relation) => {
121
+ const parts = relation.split('.');
122
+ if (parts.length === 1) {
123
+ qb.leftJoinAndSelect(`e.${parts[0]}`, parts[0]);
124
+ }
125
+ else {
126
+ const parentAlias = parts.slice(0, -1).join('_');
127
+ const field = parts[parts.length - 1];
128
+ const alias = parts.join('_');
129
+ qb.leftJoinAndSelect(`${parentAlias}.${field}`, alias);
130
+ }
131
+ });
132
+ }
133
+ (0, filter_helper_1.applyFilters)(qb, query.filter, this.allowedFilters);
134
+ const paginationMeta = (0, pagination_helper_1.applyPagination)(qb, query);
135
+ if (query.orderBy) {
136
+ const orderDirection = query.orderDirection === 'ASC' ? 'ASC' : 'DESC';
137
+ if (this.allowedSortFields.length === 0 ||
138
+ this.allowedSortFields.includes(query.orderBy)) {
139
+ qb.orderBy(`e.${query.orderBy}`, orderDirection);
140
+ }
141
+ }
142
+ const [entities, total] = await qb.getManyAndCount();
143
+ const data = this.toResponseDto
144
+ ? this.toResponseDto(entities)
145
+ : entities;
146
+ return paginationMeta
147
+ ? { data, meta: { ...paginationMeta, total } }
148
+ : { data };
149
+ }
150
+ async findAllWithCursor(query) {
151
+ const limit = query.limit ?? 10;
152
+ const strategy = this.cursorStrategy;
153
+ const orderDirection = 'DESC';
154
+ const qb = this.repo.createQueryBuilder('e');
155
+ // Join relations
156
+ if (this.include.length > 0) {
157
+ this.include.forEach((relation) => {
158
+ const parts = relation.split('.');
159
+ if (parts.length === 1) {
160
+ qb.leftJoinAndSelect(`e.${parts[0]}`, parts[0]);
161
+ }
162
+ else {
163
+ const parentAlias = parts.slice(0, -1).join('_');
164
+ const field = parts[parts.length - 1];
165
+ const alias = parts.join('_');
166
+ qb.leftJoinAndSelect(`${parentAlias}.${field}`, alias);
167
+ }
168
+ });
169
+ }
170
+ // Apply filters
171
+ (0, filter_helper_1.applyFilters)(qb, query.filter, this.allowedFilters);
172
+ // Apply cursor filter
173
+ if (query.cursor) {
174
+ const decoded = (0, cursor_pagination_helper_1.decodeCursor)(query.cursor, strategy);
175
+ (0, cursor_pagination_helper_1.applyCursorFilter)(qb, decoded, strategy, orderDirection);
176
+ }
177
+ // Default ordering by id
178
+ qb.orderBy(`e.id`, orderDirection);
179
+ // Fetch limit + 1 to detect hasMore
180
+ const take = limit + 1;
181
+ const entities = await qb.take(take).getMany();
182
+ const hasMore = entities.length > limit;
183
+ const data = hasMore ? entities.slice(0, limit) : entities;
184
+ // Build next cursor from last entity
185
+ const nextCursor = hasMore ? (0, cursor_pagination_helper_1.buildNextCursor)(data, strategy) : null;
186
+ // Optionally compute total count
187
+ let total;
188
+ if (query.includeTotal) {
189
+ // Build a clean count query (reuse same filters but no cursor/order/take)
190
+ const countQb = this.repo.createQueryBuilder('e');
191
+ if (this.include.length > 0) {
192
+ this.include.forEach((relation) => {
193
+ const parts = relation.split('.');
194
+ if (parts.length === 1) {
195
+ countQb.leftJoin(`e.${parts[0]}`, parts[0]);
196
+ }
197
+ });
198
+ }
199
+ (0, filter_helper_1.applyFilters)(countQb, query.filter, this.allowedFilters);
200
+ total = await countQb.getCount();
201
+ }
202
+ const response = this.toResponseDto
203
+ ? this.toResponseDto(data)
204
+ : data;
205
+ return {
206
+ data: response,
207
+ meta: {
208
+ limit,
209
+ hasMore,
210
+ nextCursor,
211
+ ...(total !== undefined ? { total } : {}),
212
+ },
213
+ };
214
+ }
75
215
  async findOne(id) {
216
+ await this.executeHook(this.hooks.beforeFindOne, { id });
76
217
  const entity = await this.repo.findOne({
77
218
  where: { id },
78
219
  relations: this.include,
@@ -80,16 +221,21 @@ let NestCrudService = class NestCrudService {
80
221
  if (!entity) {
81
222
  throw new common_1.NotFoundException('Resource not found');
82
223
  }
83
- return this.toResponseDto
224
+ const result = this.toResponseDto
84
225
  ? this.toResponseDto(entity)
85
226
  : entity;
227
+ await this.executeHook(this.hooks.afterFindOne, { entity, id });
228
+ return result;
86
229
  }
87
230
  async create(payload) {
231
+ await this.executeHook(this.hooks.beforeCreate, { payload });
88
232
  const resolved = await this.resolveRelations(payload);
89
233
  const entity = await this.repo.save(resolved);
90
- return this.toResponseDto
234
+ const result = this.toResponseDto
91
235
  ? this.toResponseDto(entity)
92
236
  : entity;
237
+ await this.executeHook(this.hooks.afterCreate, { entity, payload });
238
+ return result;
93
239
  }
94
240
  async update(id, payload) {
95
241
  const existing = await this.repo.findOneBy({
@@ -98,22 +244,32 @@ let NestCrudService = class NestCrudService {
98
244
  if (!existing) {
99
245
  throw new common_1.NotFoundException('Resource not found');
100
246
  }
247
+ await this.executeHook(this.hooks.beforeUpdate, { payload, entity: existing, id });
101
248
  const resolved = await this.resolveRelations(payload);
102
- await this.repo.update(id, resolved);
103
- return this.findOne(id);
249
+ this.repo.merge(existing, resolved);
250
+ await this.repo.save(existing);
251
+ const result = await this.findOne(id);
252
+ await this.executeHook(this.hooks.afterUpdate, { entity: result, payload, id });
253
+ return result;
104
254
  }
105
255
  async remove(id) {
106
- const result = await this.repo.delete(id);
107
- if (result.affected === 0) {
256
+ const existing = await this.repo.findOneBy({
257
+ id,
258
+ });
259
+ if (!existing) {
108
260
  throw new common_1.NotFoundException('Resource not found');
109
261
  }
110
- return true;
262
+ await this.executeHook(this.hooks.beforeRemove, { entity: existing, id });
263
+ const result = await this.repo.delete(id);
264
+ const deleted = result.affected !== 0;
265
+ await this.executeHook(this.hooks.afterRemove, { id, deleted });
266
+ return deleted;
111
267
  }
112
268
  async findAuditLogs(query) {
113
269
  const page = query.page ?? 1;
114
270
  const limit = query.limit ?? 10;
115
271
  const qb = this.repo.manager
116
- .getRepository(nest_audit_1.AuditLogEntity)
272
+ .getRepository(audit_log_entity_1.AuditLogEntity)
117
273
  .createQueryBuilder('auditLog')
118
274
  .where('auditLog.entity = :entity', {
119
275
  entity: this.repo.metadata.name,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nest-util/nest-crud",
3
- "version": "0.0.3",
3
+ "version": "1.0.0",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -18,17 +18,26 @@
18
18
  "!**/*.tsbuildinfo"
19
19
  ],
20
20
  "dependencies": {
21
- "tslib": "^2.3.0"
21
+ "tslib": "^2.8.1"
22
+ },
23
+ "devDependencies": {
24
+ "@nest-util/nest-auth": "workspace:*"
22
25
  },
23
26
  "peerDependencies": {
24
- "@nest-util/nest-audit": "workspace:*",
27
+ "@nest-util/nest-auth": "workspace:*",
25
28
  "@nestjs/common": "^11.0.0",
26
29
  "@nestjs/core": "^11.0.0",
27
30
  "@nestjs/swagger": "^11.2.6",
31
+ "@nestjs/typeorm": "^11.0.1",
28
32
  "class-transformer": "^0.5.1",
29
33
  "class-validator": "^0.14.3",
30
34
  "express": "^5.2.1",
31
- "typeorm": "^0.3.28",
32
- "rxjs": "^7.8.0"
35
+ "rxjs": "^7.8.0",
36
+ "typeorm": "^1.1.0"
37
+ },
38
+ "peerDependenciesMeta": {
39
+ "@nest-util/nest-auth": {
40
+ "optional": true
41
+ }
33
42
  }
34
43
  }