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