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