@opra/mongodb 0.33.13 → 1.0.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,16 +1,17 @@
1
+ import { ObjectId } from 'mongodb';
1
2
  import { DATATYPE_METADATA } from '@opra/common';
2
- import { ApiService } from '@opra/core';
3
+ import { ServiceBase } from '@opra/core';
3
4
  /**
4
5
  * Class representing a MongoDB service for interacting with a collection.
5
- * @extends ApiService
6
+ * @extends ServiceBase
6
7
  * @template T - The type of the documents in the collection.
7
8
  */
8
- export class MongoService extends ApiService {
9
+ export class MongoService extends ServiceBase {
9
10
  /**
10
11
  * Constructs a new instance
11
12
  *
12
- * @param dataType - The data type of the array elements.
13
- * @param [options] - The options for the array service.
13
+ * @param dataType - The data type of the returning results
14
+ * @param [options] - The options for the service
14
15
  * @constructor
15
16
  */
16
17
  constructor(dataType, options) {
@@ -18,26 +19,56 @@ export class MongoService extends ApiService {
18
19
  this._encoders = {};
19
20
  this._dataType = dataType;
20
21
  this.db = options?.db;
21
- this.collectionName = options?.collectionName;
22
- if (!this.collectionName) {
22
+ this.$documentFilter = this.$documentFilter || options?.documentFilter;
23
+ this.$interceptor = this.$interceptor || options?.interceptor;
24
+ this.$collectionName = options?.collectionName;
25
+ if (!this.$collectionName) {
23
26
  if (typeof dataType === 'string')
24
- this.collectionName = dataType;
27
+ this.$collectionName = dataType;
25
28
  if (typeof dataType === 'function') {
26
29
  const metadata = Reflect.getMetadata(DATATYPE_METADATA, dataType);
27
30
  if (metadata)
28
- this.collectionName = metadata.name;
31
+ this.$collectionName = metadata.name;
29
32
  }
30
33
  }
31
- this.resourceName = options?.resourceName;
34
+ this.$resourceName = options?.resourceName;
32
35
  this.$idGenerator = options?.idGenerator;
33
36
  }
37
+ /**
38
+ * Retrieves the collection name.
39
+ *
40
+ * @protected
41
+ * @returns The collection name.
42
+ * @throws {Error} If the collection name is not defined.
43
+ */
44
+ getCollectionName() {
45
+ const out = typeof this.$collectionName === 'function' ? this.$collectionName(this) : this.$collectionName;
46
+ if (out)
47
+ return out;
48
+ throw new Error('collectionName is not defined');
49
+ }
50
+ /**
51
+ * Retrieves the resource name.
52
+ *
53
+ * @protected
54
+ * @returns {string} The resource name.
55
+ * @throws {Error} If the collection name is not defined.
56
+ */
57
+ getResourceName() {
58
+ const out = typeof this.$resourceName === 'function'
59
+ ? this.$resourceName(this)
60
+ : this.$resourceName || this.getCollectionName();
61
+ if (out)
62
+ return out;
63
+ throw new Error('resourceName is not defined');
64
+ }
34
65
  /**
35
66
  * Retrieves the data type of the document
36
67
  *
37
68
  * @throws {NotAcceptableError} If the data type is not a ComplexType.
38
69
  */
39
70
  getDataType() {
40
- return this.context.api.getComplexType(this._dataType);
71
+ return this.context.document.node.getComplexType(this._dataType);
41
72
  }
42
73
  /**
43
74
  * Retrieves the encoder for the specified operation.
@@ -48,7 +79,11 @@ export class MongoService extends ApiService {
48
79
  let encoder = this._encoders[operation];
49
80
  if (encoder)
50
81
  return encoder;
51
- encoder = this._generateEncoder(operation);
82
+ const options = { projection: '*' };
83
+ if (operation === 'update')
84
+ options.partial = true;
85
+ const dataType = this.getDataType();
86
+ encoder = dataType.generateCodec('encode', options);
52
87
  this._encoders[operation] = encoder;
53
88
  return encoder;
54
89
  }
@@ -59,7 +94,9 @@ export class MongoService extends ApiService {
59
94
  let decoder = this._decoder;
60
95
  if (decoder)
61
96
  return decoder;
62
- decoder = this._generateDecoder();
97
+ const options = { projection: '*', partial: true };
98
+ const dataType = this.getDataType();
99
+ decoder = dataType.generateCodec('decode', options);
63
100
  this._decoder = decoder;
64
101
  return decoder;
65
102
  }
@@ -104,30 +141,6 @@ export class MongoService extends ApiService {
104
141
  await session.endSession();
105
142
  }
106
143
  }
107
- /**
108
- * Inserts a single document into MongoDB. If documents passed in do not contain the **_id** field,
109
- * one will be added to each of the documents missing it by the driver, mutating the document. This behavior
110
- * can be overridden by setting the **forceServerObjectId** flag.
111
- *
112
- * @param doc - The document to insert
113
- * @param options - Optional settings for the command
114
- * @protected
115
- */
116
- async __insertOne(doc, options) {
117
- const db = this.getDatabase();
118
- const collection = await this.getCollection(db);
119
- options = {
120
- ...options,
121
- session: options?.session || this.getSession()
122
- };
123
- try {
124
- return await collection.insertOne(doc, options);
125
- }
126
- catch (e) {
127
- await this.$onError?.(e, this);
128
- throw e;
129
- }
130
- }
131
144
  /**
132
145
  * Gets the number of documents matching the filter.
133
146
  *
@@ -135,188 +148,156 @@ export class MongoService extends ApiService {
135
148
  * @param options - The options for counting documents.
136
149
  * @protected
137
150
  */
138
- async __countDocuments(filter, options) {
151
+ async _dbCountDocuments(filter, options) {
139
152
  const db = this.getDatabase();
140
153
  const collection = await this.getCollection(db);
141
154
  options = {
142
155
  ...options,
143
156
  limit: undefined,
144
- session: options?.session || this.getSession()
157
+ session: options?.session || this.getSession(),
145
158
  };
146
- try {
147
- return await collection.countDocuments(filter || {}, options) || 0;
148
- }
149
- catch (e) {
150
- await this.$onError?.(e, this);
151
- throw e;
152
- }
159
+ return (await collection.countDocuments(filter || {}, options)) || 0;
153
160
  }
154
161
  /**
155
- * Delete a document from a collection
162
+ * Acquires a connection and performs Collection.deleteOne operation
156
163
  *
157
164
  * @param filter - The filter used to select the document to remove
158
165
  * @param options - Optional settings for the command
159
166
  * @protected
160
167
  */
161
- async __deleteOne(filter, options) {
168
+ async _dbDeleteOne(filter, options) {
162
169
  const db = this.getDatabase();
163
170
  const collection = await this.getCollection(db);
164
171
  options = {
165
172
  ...options,
166
- session: options?.session || this.getSession()
173
+ session: options?.session || this.getSession(),
167
174
  };
168
- try {
169
- return await collection.deleteOne(filter || {}, options);
170
- }
171
- catch (e) {
172
- await this.$onError?.(e, this);
173
- throw e;
174
- }
175
+ return await collection.deleteOne(filter || {}, options);
175
176
  }
176
177
  /**
177
- * Delete multiple documents from a collection
178
+ * Acquires a connection and performs Collection.deleteMany operation
178
179
  *
179
180
  * @param filter - The filter used to select the documents to remove
180
181
  * @param options - Optional settings for the command
181
182
  * @protected
182
183
  */
183
- async __deleteMany(filter, options) {
184
+ async _dbDeleteMany(filter, options) {
184
185
  const db = this.getDatabase();
185
186
  const collection = await this.getCollection(db);
186
187
  options = {
187
188
  ...options,
188
- session: options?.session || this.getSession()
189
+ session: options?.session || this.getSession(),
189
190
  };
190
- try {
191
- return await collection.deleteMany(filter || {}, options);
192
- }
193
- catch (e) {
194
- await this.$onError?.(e, this);
195
- throw e;
196
- }
191
+ return await collection.deleteMany(filter || {}, options);
197
192
  }
198
193
  /**
199
- * Gets the number of documents matching the filter.
194
+ * Acquires a connection and performs Collection.distinct operation
200
195
  *
201
196
  * @param field - Field of the document to find distinct values for
202
197
  * @param filter - The filter for filtering the set of documents to which we apply the distinct filter.
203
198
  * @param options - Optional settings for the command
204
199
  * @protected
205
200
  */
206
- async __distinct(field, filter, options) {
201
+ async _dbDistinct(field, filter, options) {
207
202
  const db = this.getDatabase();
208
203
  const collection = await this.getCollection(db);
209
204
  options = {
210
205
  ...options,
211
- session: options?.session || this.getSession()
206
+ session: options?.session || this.getSession(),
212
207
  };
213
- try {
214
- return await collection.distinct(field, filter || {}, options);
215
- }
216
- catch (e) {
217
- await this.$onError?.(e, this);
218
- throw e;
219
- }
208
+ return await collection.distinct(field, filter || {}, options);
220
209
  }
221
210
  /**
222
- * Execute an aggregation framework pipeline against the collection, needs MongoDB \>= 2.2
211
+ * Acquires a connection and performs Collection.aggregate operation
223
212
  *
224
213
  * @param pipeline - An array of aggregation pipelines to execute
225
214
  * @param options - Optional settings for the command
226
215
  * @protected
227
216
  */
228
- async __aggregate(pipeline, options) {
217
+ async _dbAggregate(pipeline, options) {
229
218
  const db = this.getDatabase();
230
219
  const collection = await this.getCollection(db);
231
220
  options = {
232
221
  ...options,
233
- session: options?.session || this.getSession()
222
+ session: options?.session || this.getSession(),
234
223
  };
235
- try {
236
- return await collection.aggregate(pipeline, options);
237
- }
238
- catch (e) {
239
- await this.$onError?.(e, this);
240
- throw e;
241
- }
224
+ return await collection.aggregate(pipeline, options);
242
225
  }
243
226
  /**
244
- * Fetches the first document that matches the filter
227
+ * Acquires a connection and performs Collection.findOne operation
245
228
  *
246
229
  * @param filter - Query for find Operation
247
230
  * @param options - Optional settings for the command
248
231
  * @protected
249
232
  */
250
- async __findOne(filter, options) {
233
+ async _dbFindOne(filter, options) {
251
234
  const db = this.getDatabase();
252
235
  const collection = await this.getCollection(db);
253
236
  options = {
254
237
  ...options,
255
- session: options?.session || this.getSession()
238
+ session: options?.session || this.getSession(),
256
239
  };
257
- try {
258
- return await collection.findOne(filter || {}, options);
259
- }
260
- catch (e) {
261
- await this.$onError?.(e, this);
262
- throw e;
263
- }
240
+ return (await collection.findOne(filter || {}, options));
264
241
  }
265
242
  /**
266
- * Creates a cursor for a filter that can be used to iterate over results from MongoDB
243
+ * Acquires a connection and performs Collection.find operation
267
244
  *
268
245
  * @param filter - The filter predicate. If unspecified,
269
246
  * then all documents in the collection will match the predicate
270
247
  * @param options - Optional settings for the command
271
248
  * @protected
272
249
  */
273
- async __find(filter, options) {
250
+ async _dbFind(filter, options) {
274
251
  const db = this.getDatabase();
275
252
  const collection = await this.getCollection(db);
276
253
  options = {
277
254
  ...options,
278
- session: options?.session || this.getSession()
255
+ session: options?.session || this.getSession(),
279
256
  };
280
- try {
281
- return collection.find(filter || {}, options);
282
- }
283
- catch (e) {
284
- await this.$onError?.(e, this);
285
- throw e;
286
- }
257
+ return collection.find(filter || {}, options);
287
258
  }
288
259
  /**
289
- * Update a single document in a collection
260
+ * Acquires a connection and performs Collection.insertOne operation
261
+ *
262
+ * @param doc - The document to insert
263
+ * @param options - Optional settings for the command
264
+ * @protected
265
+ */
266
+ async _dbInsertOne(doc, options) {
267
+ const db = this.getDatabase();
268
+ const collection = await this.getCollection(db);
269
+ options = {
270
+ ...options,
271
+ session: options?.session || this.getSession(),
272
+ };
273
+ return await collection.insertOne(doc, options);
274
+ }
275
+ /**
276
+ * Acquires a connection and performs Collection.updateOne operation
290
277
  *
291
278
  * @param filter - The filter used to select the document to update
292
279
  * @param update - The update operations to be applied to the document
293
280
  * @param options - Optional settings for the command
294
281
  * @protected
295
282
  */
296
- async __updateOne(filter, update, options) {
283
+ async _dbUpdateOne(filter, update, options) {
297
284
  const db = this.getDatabase();
298
285
  const collection = await this.getCollection(db);
299
286
  options = {
300
287
  ...options,
301
288
  session: options?.session || this.getSession(),
302
289
  };
303
- try {
304
- return collection.updateOne(filter || {}, update, options);
305
- }
306
- catch (e) {
307
- await this.$onError?.(e, this);
308
- throw e;
309
- }
290
+ return collection.updateOne(filter || {}, update, options);
310
291
  }
311
292
  /**
312
- * Find a document and update it in one atomic operation. Requires a write lock for the duration of the operation.
293
+ * Acquires a connection and performs Collection.findOneAndUpdate operation
313
294
  *
314
295
  * @param filter - The filter used to select the document to update
315
296
  * @param update - Update operations to be performed on the document
316
297
  * @param options - Optional settings for the command
317
298
  * @protected
318
299
  */
319
- async __findOneAndUpdate(filter, update, options) {
300
+ async _dbFindOneAndUpdate(filter, update, options) {
320
301
  const db = this.getDatabase();
321
302
  const collection = await this.getCollection(db);
322
303
  const opts = {
@@ -325,37 +306,25 @@ export class MongoService extends ApiService {
325
306
  ...options,
326
307
  session: options?.session || this.getSession(),
327
308
  };
328
- try {
329
- return await collection.findOneAndUpdate(filter || {}, update, opts);
330
- }
331
- catch (e) {
332
- await this.$onError?.(e, this);
333
- throw e;
334
- }
309
+ return await collection.findOneAndUpdate(filter || {}, update, opts);
335
310
  }
336
311
  /**
337
- * Update multiple documents in a collection
312
+ * Acquires a connection and performs Collection.updateMany operation
338
313
  *
339
314
  * @param filter - The filter used to select the documents to update
340
315
  * @param update - The update operations to be applied to the documents
341
316
  * @param options - Optional settings for the command
342
317
  * @protected
343
318
  */
344
- async __updateMany(filter, update, options) {
319
+ async _dbUpdateMany(filter, update, options) {
345
320
  const db = this.getDatabase();
346
321
  const collection = await this.getCollection(db);
347
322
  options = {
348
323
  ...options,
349
324
  session: options?.session || this.getSession(),
350
- upsert: false
325
+ upsert: false,
351
326
  };
352
- try {
353
- return await collection.updateMany(filter || {}, update, options);
354
- }
355
- catch (e) {
356
- await this.$onError?.(e, this);
357
- throw e;
358
- }
327
+ return await collection.updateMany(filter || {}, update, options);
359
328
  }
360
329
  /**
361
330
  * Retrieves the database connection.
@@ -365,9 +334,7 @@ export class MongoService extends ApiService {
365
334
  * @throws {Error} If the context or database is not set.
366
335
  */
367
336
  getDatabase() {
368
- const db = typeof this.db === 'function'
369
- ? this.db(this)
370
- : this.db;
337
+ const db = typeof this.db === 'function' ? this.db(this) : this.db;
371
338
  if (!db)
372
339
  throw new Error(`Database not set!`);
373
340
  return db;
@@ -380,9 +347,7 @@ export class MongoService extends ApiService {
380
347
  * @throws {Error} If the context or database is not set.
381
348
  */
382
349
  getSession() {
383
- return typeof this.session === 'function'
384
- ? this.session(this)
385
- : this.session;
350
+ return typeof this.session === 'function' ? this.session(this) : this.session;
386
351
  }
387
352
  /**
388
353
  * Retrieves a MongoDB collection from the given database.
@@ -394,59 +359,35 @@ export class MongoService extends ApiService {
394
359
  return db.collection(this.getCollectionName());
395
360
  }
396
361
  /**
397
- * Retrieves the collection name.
362
+ * Generates an ID.
398
363
  *
399
364
  * @protected
400
- * @returns The collection name.
401
- * @throws {Error} If the collection name is not defined.
365
+ * @returns {MongoAdapter.AnyId} The generated ID.
402
366
  */
403
- getCollectionName() {
404
- const out = typeof this.collectionName === 'function'
405
- ? this.collectionName(this)
406
- : this.collectionName;
407
- if (out)
408
- return out;
409
- throw new Error('collectionName is not defined');
367
+ _generateId() {
368
+ return typeof this.$idGenerator === 'function' ? this.$idGenerator(this) : new ObjectId();
410
369
  }
411
370
  /**
412
- * Retrieves the resource name.
371
+ * Retrieves the common filter used for querying documents.
372
+ * This method is mostly used for security issues like securing multi-tenant applications.
413
373
  *
414
374
  * @protected
415
- * @returns {string} The resource name.
416
- * @throws {Error} If the collection name is not defined.
375
+ * @returns {FilterInput | Promise<FilterInput> | undefined} The common filter or a Promise
376
+ * that resolves to the common filter, or undefined if not available.
417
377
  */
418
- getResourceName() {
419
- const out = typeof this.resourceName === 'function'
420
- ? this.resourceName(this)
421
- : this.resourceName || this.getCollectionName();
422
- if (out)
423
- return out;
424
- throw new Error('resourceName is not defined');
378
+ _getDocumentFilter(info) {
379
+ return typeof this.$documentFilter === 'function' ? this.$documentFilter(info, this) : this.$documentFilter;
425
380
  }
426
- /**
427
- * Generates an encoder for the specified operation.
428
- *
429
- * @param operation - The operation to generate the encoder for. Must be either 'create' or 'update'.
430
- * @protected
431
- * @returns - The generated encoder for the specified operation.
432
- */
433
- _generateEncoder(operation) {
434
- const dataType = this.getDataType();
435
- const options = {};
436
- if (operation === 'update') {
437
- options.omit = ['_id'];
438
- options.partial = true;
381
+ async _intercept(callback, info) {
382
+ try {
383
+ if (this.$interceptor)
384
+ return this.$interceptor(callback, info, this);
385
+ return callback();
386
+ }
387
+ catch (e) {
388
+ Error.captureStackTrace(e, this._intercept);
389
+ await this.$onError?.(e, this);
390
+ throw e;
439
391
  }
440
- return dataType.generateCodec('encode', options);
441
- }
442
- /**
443
- * Generates an encoder for the specified operation.
444
- *
445
- * @protected
446
- * @returns - The generated encoder for the specified operation.
447
- */
448
- _generateDecoder() {
449
- const dataType = this.getDataType();
450
- return dataType.generateCodec('decode', { partial: true });
451
392
  }
452
393
  }