@opra/mongodb 0.33.13 → 1.0.0-alpha.10

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