@opra/mongodb 1.0.0-alpha.9 → 1.0.0-beta.2

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.
@@ -4,6 +4,7 @@ exports.MongoNestedService = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const common_1 = require("@opra/common");
6
6
  const lodash_omit_1 = tslib_1.__importDefault(require("lodash.omit"));
7
+ const valgen_1 = require("valgen");
7
8
  const mongo_adapter_js_1 = require("./mongo-adapter.js");
8
9
  const mongo_service_js_1 = require("./mongo-service.js");
9
10
  /**
@@ -25,7 +26,7 @@ class MongoNestedService extends mongo_service_js_1.MongoService {
25
26
  this.fieldName = fieldName;
26
27
  this.nestedKey = options?.nestedKey || '_id';
27
28
  this.defaultLimit = options?.defaultLimit || 10;
28
- this.$nestedFilter = options?.$nestedFilter;
29
+ this.nestedFilter = options?.nestedFilter;
29
30
  }
30
31
  /**
31
32
  * Retrieves the data type of the array field
@@ -54,51 +55,81 @@ class MongoNestedService extends mongo_service_js_1.MongoService {
54
55
  throw new common_1.ResourceNotAvailableError(this.getResourceName() + '.' + this.nestedKey, documentId + '/' + id);
55
56
  }
56
57
  }
58
+ async create(documentId, input, options) {
59
+ const command = {
60
+ crud: 'create',
61
+ method: 'create',
62
+ byId: false,
63
+ documentId,
64
+ input,
65
+ options,
66
+ };
67
+ input[this.nestedKey] = input[this.nestedKey] ?? this._generateId(command);
68
+ return this._executeCommand(command, async () => {
69
+ const r = await this._create(command);
70
+ if (!options?.projection)
71
+ return r;
72
+ const findCommand = {
73
+ crud: 'read',
74
+ method: 'findById',
75
+ byId: true,
76
+ documentId,
77
+ nestedId: r[this.nestedKey],
78
+ options: {
79
+ ...options,
80
+ sort: undefined,
81
+ filter: undefined,
82
+ skip: undefined,
83
+ },
84
+ };
85
+ const out = await this._findById(findCommand);
86
+ if (out)
87
+ return out;
88
+ });
89
+ }
57
90
  /**
58
91
  * Adds a single item into the array field.
59
92
  *
60
93
  * @param {MongoAdapter.AnyId} documentId - The ID of the parent document.
61
- * @param {T} input - The item to be added to the array field.
94
+ * @param {DTO<T>} input - The item to be added to the array field.
62
95
  * @param {MongoNestedService.CreateOptions} [options] - Optional options for the create operation.
63
- * @return {Promise<PartialDTO<T>>} - A promise that resolves with the partial output of the created item.
96
+ * @return {Promise<PartialDTO<T>>} - A promise that resolves create operation result
64
97
  * @throws {ResourceNotAvailableError} - If the parent document is not found.
65
98
  */
66
- async create(documentId, input, options) {
67
- const id = input._id || this._generateId();
68
- if (id != null)
69
- input._id = id;
70
- const info = {
99
+ async createOnly(documentId, input, options) {
100
+ const command = {
71
101
  crud: 'create',
72
102
  method: 'create',
73
103
  byId: false,
74
104
  documentId,
75
- nestedId: id,
76
105
  input,
77
106
  options,
78
107
  };
79
- return this._intercept(() => this._create(documentId, input, options), info);
108
+ input[this.nestedKey] = input[this.nestedKey] ?? this._generateId(command);
109
+ return this._executeCommand(command, () => this._create(command));
80
110
  }
81
- async _create(documentId, input, options) {
82
- const inputCodec = this.getInputCodec('create');
83
- const doc = inputCodec(input);
84
- doc._id = doc._id || this._generateId();
111
+ async _create(command) {
112
+ const inputCodec = this._getInputCodec('create');
113
+ const { documentId, options } = command;
114
+ const input = command.input;
115
+ (0, valgen_1.isNotNullish)(input, { label: 'input' });
116
+ (0, valgen_1.isNotNullish)(input[this.nestedKey], { label: `input.${this.nestedKey}` });
117
+ const document = inputCodec(input);
85
118
  const docFilter = mongo_adapter_js_1.MongoAdapter.prepareKeyValues(documentId, ['_id']);
86
- const r = await this._dbUpdateOne(docFilter, {
87
- $push: { [this.fieldName]: doc },
88
- }, options);
89
- if (r.matchedCount) {
90
- if (!options)
91
- return doc;
92
- const id = doc[this.nestedKey];
93
- const out = await this._findById(documentId, id, {
94
- ...options,
95
- filter: undefined,
96
- skip: undefined,
97
- });
98
- if (out)
99
- return out;
119
+ const db = this.getDatabase();
120
+ const collection = await this.getCollection(db);
121
+ const update = {
122
+ $push: { [this.fieldName]: document },
123
+ };
124
+ const r = await collection.updateOne(docFilter, update, {
125
+ ...options,
126
+ session: options?.session || this.getSession(),
127
+ upsert: undefined,
128
+ });
129
+ if (!r.matchedCount) {
130
+ throw new common_1.ResourceNotAvailableError(this.getResourceName(), documentId);
100
131
  }
101
- throw new common_1.ResourceNotAvailableError(this.getResourceName(), documentId);
132
+ return document;
102
133
  }
103
134
  /**
104
135
  * Counts the number of documents in the collection that match the specified parentId and options.
@@ -108,20 +139,22 @@ class MongoNestedService extends mongo_service_js_1.MongoService {
108
139
  * @returns {Promise<number>} - A promise that resolves to the count of documents.
109
140
  */
110
141
  async count(documentId, options) {
111
- const info = {
142
+ const command = {
112
143
  crud: 'read',
113
144
  method: 'count',
114
145
  byId: false,
115
146
  documentId,
116
147
  options,
117
148
  };
118
- return this._intercept(async () => {
119
- const documentFilter = mongo_adapter_js_1.MongoAdapter.prepareFilter([await this._getDocumentFilter(info)]);
120
- const filter = mongo_adapter_js_1.MongoAdapter.prepareFilter([await this._getNestedFilter(info), options?.filter]);
121
- return this._count(documentId, { ...options, filter, documentFilter });
122
- }, info);
149
+ return this._executeCommand(command, async () => {
150
+ const documentFilter = mongo_adapter_js_1.MongoAdapter.prepareFilter([await this._getDocumentFilter(command)]);
151
+ const filter = mongo_adapter_js_1.MongoAdapter.prepareFilter([await this._getNestedFilter(command), command.options?.filter]);
152
+ command.options = { ...command.options, filter, documentFilter };
153
+ return this._count(command);
154
+ });
123
155
  }
124
- async _count(documentId, options) {
156
+ async _count(command) {
157
+ const { documentId, options } = command;
125
158
  const matchFilter = mongo_adapter_js_1.MongoAdapter.prepareFilter([
126
159
  mongo_adapter_js_1.MongoAdapter.prepareKeyValues(documentId, ['_id']),
127
160
  options?.documentFilter,
@@ -136,13 +169,18 @@ class MongoNestedService extends mongo_service_js_1.MongoService {
136
169
  stages.push({ $match: filter });
137
170
  }
138
171
  stages.push({ $count: '*' });
139
- const r = await this._dbAggregate(stages, options);
172
+ const db = this.getDatabase();
173
+ const collection = await this.getCollection(db);
174
+ const cursor = collection.aggregate(stages, {
175
+ ...(0, lodash_omit_1.default)(options, ['documentFilter', 'nestedFilter', 'projection', 'sort', 'skip', 'limit', 'filter', 'count']),
176
+ session: options?.session || this.getSession(),
177
+ });
140
178
  try {
141
- const n = await r.next();
179
+ const n = await cursor.next();
142
180
  return n?.['*'] || 0;
143
181
  }
144
182
  finally {
145
- await r.close();
183
+ await cursor.close();
146
184
  }
147
185
  }
148
186
  /**
@@ -154,7 +192,7 @@ class MongoNestedService extends mongo_service_js_1.MongoService {
154
192
  * @return {Promise<number>} - A Promise that resolves to the number of elements deleted (1 if successful, 0 if not).
155
193
  */
156
194
  async delete(documentId, nestedId, options) {
157
- const info = {
195
+ const command = {
158
196
  crud: 'delete',
159
197
  method: 'delete',
160
198
  byId: true,
@@ -162,21 +200,32 @@ class MongoNestedService extends mongo_service_js_1.MongoService {
162
200
  nestedId,
163
201
  options,
164
202
  };
165
- return this._intercept(async () => {
166
- const documentFilter = mongo_adapter_js_1.MongoAdapter.prepareFilter([await this._getDocumentFilter(info)]);
167
- const filter = mongo_adapter_js_1.MongoAdapter.prepareFilter([await this._getNestedFilter(info), options?.filter]);
168
- return this._delete(documentId, nestedId, { ...options, filter, documentFilter });
169
- }, info);
203
+ return this._executeCommand(command, async () => {
204
+ const documentFilter = mongo_adapter_js_1.MongoAdapter.prepareFilter([await this._getDocumentFilter(command)]);
205
+ const filter = mongo_adapter_js_1.MongoAdapter.prepareFilter([await this._getNestedFilter(command), command.options?.filter]);
206
+ command.options = { ...command.options, filter, documentFilter };
207
+ return this._delete(command);
208
+ });
170
209
  }
171
- async _delete(documentId, nestedId, options) {
210
+ async _delete(command) {
211
+ const { documentId, nestedId, options } = command;
212
+ (0, valgen_1.isNotNullish)(documentId, { label: 'documentId' });
213
+ (0, valgen_1.isNotNullish)(documentId, { label: 'nestedId' });
172
214
  const matchFilter = mongo_adapter_js_1.MongoAdapter.prepareFilter([
173
215
  mongo_adapter_js_1.MongoAdapter.prepareKeyValues(documentId, ['_id']),
174
216
  options?.documentFilter,
175
217
  ]);
176
218
  const pullFilter = mongo_adapter_js_1.MongoAdapter.prepareFilter([mongo_adapter_js_1.MongoAdapter.prepareKeyValues(nestedId, [this.nestedKey]), options?.filter]) || {};
177
- const r = await this._dbUpdateOne(matchFilter, {
219
+ const update = {
178
220
  $pull: { [this.fieldName]: pullFilter },
179
- }, options);
221
+ };
222
+ const db = this.getDatabase();
223
+ const collection = await this.getCollection(db);
224
+ const r = await collection.updateOne(matchFilter, update, {
225
+ ...options,
226
+ session: options?.session || this.getSession(),
227
+ upsert: undefined,
228
+ });
180
229
  return r.modifiedCount ? 1 : 0;
181
230
  }
182
231
  /**
@@ -187,33 +236,47 @@ class MongoNestedService extends mongo_service_js_1.MongoService {
187
236
  * @returns {Promise<number>} - A Promise that resolves to the number of items deleted.
188
237
  */
189
238
  async deleteMany(documentId, options) {
190
- const info = {
239
+ const command = {
191
240
  crud: 'delete',
192
241
  method: 'deleteMany',
193
242
  byId: false,
194
243
  documentId,
195
244
  options,
196
245
  };
197
- return this._intercept(async () => {
198
- const documentFilter = mongo_adapter_js_1.MongoAdapter.prepareFilter([await this._getDocumentFilter(info)]);
199
- const filter = mongo_adapter_js_1.MongoAdapter.prepareFilter([await this._getNestedFilter(info), options?.filter]);
200
- return this._deleteMany(documentId, { ...options, filter, documentFilter });
201
- }, info);
246
+ return this._executeCommand(command, async () => {
247
+ const documentFilter = mongo_adapter_js_1.MongoAdapter.prepareFilter([await this._getDocumentFilter(command)]);
248
+ const filter = mongo_adapter_js_1.MongoAdapter.prepareFilter([await this._getNestedFilter(command), command.options?.filter]);
249
+ command.options = { ...command.options, filter, documentFilter };
250
+ return this._deleteMany(command);
251
+ });
202
252
  }
203
- async _deleteMany(documentId, options) {
253
+ async _deleteMany(command) {
254
+ const { documentId, options } = command;
204
255
  const matchFilter = mongo_adapter_js_1.MongoAdapter.prepareFilter([
205
256
  mongo_adapter_js_1.MongoAdapter.prepareKeyValues(documentId, ['_id']),
206
257
  options?.documentFilter,
207
258
  ]);
208
259
  // Count matching items, we will use this as result
209
- const matchCount = await this.count(documentId, options);
260
+ const countCommand = {
261
+ crud: 'read',
262
+ method: 'count',
263
+ byId: false,
264
+ documentId,
265
+ options,
266
+ };
267
+ const matchCount = await this._count(countCommand);
210
268
  const pullFilter = mongo_adapter_js_1.MongoAdapter.prepareFilter(options?.filter) || {};
211
- const r = await this._dbUpdateOne(matchFilter, {
269
+ const update = {
212
270
  $pull: { [this.fieldName]: pullFilter },
213
- }, options);
214
- if (r.matchedCount)
215
- return matchCount;
216
- return 0;
271
+ };
272
+ const db = this.getDatabase();
273
+ const collection = await this.getCollection(db);
274
+ await collection.updateOne(matchFilter, update, {
275
+ ...options,
276
+ session: options?.session || this.getSession(),
277
+ upsert: undefined,
278
+ });
279
+ return matchCount;
217
280
  }
218
281
  /**
219
282
  * Checks if an array element with the given parentId and id exists.
@@ -224,28 +287,50 @@ class MongoNestedService extends mongo_service_js_1.MongoService {
224
287
  * @returns {Promise<boolean>} - A promise that resolves to a boolean indicating if the record exists or not.
225
288
  */
226
289
  async exists(documentId, nestedId, options) {
227
- return !!(await this.findById(documentId, nestedId, { ...options, projection: ['_id'] }));
290
+ const command = {
291
+ crud: 'read',
292
+ method: 'exists',
293
+ byId: true,
294
+ documentId,
295
+ nestedId,
296
+ options,
297
+ };
298
+ return this._executeCommand(command, async () => {
299
+ const documentFilter = mongo_adapter_js_1.MongoAdapter.prepareFilter([await this._getDocumentFilter(command)]);
300
+ const filter = mongo_adapter_js_1.MongoAdapter.prepareFilter([
301
+ await this._getNestedFilter(command),
302
+ documentFilter,
303
+ command.options?.filter,
304
+ ]);
305
+ command.options = { ...command.options, filter };
306
+ return !!(await this._findById(command));
307
+ });
228
308
  }
229
309
  /**
230
310
  * Checks if an object with the given arguments exists.
231
311
  *
232
312
  * @param {MongoAdapter.AnyId} documentId - The ID of the parent document.
233
- * @param {MongoNestedService.ExistsOneOptions} [options] - The options for the query (optional).
313
+ * @param {MongoNestedService.ExistsOptions} [options] - The options for the query (optional).
234
314
  * @return {Promise<boolean>} - A Promise that resolves to a boolean indicating whether the object exists or not.
235
315
  */
236
316
  async existsOne(documentId, options) {
237
- return !!(await this.findOne(documentId, { ...options, projection: ['_id'] }));
317
+ const command = {
318
+ crud: 'read',
319
+ method: 'exists',
320
+ byId: false,
321
+ documentId,
322
+ options,
323
+ };
324
+ return this._executeCommand(command, async () => {
325
+ const documentFilter = await this._getDocumentFilter(command);
326
+ const filter = mongo_adapter_js_1.MongoAdapter.prepareFilter([documentFilter, command.options?.filter]);
327
+ const findCommand = command;
328
+ findCommand.options = { ...command.options, filter, documentFilter, projection: ['_id'] };
329
+ return !!(await this._findOne(findCommand));
330
+ });
238
331
  }
239
- /**
240
- * Finds an element in array field by its parent ID and ID.
241
- *
242
- * @param {MongoAdapter.AnyId} documentId - The ID of the document.
243
- * @param {MongoAdapter.AnyId} nestedId - The ID of the document.
244
- * @param {MongoNestedService.FindOneOptions<T>} [options] - The optional options for the operation.
245
- * @returns {Promise<PartialDTO<T> | undefined>} - A promise that resolves to the found document or undefined if not found.
246
- */
247
332
  async findById(documentId, nestedId, options) {
248
- const info = {
333
+ const command = {
249
334
  crud: 'read',
250
335
  method: 'findById',
251
336
  byId: true,
@@ -253,95 +338,96 @@ class MongoNestedService extends mongo_service_js_1.MongoService {
253
338
  nestedId,
254
339
  options,
255
340
  };
256
- return this._intercept(async () => {
257
- const documentFilter = mongo_adapter_js_1.MongoAdapter.prepareFilter([await this._getDocumentFilter(info)]);
258
- const filter = mongo_adapter_js_1.MongoAdapter.prepareFilter([await this._getNestedFilter(info), options?.filter]);
259
- return this._findById(documentId, nestedId, { ...options, filter, documentFilter });
260
- }, info);
341
+ return this._executeCommand(command, async () => {
342
+ const documentFilter = mongo_adapter_js_1.MongoAdapter.prepareFilter([await this._getDocumentFilter(command)]);
343
+ const filter = mongo_adapter_js_1.MongoAdapter.prepareFilter([await this._getNestedFilter(command), command.options?.filter]);
344
+ command.options = { ...command.options, filter, documentFilter };
345
+ return this._findById(command);
346
+ });
261
347
  }
262
- async _findById(documentId, nestedId, options) {
348
+ async _findById(command) {
349
+ const { documentId, nestedId, options } = command;
350
+ (0, valgen_1.isNotNullish)(documentId, { label: 'documentId' });
351
+ (0, valgen_1.isNotNullish)(nestedId, { label: 'nestedId' });
263
352
  const filter = mongo_adapter_js_1.MongoAdapter.prepareFilter([
264
353
  mongo_adapter_js_1.MongoAdapter.prepareKeyValues(nestedId, [this.nestedKey]),
265
354
  options?.filter,
266
355
  ]);
267
- const rows = await this._findMany(documentId, {
268
- ...options,
269
- filter,
270
- limit: 1,
271
- skip: undefined,
272
- sort: undefined,
273
- });
356
+ const findManyCommand = {
357
+ ...command,
358
+ options: {
359
+ ...options,
360
+ filter,
361
+ limit: 1,
362
+ skip: undefined,
363
+ sort: undefined,
364
+ },
365
+ };
366
+ const rows = await this._findMany(findManyCommand);
274
367
  return rows?.[0];
275
368
  }
276
- /**
277
- * Finds the first array element that matches the given parentId.
278
- *
279
- * @param {MongoAdapter.AnyId} documentId - The ID of the document.
280
- * @param {MongoNestedService.FindOneOptions<T>} [options] - Optional options to customize the query.
281
- * @returns {Promise<PartialDTO<T> | undefined>} A promise that resolves to the first matching document, or `undefined` if no match is found.
282
- */
283
369
  async findOne(documentId, options) {
284
- const info = {
370
+ const command = {
285
371
  crud: 'read',
286
372
  method: 'findOne',
287
373
  byId: false,
288
374
  documentId,
289
375
  options,
290
376
  };
291
- return this._intercept(async () => {
292
- const documentFilter = mongo_adapter_js_1.MongoAdapter.prepareFilter([await this._getDocumentFilter(info)]);
293
- const filter = mongo_adapter_js_1.MongoAdapter.prepareFilter([await this._getNestedFilter(info), options?.filter]);
294
- return this._findOne(documentId, { ...options, filter, documentFilter });
295
- }, info);
296
- }
297
- async _findOne(documentId, options) {
298
- const rows = await this._findMany(documentId, {
299
- ...options,
300
- limit: 1,
377
+ return this._executeCommand(command, async () => {
378
+ const documentFilter = mongo_adapter_js_1.MongoAdapter.prepareFilter([await this._getDocumentFilter(command)]);
379
+ const filter = mongo_adapter_js_1.MongoAdapter.prepareFilter([await this._getNestedFilter(command), command.options?.filter]);
380
+ command.options = { ...command.options, filter, documentFilter };
381
+ return this._findOne(command);
301
382
  });
383
+ }
384
+ async _findOne(command) {
385
+ const { documentId, options } = command;
386
+ (0, valgen_1.isNotNullish)(documentId, { label: 'documentId' });
387
+ const findManyCommand = {
388
+ ...command,
389
+ options: {
390
+ ...options,
391
+ limit: 1,
392
+ },
393
+ };
394
+ const rows = await this._findMany(findManyCommand);
302
395
  return rows?.[0];
303
396
  }
304
- /**
305
- * Finds multiple elements in an array field.
306
- *
307
- * @param {MongoAdapter.AnyId} documentId - The ID of the parent document.
308
- * @param {MongoNestedService.FindManyOptions<T>} [options] - The options for finding the documents.
309
- * @returns {Promise<PartialDTO<T>[]>} - The found documents.
310
- */
311
397
  async findMany(documentId, options) {
312
- const args = {
398
+ const command = {
313
399
  crud: 'read',
314
400
  method: 'findMany',
315
401
  byId: false,
316
402
  documentId,
317
403
  options,
318
404
  };
319
- return this._intercept(async () => {
320
- const documentFilter = await this._getDocumentFilter(args);
321
- const nestedFilter = await this._getNestedFilter(args);
322
- return this._findMany(documentId, {
323
- ...options,
324
- documentFilter,
405
+ return this._executeCommand(command, async () => {
406
+ const documentFilter = await this._getDocumentFilter(command);
407
+ const nestedFilter = await this._getNestedFilter(command);
408
+ command.options = {
409
+ ...command.options,
325
410
  nestedFilter,
326
- limit: options?.limit || this.defaultLimit,
327
- });
328
- }, args);
411
+ documentFilter,
412
+ limit: command.options?.limit || this.defaultLimit,
413
+ };
414
+ return this._findMany(command);
415
+ });
329
416
  }
330
- async _findMany(documentId, options) {
417
+ async _findMany(command) {
418
+ const { documentId, options } = command;
419
+ (0, valgen_1.isNotNullish)(documentId, { label: 'documentId' });
331
420
  const matchFilter = mongo_adapter_js_1.MongoAdapter.prepareFilter([
332
421
  mongo_adapter_js_1.MongoAdapter.prepareKeyValues(documentId, ['_id']),
333
- options.documentFilter,
422
+ options?.documentFilter,
334
423
  ]);
335
- const mongoOptions = {
336
- ...(0, lodash_omit_1.default)(options, ['documentFilter', 'nestedFilter', 'projection', 'sort', 'skip', 'limit', 'filter', 'count']),
337
- };
338
424
  const limit = options?.limit || this.defaultLimit;
339
425
  const stages = [
340
426
  { $match: matchFilter },
341
427
  { $unwind: { path: '$' + this.fieldName } },
342
428
  { $replaceRoot: { newRoot: '$' + this.fieldName } },
343
429
  ];
344
- if (options?.filter || options.nestedFilter) {
430
+ if (options?.filter || options?.nestedFilter) {
345
431
  const optionsFilter = mongo_adapter_js_1.MongoAdapter.prepareFilter([options?.filter, options.nestedFilter]);
346
432
  stages.push({ $match: optionsFilter });
347
433
  }
@@ -357,51 +443,48 @@ class MongoNestedService extends mongo_service_js_1.MongoService {
357
443
  const projection = mongo_adapter_js_1.MongoAdapter.prepareProjection(dataType, options?.projection);
358
444
  if (projection)
359
445
  stages.push({ $project: projection });
360
- const cursor = await this._dbAggregate(stages, mongoOptions);
446
+ const db = this.getDatabase();
447
+ const collection = await this.getCollection(db);
448
+ const cursor = collection.aggregate(stages, {
449
+ ...(0, lodash_omit_1.default)(options, ['documentFilter', 'nestedFilter', 'projection', 'sort', 'skip', 'limit', 'filter', 'count']),
450
+ session: options?.session || this.getSession(),
451
+ });
361
452
  try {
362
- const outputCodec = this.getOutputCodec('find');
363
- const out = await (await cursor.toArray()).map((r) => outputCodec(r));
364
- return out;
453
+ const outputCodec = this._getOutputCodec('find');
454
+ return (await cursor.toArray()).map((r) => outputCodec(r));
365
455
  }
366
456
  finally {
367
457
  if (!cursor.closed)
368
458
  await cursor.close();
369
459
  }
370
460
  }
371
- /**
372
- * Finds multiple elements in an array field.
373
- *
374
- * @param {MongoAdapter.AnyId} documentId - The ID of the parent document.
375
- * @param {MongoNestedService.FindManyOptions<T>} [options] - The options for finding the documents.
376
- * @returns {Promise<PartialDTO<T>[]>} - The found documents.
377
- */
378
461
  async findManyWithCount(documentId, options) {
379
- const args = {
462
+ const command = {
380
463
  crud: 'read',
381
464
  method: 'findMany',
382
465
  byId: false,
383
466
  documentId,
384
467
  options,
385
468
  };
386
- return this._intercept(async () => {
387
- const documentFilter = await this._getDocumentFilter(args);
388
- const nestedFilter = await this._getNestedFilter(args);
389
- return this._findManyWithCount(documentId, {
390
- ...options,
391
- documentFilter,
469
+ return this._executeCommand(command, async () => {
470
+ const documentFilter = await this._getDocumentFilter(command);
471
+ const nestedFilter = await this._getNestedFilter(command);
472
+ command.options = {
473
+ ...command.options,
392
474
  nestedFilter,
393
- limit: options?.limit || this.defaultLimit,
394
- });
395
- }, args);
475
+ documentFilter,
476
+ limit: command.options?.limit || this.defaultLimit,
477
+ };
478
+ return this._findManyWithCount(command);
479
+ });
396
480
  }
397
- async _findManyWithCount(documentId, options) {
481
+ async _findManyWithCount(command) {
482
+ const { documentId, options } = command;
483
+ (0, valgen_1.isNotNullish)(documentId, { label: 'documentId' });
398
484
  const matchFilter = mongo_adapter_js_1.MongoAdapter.prepareFilter([
399
485
  mongo_adapter_js_1.MongoAdapter.prepareKeyValues(documentId, ['_id']),
400
- options.documentFilter,
486
+ options?.documentFilter,
401
487
  ]);
402
- const mongoOptions = {
403
- ...(0, lodash_omit_1.default)(options, ['pick', 'include', 'omit', 'sort', 'skip', 'limit', 'filter', 'count']),
404
- };
405
488
  const limit = options?.limit || this.defaultLimit;
406
489
  const dataStages = [];
407
490
  const stages = [
@@ -415,8 +498,8 @@ class MongoNestedService extends mongo_service_js_1.MongoService {
415
498
  },
416
499
  },
417
500
  ];
418
- if (options?.filter || options.nestedFilter) {
419
- const optionsFilter = mongo_adapter_js_1.MongoAdapter.prepareFilter([options?.filter, options.nestedFilter]);
501
+ if (options?.filter || options?.nestedFilter) {
502
+ const optionsFilter = mongo_adapter_js_1.MongoAdapter.prepareFilter([options?.filter, options?.nestedFilter]);
420
503
  dataStages.push({ $match: optionsFilter });
421
504
  }
422
505
  if (options?.skip)
@@ -431,12 +514,15 @@ class MongoNestedService extends mongo_service_js_1.MongoService {
431
514
  const projection = mongo_adapter_js_1.MongoAdapter.prepareProjection(dataType, options?.projection);
432
515
  if (projection)
433
516
  dataStages.push({ $project: projection });
434
- const cursor = await this._dbAggregate(stages, {
435
- ...mongoOptions,
517
+ const db = this.getDatabase();
518
+ const collection = await this.getCollection(db);
519
+ const cursor = collection.aggregate(stages, {
520
+ ...(0, lodash_omit_1.default)(options, ['documentFilter', 'nestedFilter', 'projection', 'sort', 'skip', 'limit', 'filter', 'count']),
521
+ session: options?.session || this.getSession(),
436
522
  });
437
523
  try {
438
524
  const facetResult = await cursor.toArray();
439
- const outputCodec = this.getOutputCodec('find');
525
+ const outputCodec = this._getOutputCodec('find');
440
526
  return {
441
527
  count: facetResult[0].count[0].totalMatches || 0,
442
528
  items: facetResult[0].data.map((r) => outputCodec(r)),
@@ -447,15 +533,6 @@ class MongoNestedService extends mongo_service_js_1.MongoService {
447
533
  await cursor.close();
448
534
  }
449
535
  }
450
- /**
451
- * Retrieves a specific item from the array of a document.
452
- *
453
- * @param {MongoAdapter.AnyId} documentId - The ID of the document.
454
- * @param {MongoAdapter.AnyId} nestedId - The ID of the item.
455
- * @param {MongoNestedService.FindOneOptions<T>} [options] - The options for finding the item.
456
- * @returns {Promise<PartialDTO<T>>} - The item found.
457
- * @throws {ResourceNotAvailableError} - If the item is not found.
458
- */
459
536
  async get(documentId, nestedId, options) {
460
537
  const out = await this.findById(documentId, nestedId, options);
461
538
  if (!out) {
@@ -463,27 +540,40 @@ class MongoNestedService extends mongo_service_js_1.MongoService {
463
540
  }
464
541
  return out;
465
542
  }
466
- /**
467
- * Updates an array element with new data and returns the updated element
468
- *
469
- * @param {AnyId} documentId - The ID of the document to update.
470
- * @param {AnyId} nestedId - The ID of the item to update within the document.
471
- * @param {PatchDTO<T>} input - The new data to update the item with.
472
- * @param {MongoNestedService.UpdateOptions<T>} [options] - Additional update options.
473
- * @returns {Promise<PartialDTO<T> | undefined>} The updated item or undefined if it does not exist.
474
- * @throws {Error} If an error occurs while updating the item.
475
- */
476
543
  async update(documentId, nestedId, input, options) {
477
- const r = await this.updateOnly(documentId, nestedId, input, options);
478
- if (!r)
479
- return;
480
- const out = await this._findById(documentId, nestedId, {
481
- ...options,
482
- sort: undefined,
544
+ const command = {
545
+ crud: 'update',
546
+ method: 'update',
547
+ byId: true,
548
+ documentId,
549
+ nestedId,
550
+ input,
551
+ options,
552
+ };
553
+ return this._executeCommand(command, async () => {
554
+ const documentFilter = mongo_adapter_js_1.MongoAdapter.prepareFilter([await this._getDocumentFilter(command)]);
555
+ const filter = mongo_adapter_js_1.MongoAdapter.prepareFilter([await this._getNestedFilter(command), command.options?.filter]);
556
+ command.options = {
557
+ ...command.options,
558
+ filter,
559
+ documentFilter,
560
+ };
561
+ const matchCount = await this._updateOnly(command);
562
+ if (matchCount) {
563
+ const findCommand = {
564
+ crud: 'read',
565
+ method: 'findById',
566
+ byId: true,
567
+ documentId,
568
+ nestedId,
569
+ options: { ...options, sort: undefined },
570
+ };
571
+ const out = this._findById(findCommand);
572
+ if (out)
573
+ return out;
574
+ }
575
+ throw new common_1.ResourceNotAvailableError(this.getResourceName() + '.' + this.nestedKey, documentId + '/' + nestedId);
483
576
  });
484
- if (out)
485
- return out;
486
- throw new common_1.ResourceNotAvailableError(this.getResourceName() + '.' + this.nestedKey, documentId + '/' + nestedId);
487
577
  }
488
578
  /**
489
579
  * Update an array element with new data. Returns 1 if document updated 0 otherwise.
@@ -491,29 +581,45 @@ class MongoNestedService extends mongo_service_js_1.MongoService {
491
581
  * @param {MongoAdapter.AnyId} documentId - The ID of the parent document.
492
582
  * @param {MongoAdapter.AnyId} nestedId - The ID of the document to update.
493
583
  * @param {PatchDTO<T>} input - The partial input object containing the fields to update.
494
- * @param {MongoNestedService.UpdateOptions<T>} [options] - Optional update options.
584
+ * @param {MongoNestedService.UpdateOneOptions<T>} [options] - Optional update options.
495
585
  * @returns {Promise<number>} - A promise that resolves to the number of elements updated.
496
586
  */
497
587
  async updateOnly(documentId, nestedId, input, options) {
498
- const info = {
588
+ const command = {
499
589
  crud: 'update',
500
590
  method: 'update',
501
591
  byId: true,
502
592
  documentId,
503
593
  nestedId,
594
+ input,
504
595
  options,
505
596
  };
506
- return this._intercept(async () => {
507
- const documentFilter = mongo_adapter_js_1.MongoAdapter.prepareFilter([await this._getDocumentFilter(info)]);
508
- const filter = mongo_adapter_js_1.MongoAdapter.prepareFilter([await this._getNestedFilter(info), options?.filter]);
509
- return this._updateOnly(documentId, nestedId, input, { ...options, filter, documentFilter });
510
- }, info);
597
+ return this._executeCommand(command, async () => {
598
+ const documentFilter = mongo_adapter_js_1.MongoAdapter.prepareFilter([await this._getDocumentFilter(command)]);
599
+ const filter = mongo_adapter_js_1.MongoAdapter.prepareFilter([await this._getNestedFilter(command), command.options?.filter]);
600
+ command.options = {
601
+ ...command.options,
602
+ filter,
603
+ documentFilter,
604
+ };
605
+ return await this._updateOnly(command);
606
+ });
511
607
  }
512
- async _updateOnly(documentId, nestedId, input, options) {
608
+ async _updateOnly(command) {
609
+ const { documentId, nestedId, options } = command;
610
+ (0, valgen_1.isNotNullish)(documentId, { label: 'documentId' });
611
+ (0, valgen_1.isNotNullish)(nestedId, { label: 'nestedId' });
513
612
  let filter = mongo_adapter_js_1.MongoAdapter.prepareKeyValues(nestedId, [this.nestedKey]);
514
613
  if (options?.filter)
515
614
  filter = mongo_adapter_js_1.MongoAdapter.prepareFilter([filter, options?.filter]);
516
- return await this._updateMany(documentId, input, { ...options, filter });
615
+ const updateManyCommand = {
616
+ ...command,
617
+ options: {
618
+ ...command.options,
619
+ filter,
620
+ },
621
+ };
622
+ return await this._updateMany(updateManyCommand);
517
623
  }
518
624
  /**
519
625
  * Updates multiple array elements in document
@@ -524,7 +630,7 @@ class MongoNestedService extends mongo_service_js_1.MongoService {
524
630
  * @returns {Promise<number>} - A promise that resolves to the number of documents updated.
525
631
  */
526
632
  async updateMany(documentId, input, options) {
527
- const info = {
633
+ const command = {
528
634
  crud: 'update',
529
635
  method: 'updateMany',
530
636
  documentId,
@@ -532,14 +638,18 @@ class MongoNestedService extends mongo_service_js_1.MongoService {
532
638
  input,
533
639
  options,
534
640
  };
535
- return this._intercept(async () => {
536
- const documentFilter = mongo_adapter_js_1.MongoAdapter.prepareFilter([await this._getDocumentFilter(info)]);
537
- const filter = mongo_adapter_js_1.MongoAdapter.prepareFilter([await this._getNestedFilter(info), options?.filter]);
538
- return this._updateMany(documentId, input, { ...options, filter, documentFilter });
539
- }, info);
540
- }
541
- async _updateMany(documentId, input, options) {
542
- const inputCodec = this.getInputCodec('update');
641
+ return this._executeCommand(command, async () => {
642
+ const documentFilter = mongo_adapter_js_1.MongoAdapter.prepareFilter([await this._getDocumentFilter(command)]);
643
+ const filter = mongo_adapter_js_1.MongoAdapter.prepareFilter([await this._getNestedFilter(command), command.options?.filter]);
644
+ command.options = { ...command.options, filter, documentFilter };
645
+ return this._updateMany(command);
646
+ });
647
+ }
648
+ async _updateMany(command) {
649
+ const { documentId, input } = command;
650
+ (0, valgen_1.isNotNullish)(documentId, { label: 'documentId' });
651
+ const options = { ...command.options };
652
+ const inputCodec = this._getInputCodec('update');
543
653
  const doc = inputCodec(input);
544
654
  if (!Object.keys(doc).length)
545
655
  return 0;
@@ -550,27 +660,122 @@ class MongoNestedService extends mongo_service_js_1.MongoService {
550
660
  ]);
551
661
  if (options?.filter) {
552
662
  const elemMatch = mongo_adapter_js_1.MongoAdapter.prepareFilter([options?.filter], { fieldPrefix: 'elem.' });
553
- options = options || {};
554
663
  options.arrayFilters = [elemMatch];
555
664
  }
556
665
  const update = mongo_adapter_js_1.MongoAdapter.preparePatch(doc, {
557
666
  fieldPrefix: this.fieldName + (options?.filter ? '.$[elem].' : '.$[].'),
558
667
  });
559
- const r = await this._dbUpdateOne(matchFilter, update, options);
560
- if (options?.count)
561
- return await this._count(documentId, options);
562
- return r.modifiedCount || 0;
668
+ // Count matching items, we will use this as result
669
+ const count = await this._count({
670
+ crud: 'read',
671
+ method: 'count',
672
+ byId: false,
673
+ documentId,
674
+ options,
675
+ });
676
+ const db = this.getDatabase();
677
+ const collection = await this.getCollection(db);
678
+ await collection.updateOne(matchFilter, update, {
679
+ ...options,
680
+ session: options?.session || this.getSession(),
681
+ upsert: undefined,
682
+ });
683
+ return count;
563
684
  }
564
685
  /**
565
686
  * Retrieves the common filter used for querying array elements.
566
687
  * This method is mostly used for security issues like securing multi-tenant applications.
567
688
  *
568
689
  * @protected
569
- * @returns {FilterInput | Promise<FilterInput> | undefined} The common filter or a Promise
690
+ * @returns {MongoAdapter.FilterInput | Promise<MongoAdapter.FilterInput> | undefined} The common filter or a Promise
570
691
  * that resolves to the common filter, or undefined if not available.
571
692
  */
572
693
  _getNestedFilter(args) {
573
- return typeof this.$nestedFilter === 'function' ? this.$nestedFilter(args, this) : this.$nestedFilter;
694
+ return typeof this.nestedFilter === 'function' ? this.nestedFilter(args, this) : this.nestedFilter;
695
+ }
696
+ async _executeCommand(command, commandFn) {
697
+ try {
698
+ const result = await super._executeCommand(command, async () => {
699
+ /** Call before[X] hooks */
700
+ if (command.crud === 'create')
701
+ await this._beforeCreate(command);
702
+ else if (command.crud === 'update' && command.byId) {
703
+ await this._beforeUpdate(command);
704
+ }
705
+ else if (command.crud === 'update' && !command.byId) {
706
+ await this._beforeUpdateMany(command);
707
+ }
708
+ else if (command.crud === 'delete' && command.byId) {
709
+ await this._beforeDelete(command);
710
+ }
711
+ else if (command.crud === 'delete' && !command.byId) {
712
+ await this._beforeDeleteMany(command);
713
+ }
714
+ /** Call command function */
715
+ return commandFn();
716
+ });
717
+ /** Call after[X] hooks */
718
+ if (command.crud === 'create')
719
+ await this._afterCreate(command, result);
720
+ else if (command.crud === 'update' && command.byId) {
721
+ await this._afterUpdate(command, result);
722
+ }
723
+ else if (command.crud === 'update' && !command.byId) {
724
+ await this._afterUpdateMany(command, result);
725
+ }
726
+ else if (command.crud === 'delete' && command.byId) {
727
+ await this._afterDelete(command, result);
728
+ }
729
+ else if (command.crud === 'delete' && !command.byId) {
730
+ await this._afterDeleteMany(command, result);
731
+ }
732
+ return result;
733
+ }
734
+ catch (e) {
735
+ Error.captureStackTrace(e, this._executeCommand);
736
+ await this.onError?.(e, this);
737
+ throw e;
738
+ }
739
+ }
740
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
741
+ async _beforeCreate(command) {
742
+ // Do nothing
743
+ }
744
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
745
+ async _beforeUpdate(command) {
746
+ // Do nothing
747
+ }
748
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
749
+ async _beforeUpdateMany(command) {
750
+ // Do nothing
751
+ }
752
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
753
+ async _beforeDelete(command) {
754
+ // Do nothing
755
+ }
756
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
757
+ async _beforeDeleteMany(command) {
758
+ // Do nothing
759
+ }
760
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
761
+ async _afterCreate(command, result) {
762
+ // Do nothing
763
+ }
764
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
765
+ async _afterUpdate(command, result) {
766
+ // Do nothing
767
+ }
768
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
769
+ async _afterUpdateMany(command, affected) {
770
+ // Do nothing
771
+ }
772
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
773
+ async _afterDelete(command, affected) {
774
+ // Do nothing
775
+ }
776
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
777
+ async _afterDeleteMany(command, affected) {
778
+ // Do nothing
574
779
  }
575
780
  }
576
781
  exports.MongoNestedService = MongoNestedService;