@opra/mongodb 0.32.1 → 0.32.3

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.
@@ -8,67 +8,83 @@ 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
15
  constructor(dataType, fieldName, options) {
16
16
  super(dataType, options);
17
- this._encoders = {};
18
17
  this.fieldName = fieldName;
19
18
  this.defaultLimit = options?.defaultLimit || 10;
20
19
  this.collectionKey = options?.collectionKey || '_id';
21
20
  this.arrayKey = options?.arrayKey || '_id';
22
21
  }
23
- getArrayDataType() {
24
- const t = this.getDataType()
22
+ /**
23
+ * Retrieves the data type of the array field
24
+ *
25
+ * @returns {ComplexType} The complex data type of the field.
26
+ * @throws {NotAcceptableError} If the data type is not a ComplexType.
27
+ */
28
+ getDataType() {
29
+ const t = super.getDataType()
25
30
  .getField(this.fieldName).type;
26
31
  if (!(t instanceof common_1.ComplexType))
27
32
  throw new common_1.NotAcceptableError(`Data type "${t.name}" is not a ComplexType`);
28
33
  return t;
29
34
  }
30
35
  /**
31
- * Checks if array item exists. Throws error if not.
36
+ * Asserts whether a resource with the specified parentId and id exists.
37
+ * Throws a ResourceNotFoundError if the resource does not exist.
32
38
  *
33
- * @param parentId
34
- * @param id
39
+ * @param {AnyId} documentId - The ID of the parent document.
40
+ * @param {AnyId} id - The ID of the resource.
41
+ * @return {Promise<void>} - A promise that resolves with no value upon success.
42
+ * @throws {ResourceNotFoundError} - If the resource does not exist.
35
43
  */
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);
44
+ async assert(documentId, id) {
45
+ if (!(await this.exists(documentId, id)))
46
+ throw new common_1.ResourceNotFoundError((this.resourceName || this.getCollectionName()) + '.' + this.arrayKey, documentId + '/' + id);
39
47
  }
40
48
  /**
41
- * Adds a single item into array field.
49
+ * Adds a single item into the array field.
42
50
  *
43
- * @param parentId
44
- * @param input
45
- * @param options
51
+ * @param {AnyId} documentId - The ID of the parent document.
52
+ * @param {T} input - The item to be added to the array field.
53
+ * @param {MongoArrayService.CreateOptions} [options] - Optional options for the create operation.
54
+ * @return {Promise<PartialOutput<T>>} - A promise that resolves with the partial output of the created item.
55
+ * @throws {ResourceNotFoundError} - If the parent document is not found.
46
56
  */
47
- async create(parentId, input, options) {
48
- const encode = this._getEncoder('create');
57
+ async create(documentId, input, options) {
58
+ const encode = this.getEncoder('create');
49
59
  const doc = encode(input);
50
60
  doc[this.arrayKey] = doc[this.arrayKey] || this._generateId();
51
- const docFilter = mongo_adapter_js_1.MongoAdapter.prepareKeyValues(parentId, [this.collectionKey]);
61
+ const docFilter = mongo_adapter_js_1.MongoAdapter.prepareKeyValues(documentId, [this.collectionKey]);
52
62
  const r = await this.__updateOne(docFilter, {
53
63
  $push: { [this.fieldName]: doc }
54
64
  }, options);
55
- if (r.modifiedCount)
65
+ if (r.modifiedCount) {
66
+ if (!options)
67
+ return doc;
56
68
  try {
57
- return this.get(parentId, doc[this.arrayKey], { ...options, filter: undefined, skip: undefined });
69
+ return this.get(documentId, doc[this.arrayKey], { ...options, filter: undefined, skip: undefined });
58
70
  }
59
71
  catch (e) {
60
72
  Error.captureStackTrace(e);
61
73
  throw e;
62
74
  }
63
- throw new common_1.ResourceNotFoundError(this.resourceName || this.getCollectionName(), parentId);
75
+ }
76
+ throw new common_1.ResourceNotFoundError(this.resourceName || this.getCollectionName(), documentId);
64
77
  }
65
78
  /**
66
- * Gets the number of array items matching the filter.
67
- * @param parentId
68
- * @param options
79
+ * Counts the number of documents in the collection that match the specified parentId and options.
80
+ *
81
+ * @param {AnyId} documentId - The ID of the parent document.
82
+ * @param {object} options - Optional parameters for counting.
83
+ * @param {object} options.filter - The filter object to apply to the count operation.
84
+ * @returns {Promise<number>} - A promise that resolves to the count of documents.
69
85
  */
70
- async count(parentId, options) {
71
- const matchFilter = mongo_adapter_js_1.MongoAdapter.prepareKeyValues(parentId, [this.collectionKey]);
86
+ async count(documentId, options) {
87
+ const matchFilter = mongo_adapter_js_1.MongoAdapter.prepareKeyValues(documentId, [this.collectionKey]);
72
88
  const stages = [
73
89
  { $match: matchFilter },
74
90
  { $unwind: { path: "$" + this.fieldName } },
@@ -89,14 +105,15 @@ class MongoArrayService extends mongo_service_js_1.MongoService {
89
105
  }
90
106
  }
91
107
  /**
92
- * Removes one item from an array field
108
+ * Deletes an element from an array within a document in the MongoDB collection.
93
109
  *
94
- * @param parentId
95
- * @param id
96
- * @param options
110
+ * @param {AnyId} documentId - The ID of the parent document.
111
+ * @param {AnyId} id - The ID of the element to delete from the array.
112
+ * @param {MongoArrayService.DeleteOptions<T>} [options] - Additional options for the delete operation.
113
+ * @return {Promise<number>} - A Promise that resolves to the number of elements deleted (1 if successful, 0 if not).
97
114
  */
98
- async delete(parentId, id, options) {
99
- const matchFilter = mongo_adapter_js_1.MongoAdapter.prepareKeyValues(parentId, [this.collectionKey]);
115
+ async delete(documentId, id, options) {
116
+ const matchFilter = mongo_adapter_js_1.MongoAdapter.prepareKeyValues(documentId, [this.collectionKey]);
100
117
  const pullFilter = mongo_adapter_js_1.MongoAdapter.prepareFilter([
101
118
  mongo_adapter_js_1.MongoAdapter.prepareKeyValues(id, [this.arrayKey]),
102
119
  options?.filter
@@ -107,15 +124,16 @@ class MongoArrayService extends mongo_service_js_1.MongoService {
107
124
  return r.modifiedCount ? 1 : 0;
108
125
  }
109
126
  /**
110
- * Removes multiple items from an array field
127
+ * Deletes multiple items from a collection based on the parent ID and optional filter.
111
128
  *
112
- * @param parentId
113
- * @param options
129
+ * @param {AnyId} documentId - The ID of the parent document.
130
+ * @param {MongoArrayService.DeleteManyOptions<T>} options - Optional options to specify a filter.
131
+ * @returns {Promise<number>} - A Promise that resolves to the number of items deleted.
114
132
  */
115
- async deleteMany(parentId, options) {
116
- const docFilter = mongo_adapter_js_1.MongoAdapter.prepareKeyValues(parentId, [this.collectionKey]);
133
+ async deleteMany(documentId, options) {
134
+ const docFilter = mongo_adapter_js_1.MongoAdapter.prepareKeyValues(documentId, [this.collectionKey]);
117
135
  // Count matching items, we will use this as result
118
- const matchCount = await this.count(parentId, options);
136
+ const matchCount = await this.count(documentId, options);
119
137
  const pullFilter = mongo_adapter_js_1.MongoAdapter.prepareFilter(options?.filter) || {};
120
138
  const r = await this.__updateOne(docFilter, {
121
139
  $pull: { [this.fieldName]: pullFilter }
@@ -125,48 +143,52 @@ class MongoArrayService extends mongo_service_js_1.MongoService {
125
143
  return 0;
126
144
  }
127
145
  /**
128
- * Returns true if item exists, false otherwise
146
+ * Checks if an array element with the given parentId and id exists.
129
147
  *
130
- * @param parentId
131
- * @param id
148
+ * @param {AnyId} documentId - The ID of the parent document.
149
+ * @param {AnyId} id - The id of the record.
150
+ * @returns {Promise<boolean>} - A promise that resolves to a boolean indicating if the record exists or not.
132
151
  */
133
- async exists(parentId, id) {
134
- return !!(await this.findById(parentId, id, { pick: ['_id'] }));
152
+ async exists(documentId, id) {
153
+ return !!(await this.findById(documentId, id, { pick: ['_id'] }));
135
154
  }
136
155
  /**
137
- * Fetches the first item in an array field that matches by id.
156
+ * Finds an element in array field by its parent ID and ID.
138
157
  *
139
- * @param parentId
140
- * @param id
141
- * @param options
158
+ * @param {AnyId} documentId - The ID of the document.
159
+ * @param {AnyId} id - The ID of the document.
160
+ * @param {MongoArrayService.FindOneOptions} [options] - The optional options for the operation.
161
+ * @returns {Promise<PartialOutput<T> | undefined>} - A promise that resolves to the found document or undefined if not found.
142
162
  */
143
- async findById(parentId, id, options) {
163
+ async findById(documentId, id, options) {
144
164
  let filter = mongo_adapter_js_1.MongoAdapter.prepareKeyValues(id, [this.arrayKey]);
145
165
  if (options?.filter)
146
166
  filter = mongo_adapter_js_1.MongoAdapter.prepareFilter([filter, options?.filter]);
147
- return await this.findOne(parentId, { ...options, filter });
167
+ return await this.findOne(documentId, { ...options, filter });
148
168
  }
149
169
  /**
150
- * Fetches the first item in an array field that matches the filter. Returns undefined if not found.
170
+ * Finds the first array element that matches the given parentId.
151
171
  *
152
- * @param parentId
153
- * @param options
172
+ * @param {AnyId} documentId - The ID of the document.
173
+ * @param {MongoArrayService.FindOneOptions} [options] - Optional options to customize the query.
174
+ * @returns {Promise<PartialOutput<T> | undefined>} A promise that resolves to the first matching document, or `undefined` if no match is found.
154
175
  */
155
- async findOne(parentId, options) {
156
- const rows = await this.findMany(parentId, {
176
+ async findOne(documentId, options) {
177
+ const rows = await this.findMany(documentId, {
157
178
  ...options,
158
179
  limit: 1
159
180
  });
160
181
  return rows?.[0];
161
182
  }
162
183
  /**
163
- * Fetches all items in an array field that matches the filter
184
+ * Finds multiple elements in an array field.
164
185
  *
165
- * @param parentId
166
- * @param options
186
+ * @param {AnyId} documentId - The ID of the parent document.
187
+ * @param {MongoArrayService.FindManyOptions<T>} [options] - The options for finding the documents.
188
+ * @returns {Promise<PartialOutput<T>[] | undefined>} - The found documents.
167
189
  */
168
- async findMany(parentId, options) {
169
- const matchFilter = mongo_adapter_js_1.MongoAdapter.prepareKeyValues(parentId, [this.collectionKey]);
190
+ async findMany(documentId, options) {
191
+ const matchFilter = mongo_adapter_js_1.MongoAdapter.prepareKeyValues(documentId, [this.collectionKey]);
170
192
  const mongoOptions = {
171
193
  ...(0, lodash_omit_1.default)(options, ['pick', 'include', 'omit', 'sort', 'skip', 'limit', 'filter', 'count'])
172
194
  };
@@ -198,10 +220,11 @@ class MongoArrayService extends mongo_service_js_1.MongoService {
198
220
  dataStages.push({ $sort: sort });
199
221
  }
200
222
  dataStages.push({ $limit: limit });
201
- const dataType = this.getArrayDataType();
223
+ const dataType = this.getDataType();
202
224
  const projection = mongo_adapter_js_1.MongoAdapter.prepareProjection(dataType, options);
203
225
  if (projection)
204
226
  dataStages.push({ $project: projection });
227
+ const decoder = this.getDecoder();
205
228
  const cursor = await this.__aggregate(stages, {
206
229
  ...mongoOptions
207
230
  });
@@ -209,7 +232,7 @@ class MongoArrayService extends mongo_service_js_1.MongoService {
209
232
  if (options?.count) {
210
233
  const facetResult = await cursor.toArray();
211
234
  this.context.response.totalMatches = facetResult[0].count[0].totalMatches || 0;
212
- return facetResult[0].data;
235
+ return facetResult[0].data.map((r) => decoder(r));
213
236
  }
214
237
  else
215
238
  return await cursor.toArray();
@@ -220,30 +243,34 @@ class MongoArrayService extends mongo_service_js_1.MongoService {
220
243
  }
221
244
  }
222
245
  /**
223
- * Fetches the first item in an array field that matches the item id. Throws error undefined if not found.
246
+ * Retrieves a specific item from the array of a document.
224
247
  *
225
- * @param parentId
226
- * @param id
227
- * @param options
248
+ * @param {AnyId} documentId - The ID of the document.
249
+ * @param {AnyId} id - The ID of the item.
250
+ * @param {MongoArrayService.FindOneOptions<T>} [options] - The options for finding the item.
251
+ * @returns {Promise<PartialOutput<T>>} - The item found.
252
+ * @throws {ResourceNotFoundError} - If the item is not found.
228
253
  */
229
- async get(parentId, id, options) {
230
- const out = await this.findById(parentId, id, options);
254
+ async get(documentId, id, options) {
255
+ const out = await this.findById(documentId, id, options);
231
256
  if (!out)
232
- throw new common_1.ResourceNotFoundError((this.resourceName || this.getCollectionName()) + '.' + this.arrayKey, parentId + '/' + id);
257
+ throw new common_1.ResourceNotFoundError((this.resourceName || this.getCollectionName()) + '.' + this.arrayKey, documentId + '/' + id);
233
258
  return out;
234
259
  }
235
260
  /**
236
- * Update a single item in array field
261
+ * Updates an array element with new data and returns the updated element
237
262
  *
238
- * @param parentId
239
- * @param id
240
- * @param input
241
- * @param options
263
+ * @param {AnyId} documentId - The ID of the document to update.
264
+ * @param {AnyId} id - The ID of the item to update within the document.
265
+ * @param {PartialInput<T>} input - The new data to update the item with.
266
+ * @param {MongoArrayService.UpdateOptions<T>} [options] - Additional update options.
267
+ * @returns {Promise<PartialOutput<T> | undefined>} The updated item or undefined if it does not exist.
268
+ * @throws {Error} If an error occurs while updating the item.
242
269
  */
243
- async update(parentId, id, input, options) {
244
- await this.updateOnly(parentId, id, input, options);
270
+ async update(documentId, id, input, options) {
271
+ await this.updateOnly(documentId, id, input, options);
245
272
  try {
246
- return await this.findById(parentId, id, options);
273
+ return await this.findById(documentId, id, options);
247
274
  }
248
275
  catch (e) {
249
276
  Error.captureStackTrace(e);
@@ -251,32 +278,35 @@ class MongoArrayService extends mongo_service_js_1.MongoService {
251
278
  }
252
279
  }
253
280
  /**
254
- * Update a single item in array field
255
- * Returns how many master documents updated (not array items)
281
+ * Update an array element with new data. Returns 1 if document updated 0 otherwise.
256
282
  *
257
- * @param parentId
258
- * @param id
259
- * @param doc
260
- * @param options
283
+ * @param {AnyId} documentId - The ID of the parent document.
284
+ * @param {AnyId} id - The ID of the document to update.
285
+ * @param {PartialInput<T>} doc - The partial input object containing the fields to update.
286
+ * @param {MongoArrayService.UpdateOptions<T>} [options] - Optional update options.
287
+ * @returns {Promise<number>} - A promise that resolves to the number of elements updated.
261
288
  */
262
- async updateOnly(parentId, id, doc, options) {
289
+ async updateOnly(documentId, id, doc, options) {
263
290
  let filter = mongo_adapter_js_1.MongoAdapter.prepareKeyValues(id, [this.arrayKey]);
264
291
  if (options?.filter)
265
292
  filter = mongo_adapter_js_1.MongoAdapter.prepareFilter([filter, options?.filter]);
266
- return await this.updateMany(parentId, doc, { ...options, filter });
293
+ return await this.updateMany(documentId, doc, { ...options, filter });
267
294
  }
268
295
  /**
269
- * Update multiple items in array field, returns how many master documents updated (not array items)
296
+ * Updates multiple array elements in document
270
297
  *
271
- * @param parentId
272
- * @param input
273
- * @param options
298
+ * @param {AnyId} documentId - The ID of the document to update.
299
+ * @param {PartialInput<T>} input - The updated data for the document(s).
300
+ * @param {MongoArrayService.UpdateManyOptions<T>} [options] - Additional options for the update operation.
301
+ * @returns {Promise<number>} - A promise that resolves to the number of documents updated.
274
302
  */
275
- async updateMany(parentId, input, options) {
276
- const encode = this._getEncoder('update');
303
+ async updateMany(documentId, input, options) {
304
+ const encode = this.getEncoder('update');
277
305
  const doc = encode(input);
306
+ if (!Object.keys(doc).length)
307
+ return 0;
278
308
  const matchFilter = mongo_adapter_js_1.MongoAdapter.prepareFilter([
279
- mongo_adapter_js_1.MongoAdapter.prepareKeyValues(parentId, [this.collectionKey]),
309
+ mongo_adapter_js_1.MongoAdapter.prepareKeyValues(documentId, [this.collectionKey]),
280
310
  { [this.fieldName]: { $exists: true } }
281
311
  ]);
282
312
  if (options?.filter) {
@@ -291,54 +321,28 @@ class MongoArrayService extends mongo_service_js_1.MongoService {
291
321
  return r.modifiedCount;
292
322
  }
293
323
  /**
294
- * Update multiple items in array field and returns number of updated array items
324
+ * Updates multiple elements and returns the count of elements that were updated.
295
325
  *
296
- * @param parentId
297
- * @param doc
298
- * @param options
326
+ * @param {AnyId} documentId - The ID of the document to update.
327
+ * @param {PartialInput<T>} doc - The partial document to update with.
328
+ * @param {MongoArrayService.UpdateManyOptions<T>} [options] - The options for updating multiple documents.
329
+ * @return {Promise<number>} A promise that resolves to the number of elements updated.
299
330
  */
300
- async updateManyReturnCount(parentId, doc, options) {
301
- const r = await this.updateMany(parentId, doc, options);
331
+ async updateManyReturnCount(documentId, doc, options) {
332
+ const r = await this.updateMany(documentId, doc, options);
302
333
  return r
303
334
  // Count matching items that fits filter criteria
304
- ? await this.count(parentId, options)
335
+ ? await this.count(documentId, options)
305
336
  : 0;
306
337
  }
307
338
  /**
308
- * Generates Id value
339
+ * Generates a new id for new inserting Document.
309
340
  *
310
341
  * @protected
342
+ * @returns {AnyId} A new instance of AnyId.
311
343
  */
312
344
  _generateId() {
313
345
  return new mongodb_1.ObjectId();
314
346
  }
315
- /**
316
- * Generates a new Validator for encoding or returns from cache if already generated before
317
- * @param operation
318
- * @protected
319
- */
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;
327
- }
328
- /**
329
- * Generates a new Valgen Validator for encode operation
330
- *
331
- * @param operation
332
- * @protected
333
- */
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);
342
- }
343
347
  }
344
348
  exports.MongoArrayService = MongoArrayService;