@opra/mongodb 0.32.2 → 0.32.4

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.
@@ -12,9 +12,13 @@ const opMap = {
12
12
  '!in': '$nin',
13
13
  };
14
14
  /**
15
- * Convert filter expressions to MongoDB filter objects.
16
- * This method also merges multiple expressions into one single filter object
17
- * @param filters
15
+ * Prepare the MongoDB filter based on the provided filters and options.
16
+ *
17
+ * @param {FilterInput|FilterInput[]} filters - The filter(s) to be applied.
18
+ * @param {Object} [options] - Additional options.
19
+ * @param {string} [options.fieldPrefix] - The prefix to be added to field names.
20
+ *
21
+ * @returns {mongodb.Filter<any>} - The prepared MongoDB filter.
18
22
  */
19
23
  function prepareFilter(filters, options) {
20
24
  const filtersArray = Array.isArray(filters) ? filters : [filters];
@@ -8,74 +8,109 @@ const common_1 = require("@opra/common");
8
8
  const mongo_adapter_js_1 = require("./mongo-adapter.js");
9
9
  const mongo_service_js_1 = require("./mongo-service.js");
10
10
  /**
11
- *
12
- * @class MongoArrayService
11
+ * A class that provides methods to perform operations on an array field in a MongoDB collection.
12
+ * @template T The type of the array item.
13
13
  */
14
14
  class MongoArrayService extends mongo_service_js_1.MongoService {
15
+ /**
16
+ * Constructs a new instance
17
+ *
18
+ * @param {Type | string} dataType - The data type of the array elements.
19
+ * @param {string} fieldName - The name of the field in the document representing the array.
20
+ * @param {MongoArrayService.Options} [options] - The options for the array service.
21
+ * @constructor
22
+ */
15
23
  constructor(dataType, fieldName, options) {
16
24
  super(dataType, options);
17
- this._encoders = {};
18
25
  this.fieldName = fieldName;
19
26
  this.defaultLimit = options?.defaultLimit || 10;
20
27
  this.collectionKey = options?.collectionKey || '_id';
21
28
  this.arrayKey = options?.arrayKey || '_id';
22
- }
23
- getArrayDataType() {
24
- const t = this.getDataType()
25
- .getField(this.fieldName).type;
26
- if (!(t instanceof common_1.ComplexType))
27
- throw new common_1.NotAcceptableError(`Data type "${t.name}" is not a ComplexType`);
28
- return t;
29
+ this.$documentFilter = options?.documentFilter;
30
+ this.$arrayFilter = options?.arrayFilter;
31
+ this.$interceptor = this.$interceptor || options?.interceptor;
29
32
  }
30
33
  /**
31
- * Checks if array item exists. Throws error if not.
34
+ * Asserts whether a resource with the specified parentId and id exists.
35
+ * Throws a ResourceNotFoundError if the resource does not exist.
32
36
  *
33
- * @param parentId
34
- * @param id
37
+ * @param {AnyId} documentId - The ID of the parent document.
38
+ * @param {AnyId} id - The ID of the resource.
39
+ * @param {MongoArrayService.ExistsOptions} [options] - Optional parameters for checking resource existence.
40
+ * @return {Promise<void>} - A promise that resolves with no value upon success.
41
+ * @throws {ResourceNotFoundError} - If the resource does not exist.
35
42
  */
36
- async assert(parentId, id) {
37
- if (!(await this.exists(parentId, id)))
38
- throw new common_1.ResourceNotFoundError((this.resourceName || this.getCollectionName()) + '.' + this.arrayKey, parentId + '/' + id);
43
+ async assert(documentId, id, options) {
44
+ if (!(await this.exists(documentId, id, options)))
45
+ throw new common_1.ResourceNotFoundError(this.getResourceName() + '.' + this.arrayKey, documentId + '/' + id);
39
46
  }
40
47
  /**
41
- * Adds a single item into array field.
48
+ * Adds a single item into the array field.
42
49
  *
43
- * @param parentId
44
- * @param input
45
- * @param options
50
+ * @param {AnyId} documentId - The ID of the parent document.
51
+ * @param {T} input - The item to be added to the array field.
52
+ * @param {MongoArrayService.CreateOptions} [options] - Optional options for the create operation.
53
+ * @return {Promise<PartialDTO<T>>} - A promise that resolves with the partial output of the created item.
54
+ * @throws {ResourceNotFoundError} - If the parent document is not found.
46
55
  */
47
- async create(parentId, input, options) {
48
- const encode = this._getEncoder('create');
56
+ async create(documentId, input, options) {
57
+ if (this.$interceptor && !options?.__interceptor__)
58
+ return this.$interceptor(() => this.create(documentId, input, { ...options, __interceptor__: true }), {
59
+ crud: 'create',
60
+ method: 'create',
61
+ documentId,
62
+ itemId: input._id,
63
+ input,
64
+ options
65
+ }, this);
66
+ const encode = this.getEncoder('create');
49
67
  const doc = encode(input);
50
- doc[this.arrayKey] = doc[this.arrayKey] || this._generateId();
51
- const docFilter = mongo_adapter_js_1.MongoAdapter.prepareKeyValues(parentId, [this.collectionKey]);
68
+ doc._id = doc._id || this._generateId();
69
+ const docFilter = mongo_adapter_js_1.MongoAdapter.prepareKeyValues(documentId, [this.collectionKey]);
52
70
  const r = await this.__updateOne(docFilter, {
53
71
  $push: { [this.fieldName]: doc }
54
72
  }, options);
55
- if (r.modifiedCount)
73
+ if (r.modifiedCount) {
74
+ if (!options)
75
+ return doc;
56
76
  try {
57
- return this.get(parentId, doc[this.arrayKey], { ...options, filter: undefined, skip: undefined });
77
+ return this.get(documentId, doc[this.arrayKey], { ...options, filter: undefined, skip: undefined });
58
78
  }
59
79
  catch (e) {
60
80
  Error.captureStackTrace(e);
61
81
  throw e;
62
82
  }
63
- throw new common_1.ResourceNotFoundError(this.resourceName || this.getCollectionName(), parentId);
83
+ }
84
+ throw new common_1.ResourceNotFoundError(this.getResourceName(), documentId);
64
85
  }
65
86
  /**
66
- * Gets the number of array items matching the filter.
67
- * @param parentId
68
- * @param options
87
+ * Counts the number of documents in the collection that match the specified parentId and options.
88
+ *
89
+ * @param {AnyId} documentId - The ID of the parent document.
90
+ * @param {object} options - Optional parameters for counting.
91
+ * @param {object} options.filter - The filter object to apply to the count operation.
92
+ * @returns {Promise<number>} - A promise that resolves to the count of documents.
69
93
  */
70
- async count(parentId, options) {
71
- const matchFilter = mongo_adapter_js_1.MongoAdapter.prepareKeyValues(parentId, [this.collectionKey]);
94
+ async count(documentId, options) {
95
+ if (this.$interceptor && !options?.__interceptor__)
96
+ return this.$interceptor(() => this.count(documentId, { ...options, __interceptor__: true }), {
97
+ crud: 'read',
98
+ method: 'count',
99
+ documentId,
100
+ options
101
+ }, this);
102
+ const matchFilter = mongo_adapter_js_1.MongoAdapter.prepareFilter([
103
+ mongo_adapter_js_1.MongoAdapter.prepareKeyValues(documentId, [this.collectionKey]),
104
+ await this._getDocumentFilter()
105
+ ]);
72
106
  const stages = [
73
107
  { $match: matchFilter },
74
108
  { $unwind: { path: "$" + this.fieldName } },
75
109
  { $replaceRoot: { newRoot: "$" + this.fieldName } }
76
110
  ];
77
- if (options?.filter) {
78
- const optionsFilter = mongo_adapter_js_1.MongoAdapter.prepareFilter(options.filter);
111
+ const contextArrayFilter = await this._getArrayFilter();
112
+ if (options?.filter || contextArrayFilter) {
113
+ const optionsFilter = mongo_adapter_js_1.MongoAdapter.prepareFilter([options?.filter, contextArrayFilter]);
79
114
  stages.push({ $match: optionsFilter });
80
115
  }
81
116
  stages.push({ $count: '*' });
@@ -89,35 +124,62 @@ class MongoArrayService extends mongo_service_js_1.MongoService {
89
124
  }
90
125
  }
91
126
  /**
92
- * Removes one item from an array field
127
+ * Deletes an element from an array within a document in the MongoDB collection.
93
128
  *
94
- * @param parentId
95
- * @param id
96
- * @param options
129
+ * @param {AnyId} documentId - The ID of the parent document.
130
+ * @param {AnyId} id - The ID of the element to delete from the array.
131
+ * @param {MongoArrayService.DeleteOptions<T>} [options] - Additional options for the delete operation.
132
+ * @return {Promise<number>} - A Promise that resolves to the number of elements deleted (1 if successful, 0 if not).
97
133
  */
98
- async delete(parentId, id, options) {
99
- const matchFilter = mongo_adapter_js_1.MongoAdapter.prepareKeyValues(parentId, [this.collectionKey]);
134
+ async delete(documentId, id, options) {
135
+ if (this.$interceptor && !options?.__interceptor__)
136
+ return this.$interceptor(() => this.delete(documentId, id, { ...options, __interceptor__: true }), {
137
+ crud: 'delete',
138
+ method: 'delete',
139
+ documentId,
140
+ itemId: id,
141
+ options
142
+ }, this);
143
+ const matchFilter = mongo_adapter_js_1.MongoAdapter.prepareFilter([
144
+ mongo_adapter_js_1.MongoAdapter.prepareKeyValues(documentId, [this.collectionKey]),
145
+ await this._getDocumentFilter()
146
+ ]);
100
147
  const pullFilter = mongo_adapter_js_1.MongoAdapter.prepareFilter([
101
148
  mongo_adapter_js_1.MongoAdapter.prepareKeyValues(id, [this.arrayKey]),
102
- options?.filter
103
- ]);
149
+ options?.filter,
150
+ await this._getArrayFilter()
151
+ ]) || {};
104
152
  const r = await this.__updateOne(matchFilter, {
105
153
  $pull: { [this.fieldName]: pullFilter }
106
154
  }, options);
107
155
  return r.modifiedCount ? 1 : 0;
108
156
  }
109
157
  /**
110
- * Removes multiple items from an array field
158
+ * Deletes multiple items from a collection based on the parent ID and optional filter.
111
159
  *
112
- * @param parentId
113
- * @param options
160
+ * @param {AnyId} documentId - The ID of the parent document.
161
+ * @param {MongoArrayService.DeleteManyOptions<T>} options - Optional options to specify a filter.
162
+ * @returns {Promise<number>} - A Promise that resolves to the number of items deleted.
114
163
  */
115
- async deleteMany(parentId, options) {
116
- const docFilter = mongo_adapter_js_1.MongoAdapter.prepareKeyValues(parentId, [this.collectionKey]);
164
+ async deleteMany(documentId, options) {
165
+ if (this.$interceptor && !options?.__interceptor__)
166
+ return this.$interceptor(() => this.deleteMany(documentId, { ...options, __interceptor__: true }), {
167
+ crud: 'delete',
168
+ method: 'deleteMany',
169
+ documentId,
170
+ options
171
+ }, this);
172
+ const matchFilter = mongo_adapter_js_1.MongoAdapter.prepareFilter([
173
+ mongo_adapter_js_1.MongoAdapter.prepareKeyValues(documentId, [this.collectionKey]),
174
+ await this._getDocumentFilter()
175
+ ]);
117
176
  // Count matching items, we will use this as result
118
- const matchCount = await this.count(parentId, options);
119
- const pullFilter = mongo_adapter_js_1.MongoAdapter.prepareFilter(options?.filter) || {};
120
- const r = await this.__updateOne(docFilter, {
177
+ const matchCount = await this.count(documentId, options);
178
+ const pullFilter = mongo_adapter_js_1.MongoAdapter.prepareFilter([
179
+ options?.filter,
180
+ await this._getArrayFilter()
181
+ ]) || {};
182
+ const r = await this.__updateOne(matchFilter, {
121
183
  $pull: { [this.fieldName]: pullFilter }
122
184
  }, options);
123
185
  if (r.modifiedCount)
@@ -125,48 +187,87 @@ class MongoArrayService extends mongo_service_js_1.MongoService {
125
187
  return 0;
126
188
  }
127
189
  /**
128
- * Returns true if item exists, false otherwise
190
+ * Checks if an array element with the given parentId and id exists.
129
191
  *
130
- * @param parentId
131
- * @param id
192
+ * @param {AnyId} documentId - The ID of the parent document.
193
+ * @param {AnyId} id - The id of the record.
194
+ * @param {MongoArrayService.ExistsOptions} [options] - The options for the exists method.
195
+ * @returns {Promise<boolean>} - A promise that resolves to a boolean indicating if the record exists or not.
132
196
  */
133
- async exists(parentId, id) {
134
- return !!(await this.findById(parentId, id, { pick: ['_id'] }));
197
+ async exists(documentId, id, options) {
198
+ if (this.$interceptor && !options?.__interceptor__)
199
+ return this.$interceptor(() => this.exists(documentId, id, { ...options, __interceptor__: true }), {
200
+ crud: 'read',
201
+ method: 'exists',
202
+ documentId,
203
+ itemId: id,
204
+ options
205
+ }, this);
206
+ return !!(await this.findById(documentId, id, { ...options, pick: ['_id'] }));
135
207
  }
136
208
  /**
137
- * Fetches the first item in an array field that matches by id.
209
+ * Finds an element in array field by its parent ID and ID.
138
210
  *
139
- * @param parentId
140
- * @param id
141
- * @param options
211
+ * @param {AnyId} documentId - The ID of the document.
212
+ * @param {AnyId} id - The ID of the document.
213
+ * @param {MongoArrayService.FindOneOptions} [options] - The optional options for the operation.
214
+ * @returns {Promise<PartialDTO<T> | undefined>} - A promise that resolves to the found document or undefined if not found.
142
215
  */
143
- async findById(parentId, id, options) {
144
- let filter = mongo_adapter_js_1.MongoAdapter.prepareKeyValues(id, [this.arrayKey]);
145
- if (options?.filter)
146
- filter = mongo_adapter_js_1.MongoAdapter.prepareFilter([filter, options?.filter]);
147
- return await this.findOne(parentId, { ...options, filter });
216
+ async findById(documentId, id, options) {
217
+ if (this.$interceptor && !options?.__interceptor__)
218
+ return this.$interceptor(() => this.findOne(documentId, { ...options, __interceptor__: true }), {
219
+ crud: 'read',
220
+ method: 'findById',
221
+ documentId,
222
+ itemId: id,
223
+ options
224
+ }, this);
225
+ const filter = mongo_adapter_js_1.MongoAdapter.prepareFilter([
226
+ mongo_adapter_js_1.MongoAdapter.prepareKeyValues(id, [this.arrayKey]),
227
+ options?.filter
228
+ ]);
229
+ return await this.findOne(documentId, { ...options, filter });
148
230
  }
149
231
  /**
150
- * Fetches the first item in an array field that matches the filter. Returns undefined if not found.
232
+ * Finds the first array element that matches the given parentId.
151
233
  *
152
- * @param parentId
153
- * @param options
234
+ * @param {AnyId} documentId - The ID of the document.
235
+ * @param {MongoArrayService.FindOneOptions} [options] - Optional options to customize the query.
236
+ * @returns {Promise<PartialDTO<T> | undefined>} A promise that resolves to the first matching document, or `undefined` if no match is found.
154
237
  */
155
- async findOne(parentId, options) {
156
- const rows = await this.findMany(parentId, {
238
+ async findOne(documentId, options) {
239
+ if (this.$interceptor && !options?.__interceptor__)
240
+ return this.$interceptor(() => this.findOne(documentId, { ...options, __interceptor__: true }), {
241
+ crud: 'read',
242
+ method: 'findOne',
243
+ documentId,
244
+ options
245
+ }, this);
246
+ const rows = await this.findMany(documentId, {
157
247
  ...options,
158
248
  limit: 1
159
249
  });
160
250
  return rows?.[0];
161
251
  }
162
252
  /**
163
- * Fetches all items in an array field that matches the filter
253
+ * Finds multiple elements in an array field.
164
254
  *
165
- * @param parentId
166
- * @param options
255
+ * @param {AnyId} documentId - The ID of the parent document.
256
+ * @param {MongoArrayService.FindManyOptions<T>} [options] - The options for finding the documents.
257
+ * @returns {Promise<PartialDTO<T>[]>} - The found documents.
167
258
  */
168
- async findMany(parentId, options) {
169
- const matchFilter = mongo_adapter_js_1.MongoAdapter.prepareKeyValues(parentId, [this.collectionKey]);
259
+ async findMany(documentId, options) {
260
+ if (this.$interceptor && !options?.__interceptor__)
261
+ return this.$interceptor(() => this.findMany(documentId, { ...options, __interceptor__: true }), {
262
+ crud: 'read',
263
+ method: 'findMany',
264
+ documentId,
265
+ options
266
+ }, this);
267
+ const matchFilter = mongo_adapter_js_1.MongoAdapter.prepareFilter([
268
+ mongo_adapter_js_1.MongoAdapter.prepareKeyValues(documentId, [this.collectionKey]),
269
+ await this._getDocumentFilter()
270
+ ]);
170
271
  const mongoOptions = {
171
272
  ...(0, lodash_omit_1.default)(options, ['pick', 'include', 'omit', 'sort', 'skip', 'limit', 'filter', 'count'])
172
273
  };
@@ -186,8 +287,12 @@ class MongoArrayService extends mongo_service_js_1.MongoService {
186
287
  }
187
288
  });
188
289
  }
189
- if (options?.filter) {
190
- const optionsFilter = mongo_adapter_js_1.MongoAdapter.prepareFilter(options?.filter);
290
+ const contextArrayFilter = await this._getArrayFilter();
291
+ if (options?.filter || contextArrayFilter) {
292
+ const optionsFilter = mongo_adapter_js_1.MongoAdapter.prepareFilter([
293
+ options?.filter,
294
+ contextArrayFilter
295
+ ]);
191
296
  dataStages.push({ $match: optionsFilter });
192
297
  }
193
298
  if (options?.skip)
@@ -198,10 +303,11 @@ class MongoArrayService extends mongo_service_js_1.MongoService {
198
303
  dataStages.push({ $sort: sort });
199
304
  }
200
305
  dataStages.push({ $limit: limit });
201
- const dataType = this.getArrayDataType();
306
+ const dataType = this.getDataType();
202
307
  const projection = mongo_adapter_js_1.MongoAdapter.prepareProjection(dataType, options);
203
308
  if (projection)
204
309
  dataStages.push({ $project: projection });
310
+ const decoder = this.getDecoder();
205
311
  const cursor = await this.__aggregate(stages, {
206
312
  ...mongoOptions
207
313
  });
@@ -209,7 +315,7 @@ class MongoArrayService extends mongo_service_js_1.MongoService {
209
315
  if (options?.count) {
210
316
  const facetResult = await cursor.toArray();
211
317
  this.context.response.totalMatches = facetResult[0].count[0].totalMatches || 0;
212
- return facetResult[0].data;
318
+ return facetResult[0].data.map((r) => decoder(r));
213
319
  }
214
320
  else
215
321
  return await cursor.toArray();
@@ -220,30 +326,44 @@ class MongoArrayService extends mongo_service_js_1.MongoService {
220
326
  }
221
327
  }
222
328
  /**
223
- * Fetches the first item in an array field that matches the item id. Throws error undefined if not found.
329
+ * Retrieves a specific item from the array of a document.
224
330
  *
225
- * @param parentId
226
- * @param id
227
- * @param options
331
+ * @param {AnyId} documentId - The ID of the document.
332
+ * @param {AnyId} id - The ID of the item.
333
+ * @param {MongoArrayService.FindOneOptions<T>} [options] - The options for finding the item.
334
+ * @returns {Promise<PartialDTO<T>>} - The item found.
335
+ * @throws {ResourceNotFoundError} - If the item is not found.
228
336
  */
229
- async get(parentId, id, options) {
230
- const out = await this.findById(parentId, id, options);
337
+ async get(documentId, id, options) {
338
+ const out = await this.findById(documentId, id, options);
231
339
  if (!out)
232
- throw new common_1.ResourceNotFoundError((this.resourceName || this.getCollectionName()) + '.' + this.arrayKey, parentId + '/' + id);
340
+ throw new common_1.ResourceNotFoundError(this.getResourceName() + '.' + this.arrayKey, documentId + '/' + id);
233
341
  return out;
234
342
  }
235
343
  /**
236
- * Update a single item in array field
344
+ * Updates an array element with new data and returns the updated element
237
345
  *
238
- * @param parentId
239
- * @param id
240
- * @param input
241
- * @param options
346
+ * @param {AnyId} documentId - The ID of the document to update.
347
+ * @param {AnyId} id - The ID of the item to update within the document.
348
+ * @param {PatchDTO<T>} input - The new data to update the item with.
349
+ * @param {MongoArrayService.UpdateOptions<T>} [options] - Additional update options.
350
+ * @returns {Promise<PartialDTO<T> | undefined>} The updated item or undefined if it does not exist.
351
+ * @throws {Error} If an error occurs while updating the item.
242
352
  */
243
- async update(parentId, id, input, options) {
244
- await this.updateOnly(parentId, id, input, options);
353
+ async update(documentId, id, input, options) {
354
+ if (this.$interceptor && !options?.__interceptor__)
355
+ return this.$interceptor(() => this.update(documentId, id, input, { ...options, __interceptor__: true }), {
356
+ crud: 'update',
357
+ method: 'update',
358
+ documentId,
359
+ itemId: id,
360
+ options
361
+ }, this);
362
+ const r = await this.updateOnly(documentId, id, input, options);
363
+ if (!r)
364
+ return;
245
365
  try {
246
- return await this.findById(parentId, id, options);
366
+ return await this.findById(documentId, id, options);
247
367
  }
248
368
  catch (e) {
249
369
  Error.captureStackTrace(e);
@@ -251,94 +371,136 @@ class MongoArrayService extends mongo_service_js_1.MongoService {
251
371
  }
252
372
  }
253
373
  /**
254
- * Update a single item in array field
255
- * Returns how many master documents updated (not array items)
374
+ * Update an array element with new data. Returns 1 if document updated 0 otherwise.
256
375
  *
257
- * @param parentId
258
- * @param id
259
- * @param doc
260
- * @param options
376
+ * @param {AnyId} documentId - The ID of the parent document.
377
+ * @param {AnyId} id - The ID of the document to update.
378
+ * @param {PatchDTO<T>} input - The partial input object containing the fields to update.
379
+ * @param {MongoArrayService.UpdateOptions<T>} [options] - Optional update options.
380
+ * @returns {Promise<number>} - A promise that resolves to the number of elements updated.
261
381
  */
262
- async updateOnly(parentId, id, doc, options) {
382
+ async updateOnly(documentId, id, input, options) {
383
+ if (this.$interceptor && !options?.__interceptor__)
384
+ return this.$interceptor(() => this.updateOnly(documentId, id, input, { ...options, __interceptor__: true }), {
385
+ crud: 'update',
386
+ method: 'updateOnly',
387
+ documentId,
388
+ itemId: id,
389
+ input,
390
+ options
391
+ }, this);
263
392
  let filter = mongo_adapter_js_1.MongoAdapter.prepareKeyValues(id, [this.arrayKey]);
264
393
  if (options?.filter)
265
394
  filter = mongo_adapter_js_1.MongoAdapter.prepareFilter([filter, options?.filter]);
266
- return await this.updateMany(parentId, doc, { ...options, filter });
395
+ return await this.updateMany(documentId, input, { ...options, filter });
267
396
  }
268
397
  /**
269
- * Update multiple items in array field, returns how many master documents updated (not array items)
398
+ * Updates multiple array elements in document
270
399
  *
271
- * @param parentId
272
- * @param input
273
- * @param options
400
+ * @param {AnyId} documentId - The ID of the document to update.
401
+ * @param {PatchDTO<T>} input - The updated data for the document(s).
402
+ * @param {MongoArrayService.UpdateManyOptions<T>} [options] - Additional options for the update operation.
403
+ * @returns {Promise<number>} - A promise that resolves to the number of documents updated.
274
404
  */
275
- async updateMany(parentId, input, options) {
276
- const encode = this._getEncoder('update');
405
+ async updateMany(documentId, input, options) {
406
+ if (this.$interceptor && !options?.__interceptor__)
407
+ return this.$interceptor(() => this.updateMany(documentId, input, { ...options, __interceptor__: true }), {
408
+ crud: 'update',
409
+ method: 'updateMany',
410
+ documentId,
411
+ input,
412
+ options
413
+ }, this);
414
+ const encode = this.getEncoder('update');
277
415
  const doc = encode(input);
416
+ if (!Object.keys(doc).length)
417
+ return 0;
278
418
  const matchFilter = mongo_adapter_js_1.MongoAdapter.prepareFilter([
279
- mongo_adapter_js_1.MongoAdapter.prepareKeyValues(parentId, [this.collectionKey]),
419
+ mongo_adapter_js_1.MongoAdapter.prepareKeyValues(documentId, [this.collectionKey]),
420
+ await this._getDocumentFilter(),
280
421
  { [this.fieldName]: { $exists: true } }
281
422
  ]);
282
- if (options?.filter) {
283
- const elemMatch = mongo_adapter_js_1.MongoAdapter.prepareFilter(options?.filter, { fieldPrefix: 'elem.' });
423
+ const contextArrayFilter = await this._getArrayFilter();
424
+ if (options?.filter || contextArrayFilter) {
425
+ const elemMatch = mongo_adapter_js_1.MongoAdapter.prepareFilter([options?.filter, contextArrayFilter], { fieldPrefix: 'elem.' });
284
426
  options = options || {};
285
427
  options.arrayFilters = [elemMatch];
286
428
  }
287
429
  const update = mongo_adapter_js_1.MongoAdapter.preparePatch(doc, {
288
- fieldPrefix: this.fieldName + (options?.filter ? '.$[elem].' : '.$[].')
430
+ fieldPrefix: this.fieldName + ((options?.filter || contextArrayFilter) ? '.$[elem].' : '.$[].')
289
431
  });
290
432
  const r = await this.__updateOne(matchFilter, update, options);
291
433
  return r.modifiedCount;
292
434
  }
293
435
  /**
294
- * Update multiple items in array field and returns number of updated array items
436
+ * Updates multiple elements and returns the count of elements that were updated.
295
437
  *
296
- * @param parentId
297
- * @param doc
298
- * @param options
438
+ * @param {AnyId} documentId - The ID of the document to update.
439
+ * @param {PatchDTO<T>} input - The partial document to update with.
440
+ * @param {MongoArrayService.UpdateManyOptions<T>} [options] - The options for updating multiple documents.
441
+ * @return {Promise<number>} A promise that resolves to the number of elements updated.
299
442
  */
300
- async updateManyReturnCount(parentId, doc, options) {
301
- const r = await this.updateMany(parentId, doc, options);
443
+ async updateManyReturnCount(documentId, input, options) {
444
+ if (this.$interceptor && !options?.__interceptor__)
445
+ return this.$interceptor(() => this.updateManyReturnCount(documentId, input, { ...options, __interceptor__: true }), {
446
+ crud: 'update',
447
+ method: 'updateManyReturnCount',
448
+ documentId,
449
+ input,
450
+ options
451
+ }, this);
452
+ const r = await this.updateMany(documentId, input, options);
302
453
  return r
303
454
  // Count matching items that fits filter criteria
304
- ? await this.count(parentId, options)
455
+ ? await this.count(documentId, options)
305
456
  : 0;
306
457
  }
307
458
  /**
308
- * Generates Id value
459
+ * Retrieves the data type of the array field
460
+ *
461
+ * @returns {ComplexType} The complex data type of the field.
462
+ * @throws {NotAcceptableError} If the data type is not a ComplexType.
463
+ */
464
+ getDataType() {
465
+ const t = super.getDataType()
466
+ .getField(this.fieldName).type;
467
+ if (!(t instanceof common_1.ComplexType))
468
+ throw new common_1.NotAcceptableError(`Data type "${t.name}" is not a ComplexType`);
469
+ return t;
470
+ }
471
+ /**
472
+ * Generates an ID.
309
473
  *
310
474
  * @protected
475
+ * @returns {AnyId} The generated ID.
311
476
  */
312
477
  _generateId() {
313
- return new mongodb_1.ObjectId();
478
+ return typeof this.$idGenerator === 'function' ?
479
+ this.$idGenerator(this) : new mongodb_1.ObjectId();
314
480
  }
315
481
  /**
316
- * Generates a new Validator for encoding or returns from cache if already generated before
317
- * @param operation
482
+ * Retrieves the common filter used for querying documents.
483
+ * This method is mostly used for security issues like securing multi-tenant applications.
484
+ *
318
485
  * @protected
486
+ * @returns {FilterInput | Promise<FilterInput> | undefined} The common filter or a Promise
487
+ * that resolves to the common filter, or undefined if not available.
319
488
  */
320
- _getEncoder(operation) {
321
- let encoder = this._encoders[operation];
322
- if (encoder)
323
- return encoder;
324
- encoder = this._generateEncoder(operation);
325
- this._encoders[operation] = encoder;
326
- return encoder;
489
+ _getDocumentFilter() {
490
+ return typeof this.$documentFilter === 'function' ?
491
+ this.$documentFilter(this) : this.$documentFilter;
327
492
  }
328
493
  /**
329
- * Generates a new Valgen Validator for encode operation
494
+ * Retrieves the common filter used for querying array elements.
495
+ * This method is mostly used for security issues like securing multi-tenant applications.
330
496
  *
331
- * @param operation
332
497
  * @protected
498
+ * @returns {FilterInput | Promise<FilterInput> | undefined} The common filter or a Promise
499
+ * that resolves to the common filter, or undefined if not available.
333
500
  */
334
- _generateEncoder(operation) {
335
- const dataType = this.getArrayDataType();
336
- const options = {};
337
- if (operation === 'update') {
338
- options.omit = ['_id'];
339
- options.partial = true;
340
- }
341
- return dataType.generateCodec('encode', options);
501
+ _getArrayFilter() {
502
+ return typeof this.$arrayFilter === 'function' ?
503
+ this.$arrayFilter(this) : this.$arrayFilter;
342
504
  }
343
505
  }
344
506
  exports.MongoArrayService = MongoArrayService;