@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.
- package/README.md +13 -3
- package/dist/index.d.ts +11 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -4
- package/dist/lib/controllers/nest-crud.controller.d.ts +2 -1
- package/dist/lib/controllers/nest-crud.controller.d.ts.map +1 -1
- package/dist/lib/controllers/nest-crud.controller.js +41 -5
- package/dist/lib/decorators/audit-log.decorator.d.ts +7 -0
- package/dist/lib/decorators/audit-log.decorator.d.ts.map +1 -0
- package/dist/lib/decorators/audit-log.decorator.js +7 -0
- package/dist/lib/dtos/cursor-pagination.dto.d.ts +6 -0
- package/dist/lib/dtos/cursor-pagination.dto.d.ts.map +1 -0
- package/dist/lib/dtos/cursor-pagination.dto.js +47 -0
- package/dist/lib/dtos/list-audit-logs.dto.d.ts +8 -0
- package/dist/lib/dtos/list-audit-logs.dto.d.ts.map +1 -0
- package/dist/lib/dtos/list-audit-logs.dto.js +65 -0
- package/dist/lib/entities/audit-log.entity.d.ts +13 -0
- package/dist/lib/entities/audit-log.entity.d.ts.map +1 -0
- package/dist/lib/entities/audit-log.entity.js +58 -0
- package/dist/lib/helpers/cursor-pagination.helper.d.ts +9 -0
- package/dist/lib/helpers/cursor-pagination.helper.d.ts.map +1 -0
- package/dist/lib/helpers/cursor-pagination.helper.js +93 -0
- package/dist/lib/interceptors/audit-log.interceptor.d.ts +12 -0
- package/dist/lib/interceptors/audit-log.interceptor.d.ts.map +1 -0
- package/dist/lib/interceptors/audit-log.interceptor.js +72 -0
- package/dist/lib/interfaces/audit-log.interface.d.ts +11 -0
- package/dist/lib/interfaces/audit-log.interface.d.ts.map +1 -0
- package/dist/lib/interfaces/audit-log.interface.js +2 -0
- package/dist/lib/interfaces/crud.interface.d.ts +16 -1
- package/dist/lib/interfaces/crud.interface.d.ts.map +1 -1
- package/dist/lib/interfaces/cursor-strategy.interface.d.ts +30 -0
- package/dist/lib/interfaces/cursor-strategy.interface.d.ts.map +1 -0
- package/dist/lib/interfaces/cursor-strategy.interface.js +2 -0
- package/dist/lib/interfaces/find-mine.interface.d.ts +6 -0
- package/dist/lib/interfaces/find-mine.interface.d.ts.map +1 -0
- package/dist/lib/interfaces/find-mine.interface.js +2 -0
- package/dist/lib/interfaces/hooks.interface.d.ts +52 -0
- package/dist/lib/interfaces/hooks.interface.d.ts.map +1 -0
- package/dist/lib/interfaces/hooks.interface.js +2 -0
- package/dist/lib/nest-crud.module.d.ts.map +1 -1
- package/dist/lib/nest-crud.module.js +6 -3
- package/dist/lib/services/audit-log.service.d.ts +43 -0
- package/dist/lib/services/audit-log.service.d.ts.map +1 -0
- package/dist/lib/services/audit-log.service.js +154 -0
- package/dist/lib/services/nest-crud.service.d.ts +23 -4
- package/dist/lib/services/nest-crud.service.d.ts.map +1 -1
- package/dist/lib/services/nest-crud.service.js +165 -9
- 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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
103
|
-
|
|
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
|
|
107
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
+
"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.
|
|
21
|
+
"tslib": "^2.8.1"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@nest-util/nest-auth": "workspace:*"
|
|
22
25
|
},
|
|
23
26
|
"peerDependencies": {
|
|
24
|
-
"@nest-util/nest-
|
|
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
|
-
"
|
|
32
|
-
"
|
|
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
|
}
|