@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.
@@ -1,11 +1,24 @@
1
1
  import { DATATYPE_METADATA } from '@opra/common';
2
2
  import { ApiService } from '@opra/core';
3
+ /**
4
+ * Class representing a MongoDB service for interacting with a collection.
5
+ * @extends ApiService
6
+ * @template T - The type of the documents in the collection.
7
+ */
3
8
  export class MongoService extends ApiService {
9
+ /**
10
+ * Constructs a new instance
11
+ *
12
+ * @param {Type | string} dataType - The data type of the array elements.
13
+ * @param {MongoService.Options} [options] - The options for the array service.
14
+ * @constructor
15
+ */
4
16
  constructor(dataType, options) {
5
17
  super();
18
+ this._encoders = {};
6
19
  this._dataType = dataType;
7
20
  this.db = options?.db;
8
- this.collectionName = options?.collectionName || options?.resourceName;
21
+ this.collectionName = options?.collectionName;
9
22
  if (!this.collectionName) {
10
23
  if (typeof dataType === 'string')
11
24
  this.collectionName = dataType;
@@ -15,264 +28,407 @@ export class MongoService extends ApiService {
15
28
  this.collectionName = metadata.name;
16
29
  }
17
30
  }
18
- this.resourceName = options?.resourceName || this.collectionName;
31
+ this.resourceName = options?.resourceName;
32
+ this.$idGenerator = options?.idGenerator;
19
33
  }
34
+ /**
35
+ * Retrieves the data type of the document
36
+ *
37
+ * @returns {ComplexType} The complex data type of the field.
38
+ * @throws {NotAcceptableError} If the data type is not a ComplexType.
39
+ */
20
40
  getDataType() {
21
41
  return this.context.api.getComplexType(this._dataType);
22
42
  }
23
- forContext(arg0, attributes) {
24
- return super.forContext(arg0, attributes);
43
+ /**
44
+ * Retrieves the encoder for the specified operation.
45
+ *
46
+ * @param {String} operation - The operation to retrieve the encoder for. Valid values are 'create' and 'update'.
47
+ * @returns {vg.ObjectValidator<T>} - The encoder for the specified operation.
48
+ */
49
+ getEncoder(operation) {
50
+ let encoder = this._encoders[operation];
51
+ if (encoder)
52
+ return encoder;
53
+ encoder = this._generateEncoder(operation);
54
+ this._encoders[operation] = encoder;
55
+ return encoder;
56
+ }
57
+ /**
58
+ * Retrieves the decoder.
59
+ *
60
+ * @returns {vg.ObjectValidator<T>} - The encoder for the specified operation.
61
+ */
62
+ getDecoder() {
63
+ let decoder = this._decoder;
64
+ if (decoder)
65
+ return decoder;
66
+ decoder = this._generateDecoder();
67
+ this._decoder = decoder;
68
+ return decoder;
25
69
  }
26
70
  /**
27
71
  * Inserts a single document into MongoDB. If documents passed in do not contain the **_id** field,
28
72
  * one will be added to each of the documents missing it by the driver, mutating the document. This behavior
29
73
  * can be overridden by setting the **forceServerObjectId** flag.
30
74
  *
31
- * @param doc
32
- * @param options
75
+ * @param {T} doc - The document to be inserted.
76
+ * @param {mongodb.InsertOneOptions} options - The options for the insert operation.
77
+ * @returns {Promise<mongodb.InsertOneWriteOpResult<mongodb.OptionalId<T>>>} - A promise that resolves with the result of the insert operation.
33
78
  * @protected
34
79
  */
35
80
  async __insertOne(doc, options) {
36
- const db = await this.getDatabase();
81
+ const db = this.getDatabase();
37
82
  const collection = await this.getCollection(db);
38
83
  options = {
39
84
  ...options,
40
- session: options?.session || this.session
85
+ session: options?.session || this.getSession()
41
86
  };
42
87
  try {
43
88
  return await collection.insertOne(doc, options);
44
89
  }
45
90
  catch (e) {
46
- await this._onError(e);
91
+ await this.$onError?.(e, this);
47
92
  throw e;
48
93
  }
49
94
  }
50
95
  /**
51
96
  * Gets the number of documents matching the filter.
52
97
  *
53
- * @param filter
54
- * @param options
98
+ * @param {mongodb.Filter<T>} filter - The filter used to match documents.
99
+ * @param {mongodb.CountOptions} options - The options for counting documents.
100
+ * @returns {Promise<number>} - The number of documents matching the filter.
55
101
  * @protected
56
102
  */
57
103
  async __countDocuments(filter, options) {
58
- const db = await this.getDatabase();
104
+ const db = this.getDatabase();
59
105
  const collection = await this.getCollection(db);
60
106
  options = {
61
107
  ...options,
62
108
  limit: undefined,
63
- session: options?.session || this.session
109
+ session: options?.session || this.getSession()
64
110
  };
65
111
  try {
66
- return await collection.countDocuments(filter, options) || 0;
112
+ return await collection.countDocuments(filter || {}, options) || 0;
67
113
  }
68
114
  catch (e) {
69
- await this._onError(e);
115
+ await this.$onError?.(e, this);
70
116
  throw e;
71
117
  }
72
118
  }
73
119
  /**
74
120
  * Delete a document from a collection
75
121
  *
76
- * @param filter - The filter used to select the document to remove
77
- * @param options - Optional settings for the command
122
+ * @param {mongodb.Filter<T>} filter - The filter used to select the document to remove
123
+ * @param {mongodb.DeleteOptions} options - Optional settings for the command
124
+ * @return {Promise<mongodb.DeleteResult>} A Promise that resolves to the result of the delete operation
78
125
  */
79
126
  async __deleteOne(filter, options) {
80
- const db = await this.getDatabase();
127
+ const db = this.getDatabase();
81
128
  const collection = await this.getCollection(db);
82
129
  options = {
83
130
  ...options,
84
- session: options?.session || this.session
131
+ session: options?.session || this.getSession()
85
132
  };
86
133
  try {
87
- return await collection.deleteOne(filter, options);
134
+ return await collection.deleteOne(filter || {}, options);
88
135
  }
89
136
  catch (e) {
90
- await this._onError(e);
137
+ await this.$onError?.(e, this);
91
138
  throw e;
92
139
  }
93
140
  }
94
141
  /**
95
- * Delete multiple documents from a collection
142
+ * Deletes multiple documents from a collection.
96
143
  *
97
- * @param filter
98
- * @param options
144
+ * @param {mongodb.Filter<T>} [filter] - The filter object specifying the documents to delete.
145
+ * If not provided, all documents in the collection will be deleted.
146
+ * @param {mongodb.DeleteOptions} [options] - The options for the delete operation.
147
+ * @returns {Promise<mongodb.DeleteResult>} A promise that resolves with the delete result object.
99
148
  * @protected
100
149
  */
101
150
  async __deleteMany(filter, options) {
102
- const db = await this.getDatabase();
151
+ const db = this.getDatabase();
103
152
  const collection = await this.getCollection(db);
104
153
  options = {
105
154
  ...options,
106
- session: options?.session || this.session
155
+ session: options?.session || this.getSession()
107
156
  };
108
157
  try {
109
- return await collection.deleteMany(filter, options);
158
+ return await collection.deleteMany(filter || {}, options);
110
159
  }
111
160
  catch (e) {
112
- await this._onError(e);
161
+ await this.$onError?.(e, this);
113
162
  throw e;
114
163
  }
115
164
  }
116
165
  /**
117
- * Create a new Change Stream, watching for new changes (insertions, updates, replacements, deletions, and invalidations) in this collection.
166
+ * Create a new Change Stream, watching for new changes
167
+ * (insertions, updates, replacements, deletions, and invalidations) in this collection.
118
168
  *
119
- * @param pipeline
120
- * @param options
169
+ * @param {mongodb.Document[]} pipeline - The pipeline of aggregation stages to apply to the collection.
170
+ * @param {mongodb.AggregateOptions} options - The options to customize the aggregation.
171
+ * @returns {Promise<mongodb.ChangeStream<T>>} - A promise that resolves to a Change Stream that represents the result of the aggregation.
121
172
  * @protected
122
173
  */
123
174
  async __aggregate(pipeline, options) {
124
- const db = await this.getDatabase();
175
+ const db = this.getDatabase();
125
176
  const collection = await this.getCollection(db);
126
177
  options = {
127
178
  ...options,
128
- session: options?.session || this.session
179
+ session: options?.session || this.getSession()
129
180
  };
130
181
  try {
131
182
  return await collection.aggregate(pipeline, options);
132
183
  }
133
184
  catch (e) {
134
- await this._onError(e);
185
+ await this.$onError?.(e, this);
135
186
  throw e;
136
187
  }
137
188
  }
138
189
  /**
139
190
  * Fetches the first document that matches the filter
140
191
  *
141
- * @param filter
142
- * @param options
192
+ * @param {mongodb.Filter<T>} filter - The filter object to match documents against
193
+ * @param {mongodb.FindOptions} [options] - The options to use when querying the collection
143
194
  * @protected
195
+ * @returns {Promise<PartialDTO<T> | undefined>} - A promise that resolves to the first matching document, or undefined if no match is found
144
196
  */
145
197
  async __findOne(filter, options) {
146
- const db = await this.getDatabase();
198
+ const db = this.getDatabase();
147
199
  const collection = await this.getCollection(db);
148
200
  options = {
149
201
  ...options,
150
- session: options?.session || this.session
202
+ session: options?.session || this.getSession()
151
203
  };
152
204
  try {
153
- return await collection.findOne(filter, options);
205
+ return await collection.findOne(filter || {}, options);
154
206
  }
155
207
  catch (e) {
156
- await this._onError(e);
208
+ await this.$onError?.(e, this);
157
209
  throw e;
158
210
  }
159
211
  }
160
212
  /**
161
- * Creates a cursor for a filter that can be used to iterate over results from MongoDB
213
+ * Creates a cursor for a filter that can be used to iterate over results from MongoDB.
162
214
  *
163
- * @param filter
164
- * @param options
215
+ * @param {mongodb.Filter<T>} filter - The filter to apply when searching for results.
216
+ * @param {mongodb.FindOptions} [options] - The options to customize the search behavior.
217
+ * @returns {mongodb.Cursor<T>} - The cursor object that can be used to iterate over the results.
165
218
  * @protected
166
219
  */
167
220
  async __find(filter, options) {
168
- const db = await this.getDatabase();
221
+ const db = this.getDatabase();
169
222
  const collection = await this.getCollection(db);
170
223
  options = {
171
224
  ...options,
172
- session: options?.session || this.session
225
+ session: options?.session || this.getSession()
173
226
  };
174
227
  try {
175
- return collection.find(filter, options);
228
+ return collection.find(filter || {}, options);
176
229
  }
177
230
  catch (e) {
178
- await this._onError(e);
231
+ await this.$onError?.(e, this);
179
232
  throw e;
180
233
  }
181
234
  }
182
235
  /**
183
- * Update a single document in a collection
236
+ * Update a single document in a collection.
184
237
  *
185
- * @param filter
186
- * @param update
187
- * @param options
238
+ * @param {mongodb.Filter<T>} filter - The filter to select the document(s) to update.
239
+ * @param {mongodb.UpdateFilter<T>} update - The update operation to be applied on the selected document(s).
240
+ * @param {mongodb.UpdateOptions} [options] - Optional settings for the update operation.
188
241
  * @protected
242
+ * @returns {Promise<mongodb.UpdateResult>} - A promise that resolves to the result of the update operation.
189
243
  */
190
244
  async __updateOne(filter, update, options) {
191
- const db = await this.getDatabase();
245
+ const db = this.getDatabase();
192
246
  const collection = await this.getCollection(db);
193
247
  options = {
194
- session: this.session,
195
- ...options
248
+ ...options,
249
+ session: options?.session || this.getSession(),
196
250
  };
197
251
  try {
198
- return collection.updateOne(filter, update, options);
252
+ return collection.updateOne(filter || {}, update, options);
199
253
  }
200
254
  catch (e) {
201
- await this._onError(e);
255
+ await this.$onError?.(e, this);
202
256
  throw e;
203
257
  }
204
258
  }
205
259
  /**
206
260
  * Find a document and update it in one atomic operation. Requires a write lock for the duration of the operation.
207
261
  *
208
- * @param filter
209
- * @param doc
210
- * @param options
262
+ * @param {mongodb.Filter<T>} filter - The filter to select the document to be updated.
263
+ * @param {mongodb.UpdateFilter<T>} doc - The update document.
264
+ * @param {mongodb.FindOneAndUpdateOptions} [options] - Optional options for the find one and update operation.
265
+ * @returns {Promise<T | null>} - A promise that resolves to the updated document or null if no document matched the filter.
211
266
  * @protected
212
267
  */
213
268
  async __findOneAndUpdate(filter, doc, options) {
214
- const db = await this.getDatabase();
269
+ const db = this.getDatabase();
215
270
  const collection = await this.getCollection(db);
216
271
  const opts = {
217
272
  returnDocument: 'after',
218
- session: this.session,
219
273
  includeResultMetadata: false,
220
274
  ...options,
275
+ session: options?.session || this.getSession(),
221
276
  };
222
277
  try {
223
- return await collection.findOneAndUpdate(filter, doc, opts);
278
+ return await collection.findOneAndUpdate(filter || {}, doc, opts);
224
279
  }
225
280
  catch (e) {
226
- await this._onError(e);
281
+ await this.$onError?.(e, this);
227
282
  throw e;
228
283
  }
229
284
  }
230
285
  /**
231
- * Update multiple documents in a collection
286
+ * Update multiple documents in a collection.
232
287
  *
233
- * @param filter
234
- * @param doc
235
- * @param options
288
+ * @param {mongodb.Filter<T>} filter - The filter used to select the documents to be updated.
289
+ * @param {mongodb.UpdateFilter<T> | Partial<T>} doc - The updates to be applied to the selected documents.
290
+ * @param {StrictOmit<mongodb.UpdateOptions, 'upsert'>} [options] - The optional settings for the update operation.
291
+ * @return {Promise<mongodb.UpdateResult>} - A Promise that resolves to the result of the update operation.
236
292
  * @protected
237
293
  */
238
294
  async __updateMany(filter, doc, options) {
239
- const db = await this.getDatabase();
295
+ const db = this.getDatabase();
240
296
  const collection = await this.getCollection(db);
241
297
  options = {
242
298
  ...options,
243
- session: options?.session || this.session,
299
+ session: options?.session || this.getSession(),
244
300
  upsert: false
245
301
  };
246
302
  try {
247
- return await collection.updateMany(filter, doc, options);
303
+ return await collection.updateMany(filter || {}, doc, options);
248
304
  }
249
305
  catch (e) {
250
- await this._onError(e);
306
+ await this.$onError?.(e, this);
251
307
  throw e;
252
308
  }
253
309
  }
254
- async _onError(error) {
255
- if (this.onError)
256
- await this.onError(error);
257
- }
310
+ /**
311
+ * Retrieves the database connection.
312
+ *
313
+ * @protected
314
+ *
315
+ * @returns {Promise<mongodb.Db>} The database connection.
316
+ * @throws {Error} If the context or database is not set.
317
+ */
258
318
  getDatabase() {
259
- if (!this.context)
260
- throw new Error(`Context not set!`);
261
- if (!this.db)
319
+ const db = typeof this.db === 'function'
320
+ ? this.db(this)
321
+ : this.db;
322
+ if (!db)
262
323
  throw new Error(`Database not set!`);
263
- return this.db;
324
+ return db;
264
325
  }
326
+ /**
327
+ * Retrieves the database session.
328
+ *
329
+ * @protected
330
+ *
331
+ * @returns {Promise<mongodb.ClientSession>} The database connection.
332
+ * @throws {Error} If the context or database is not set.
333
+ */
334
+ getSession() {
335
+ return typeof this.session === 'function'
336
+ ? this.session(this)
337
+ : this.session;
338
+ }
339
+ /**
340
+ * Retrieves a MongoDB collection from the given database.
341
+ *
342
+ * @param {mongodb.Db} db - The MongoDB database.
343
+ * @protected
344
+ * @returns {Promise<mongodb.Collection<T>>} A promise that resolves to the MongoDB collection.
345
+ */
265
346
  async getCollection(db) {
266
347
  return db.collection(this.getCollectionName());
267
348
  }
349
+ /**
350
+ * Retrieves the collection name.
351
+ *
352
+ * @protected
353
+ * @returns {string} The collection name.
354
+ * @throws {Error} If the collection name is not defined.
355
+ */
268
356
  getCollectionName() {
269
- if (this.collectionName)
270
- return this.collectionName;
357
+ const out = typeof this.collectionName === 'function'
358
+ ? this.collectionName(this)
359
+ : this.collectionName;
360
+ if (out)
361
+ return out;
271
362
  throw new Error('collectionName is not defined');
272
363
  }
273
- _cacheMatch(service, context, attributes) {
274
- return super._cacheMatch(service, context, attributes) &&
275
- (!attributes?.db || service.db === attributes.db) &&
276
- (!attributes?.session || this.session === attributes?.session);
364
+ /**
365
+ * Retrieves the resource name.
366
+ *
367
+ * @protected
368
+ * @returns {string} The collection name.
369
+ * @throws {Error} If the collection name is not defined.
370
+ */
371
+ getResourceName() {
372
+ const out = typeof this.resourceName === 'function'
373
+ ? this.resourceName(this)
374
+ : this.resourceName || this.getCollectionName();
375
+ if (out)
376
+ return out;
377
+ throw new Error('resourceName is not defined');
378
+ }
379
+ /**
380
+ * Compares the current instance with the provided attributes and returns true if they match, false otherwise.
381
+ * This method is protected and should only be called by subclasses.
382
+ *
383
+ * @param {MongoService<any>} service - The service instance to compare.
384
+ * @param {RequestContext} context - The request context.
385
+ * @param {Object} attributes - Optional attributes object for comparison.
386
+ * @return {boolean} - True if the instance matches the provided attributes, false otherwise.
387
+ * @protected
388
+ */
389
+ _instanceCompare(service, context, attributes) {
390
+ return super._instanceCompare(service, context, attributes) &&
391
+ (!attributes?.db ||
392
+ (typeof service.db === 'object' && service.db === attributes.db) ||
393
+ (typeof service.db === 'function' && service.getDatabase() === attributes.db)) &&
394
+ (!attributes?.session ||
395
+ (typeof service.session === 'object' && service.session === attributes?.session) ||
396
+ (typeof service.session === 'function' && service.getSession() === attributes?.session));
397
+ }
398
+ /**
399
+ * Generates an encoder for the specified operation.
400
+ *
401
+ * @param {string} operation - The operation to generate the encoder for. Must be either 'create' or 'update'.
402
+ * @protected
403
+ * @returns {vg.Validator} - The generated encoder for the specified operation.
404
+ */
405
+ _generateEncoder(operation) {
406
+ let encoder = this._encoders[operation];
407
+ if (encoder)
408
+ return encoder;
409
+ const dataType = this.getDataType();
410
+ const options = {};
411
+ if (operation === 'update') {
412
+ options.omit = ['_id'];
413
+ options.partial = true;
414
+ }
415
+ encoder = dataType.generateCodec('encode', options);
416
+ this._encoders[operation] = encoder;
417
+ return encoder;
418
+ }
419
+ /**
420
+ * Generates an encoder for the specified operation.
421
+ *
422
+ * @protected
423
+ * @returns {vg.Validator} - The generated encoder for the specified operation.
424
+ */
425
+ _generateDecoder() {
426
+ let decoder = this._decoder;
427
+ if (decoder)
428
+ return decoder;
429
+ const dataType = this.getDataType();
430
+ const options = {};
431
+ decoder = this._decoder = dataType.generateCodec('decode', options);
432
+ return decoder;
277
433
  }
278
434
  }