@opra/mongodb 0.33.2 → 0.33.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.
@@ -155,6 +155,24 @@ class MongoCollectionService extends mongo_service_js_1.MongoService {
155
155
  const r = await this.__deleteMany(filter, (0, lodash_omit_1.default)(options, 'filter'));
156
156
  return r.deletedCount;
157
157
  }
158
+ async distinct(field, options) {
159
+ const info = {
160
+ crud: 'read',
161
+ method: 'distinct',
162
+ options
163
+ };
164
+ return this._intercept(async () => {
165
+ const filter = mongo_adapter_js_1.MongoAdapter.prepareFilter([
166
+ await this._getDocumentFilter(info),
167
+ options?.filter,
168
+ ]);
169
+ return this._distinct(field, { ...options, filter });
170
+ }, info);
171
+ }
172
+ async _distinct(field, options) {
173
+ const filter = mongo_adapter_js_1.MongoAdapter.prepareFilter(options?.filter);
174
+ return await this.__distinct(field, filter, (0, lodash_omit_1.default)(options, 'filter'));
175
+ }
158
176
  /**
159
177
  * Checks if an object with the given id exists.
160
178
  *
@@ -365,21 +383,33 @@ class MongoCollectionService extends mongo_service_js_1.MongoService {
365
383
  }, info);
366
384
  }
367
385
  async _update(id, input, options) {
368
- const encode = this.getEncoder('update');
369
- const doc = encode(input, { coerce: true });
370
- const patch = mongo_adapter_js_1.MongoAdapter.preparePatch(doc);
386
+ const isUpdateFilter = Array.isArray(input) ||
387
+ !!Object.keys(input).find(x => x.startsWith('$'));
388
+ const isDocument = !Array.isArray(input) &&
389
+ !!Object.keys(input).find(x => !x.startsWith('$'));
390
+ if (isUpdateFilter && isDocument)
391
+ throw new TypeError('You must pass one of MongoDB UpdateFilter or a partial document, not both');
392
+ let update;
393
+ if (isDocument) {
394
+ const encode = this.getEncoder('update');
395
+ const doc = encode(input, { coerce: true });
396
+ update = mongo_adapter_js_1.MongoAdapter.preparePatch(doc);
397
+ update.$set = update.$set || {};
398
+ }
399
+ else
400
+ update = input;
401
+ const filter = mongo_adapter_js_1.MongoAdapter.prepareFilter([
402
+ mongo_adapter_js_1.MongoAdapter.prepareKeyValues(id, [this.collectionKey]),
403
+ options?.filter
404
+ ]);
371
405
  const mongoOptions = {
372
406
  ...options,
373
407
  includeResultMetadata: false,
374
408
  upsert: undefined,
375
409
  projection: mongo_adapter_js_1.MongoAdapter.prepareProjection(this.getDataType(), options),
376
410
  };
377
- const filter = mongo_adapter_js_1.MongoAdapter.prepareFilter([
378
- mongo_adapter_js_1.MongoAdapter.prepareKeyValues(id, [this.collectionKey]),
379
- options?.filter
380
- ]);
381
411
  const decode = this.getDecoder();
382
- const out = await this.__findOneAndUpdate(filter, patch, mongoOptions);
412
+ const out = await this.__findOneAndUpdate(filter, update, mongoOptions);
383
413
  return out ? decode(out, { coerce: true }) : undefined;
384
414
  }
385
415
  /**
@@ -407,22 +437,33 @@ class MongoCollectionService extends mongo_service_js_1.MongoService {
407
437
  }, info);
408
438
  }
409
439
  async _updateOnly(id, input, options) {
440
+ const isUpdateFilter = Array.isArray(input) ||
441
+ !!Object.keys(input).find(x => x.startsWith('$'));
442
+ const isDocument = !Array.isArray(input) &&
443
+ !!Object.keys(input).find(x => !x.startsWith('$'));
444
+ if (isUpdateFilter && isDocument)
445
+ throw new TypeError('You must pass one of MongoDB UpdateFilter or a partial document, not both');
446
+ let update;
447
+ if (isDocument) {
448
+ const encode = this.getEncoder('update');
449
+ const doc = encode(input, { coerce: true });
450
+ update = mongo_adapter_js_1.MongoAdapter.preparePatch(doc);
451
+ if (!Object.keys(doc).length)
452
+ return 0;
453
+ }
454
+ else
455
+ update = input;
410
456
  const filter = mongo_adapter_js_1.MongoAdapter.prepareFilter([
411
457
  mongo_adapter_js_1.MongoAdapter.prepareKeyValues(id, [this.collectionKey]),
412
458
  options?.filter
413
459
  ]);
414
- const encode = this.getEncoder('update');
415
- const doc = encode(input, { coerce: true });
416
- if (!Object.keys(doc).length)
417
- return 0;
418
- const patch = mongo_adapter_js_1.MongoAdapter.preparePatch(doc);
419
460
  const mongoOptions = {
420
461
  ...options,
421
462
  includeResultMetadata: false,
422
463
  upsert: undefined,
423
464
  projection: mongo_adapter_js_1.MongoAdapter.prepareProjection(this.getDataType(), options),
424
465
  };
425
- const out = await this.__updateOne(filter, patch, mongoOptions);
466
+ const out = await this.__updateOne(filter, update, mongoOptions);
426
467
  return out.matchedCount;
427
468
  }
428
469
  /**
@@ -448,18 +489,28 @@ class MongoCollectionService extends mongo_service_js_1.MongoService {
448
489
  }, info);
449
490
  }
450
491
  async _updateMany(input, options) {
451
- const encode = this.getEncoder('update');
452
- const doc = encode(input, { coerce: true });
453
- if (!Object.keys(doc).length)
454
- return 0;
455
- const patch = mongo_adapter_js_1.MongoAdapter.preparePatch(doc);
456
- patch.$set = patch.$set || {};
492
+ const isUpdateFilter = Array.isArray(input) ||
493
+ !!Object.keys(input).find(x => x.startsWith('$'));
494
+ const isDocument = !Array.isArray(input) &&
495
+ !!Object.keys(input).find(x => !x.startsWith('$'));
496
+ if (isUpdateFilter && isDocument)
497
+ throw new TypeError('You must pass one of MongoDB UpdateFilter or a partial document, not both');
498
+ let update;
499
+ if (isDocument) {
500
+ const encode = this.getEncoder('update');
501
+ const doc = encode(input, { coerce: true });
502
+ update = mongo_adapter_js_1.MongoAdapter.preparePatch(doc);
503
+ if (!Object.keys(doc).length)
504
+ return 0;
505
+ }
506
+ else
507
+ update = input;
457
508
  const mongoOptions = {
458
509
  ...(0, lodash_omit_1.default)(options, 'filter'),
459
510
  upsert: undefined
460
511
  };
461
512
  const filter = mongo_adapter_js_1.MongoAdapter.prepareFilter(options?.filter);
462
- const r = await this.__updateMany(filter, patch, mongoOptions);
513
+ const r = await this.__updateMany(filter, update, mongoOptions);
463
514
  return r.matchedCount;
464
515
  }
465
516
  /**
@@ -12,8 +12,8 @@ class MongoService extends core_1.ApiService {
12
12
  /**
13
13
  * Constructs a new instance
14
14
  *
15
- * @param {Type | string} dataType - The data type of the array elements.
16
- * @param {MongoService.Options} [options] - The options for the array service.
15
+ * @param dataType - The data type of the array elements.
16
+ * @param [options] - The options for the array service.
17
17
  * @constructor
18
18
  */
19
19
  constructor(dataType, options) {
@@ -37,7 +37,6 @@ class MongoService extends core_1.ApiService {
37
37
  /**
38
38
  * Retrieves the data type of the document
39
39
  *
40
- * @returns {ComplexType} The complex data type of the field.
41
40
  * @throws {NotAcceptableError} If the data type is not a ComplexType.
42
41
  */
43
42
  getDataType() {
@@ -46,8 +45,7 @@ class MongoService extends core_1.ApiService {
46
45
  /**
47
46
  * Retrieves the encoder for the specified operation.
48
47
  *
49
- * @param {String} operation - The operation to retrieve the encoder for. Valid values are 'create' and 'update'.
50
- * @returns {IsObject.Validator<T>} - The encoder for the specified operation.
48
+ * @param operation - The operation to retrieve the encoder for. Valid values are 'create' and 'update'.
51
49
  */
52
50
  getEncoder(operation) {
53
51
  let encoder = this._encoders[operation];
@@ -59,8 +57,6 @@ class MongoService extends core_1.ApiService {
59
57
  }
60
58
  /**
61
59
  * Retrieves the decoder.
62
- *
63
- * @returns {IsObject.Validator<T>} - The encoder for the specified operation.
64
60
  */
65
61
  getDecoder() {
66
62
  let decoder = this._decoder;
@@ -73,9 +69,8 @@ class MongoService extends core_1.ApiService {
73
69
  /**
74
70
  * Executes the provided function within a transaction.
75
71
  *
76
- * @param {WithTransactionCallback} callback - The function to be executed within the transaction.
77
- * @param {TransactionOptions} [options] - Optional options for the transaction.
78
- * @returns {Promise<any>} - A promise that resolves with the result of the function execution within the transaction.
72
+ * @param callback - The function to be executed within the transaction.
73
+ * @param [options] - Optional options for the transaction.
79
74
  */
80
75
  async withTransaction(callback, options) {
81
76
  let session = this.getSession();
@@ -104,12 +99,12 @@ class MongoService extends core_1.ApiService {
104
99
  }
105
100
  finally {
106
101
  // Restore old session property
107
- if (hasOldSession) {
102
+ if (hasOldSession)
108
103
  this.session = oldSessionGetter;
109
- await session.endSession();
110
- }
111
104
  else
112
105
  delete this.session;
106
+ if (!oldInTransaction)
107
+ await session.endSession();
113
108
  }
114
109
  }
115
110
  /**
@@ -117,9 +112,8 @@ class MongoService extends core_1.ApiService {
117
112
  * one will be added to each of the documents missing it by the driver, mutating the document. This behavior
118
113
  * can be overridden by setting the **forceServerObjectId** flag.
119
114
  *
120
- * @param {T} doc - The document to be inserted.
121
- * @param {mongodb.InsertOneOptions} options - The options for the insert operation.
122
- * @returns {Promise<mongodb.InsertOneWriteOpResult<mongodb.OptionalId<T>>>} - A promise that resolves with the result of the insert operation.
115
+ * @param doc - The document to insert
116
+ * @param options - Optional settings for the command
123
117
  * @protected
124
118
  */
125
119
  async __insertOne(doc, options) {
@@ -140,9 +134,8 @@ class MongoService extends core_1.ApiService {
140
134
  /**
141
135
  * Gets the number of documents matching the filter.
142
136
  *
143
- * @param {mongodb.Filter<T>} filter - The filter used to match documents.
144
- * @param {mongodb.CountOptions} options - The options for counting documents.
145
- * @returns {Promise<number>} - The number of documents matching the filter.
137
+ * @param filter - The filter used to match documents.
138
+ * @param options - The options for counting documents.
146
139
  * @protected
147
140
  */
148
141
  async __countDocuments(filter, options) {
@@ -164,9 +157,9 @@ class MongoService extends core_1.ApiService {
164
157
  /**
165
158
  * Delete a document from a collection
166
159
  *
167
- * @param {mongodb.Filter<T>} filter - The filter used to select the document to remove
168
- * @param {mongodb.DeleteOptions} options - Optional settings for the command
169
- * @return {Promise<mongodb.DeleteResult>} A Promise that resolves to the result of the delete operation
160
+ * @param filter - The filter used to select the document to remove
161
+ * @param options - Optional settings for the command
162
+ * @protected
170
163
  */
171
164
  async __deleteOne(filter, options) {
172
165
  const db = this.getDatabase();
@@ -184,12 +177,10 @@ class MongoService extends core_1.ApiService {
184
177
  }
185
178
  }
186
179
  /**
187
- * Deletes multiple documents from a collection.
180
+ * Delete multiple documents from a collection
188
181
  *
189
- * @param {mongodb.Filter<T>} [filter] - The filter object specifying the documents to delete.
190
- * If not provided, all documents in the collection will be deleted.
191
- * @param {mongodb.DeleteOptions} [options] - The options for the delete operation.
192
- * @returns {Promise<mongodb.DeleteResult>} A promise that resolves with the delete result object.
182
+ * @param filter - The filter used to select the documents to remove
183
+ * @param options - Optional settings for the command
193
184
  * @protected
194
185
  */
195
186
  async __deleteMany(filter, options) {
@@ -208,12 +199,33 @@ class MongoService extends core_1.ApiService {
208
199
  }
209
200
  }
210
201
  /**
211
- * Create a new Change Stream, watching for new changes
212
- * (insertions, updates, replacements, deletions, and invalidations) in this collection.
202
+ * Gets the number of documents matching the filter.
203
+ *
204
+ * @param field - Field of the document to find distinct values for
205
+ * @param filter - The filter for filtering the set of documents to which we apply the distinct filter.
206
+ * @param options - Optional settings for the command
207
+ * @protected
208
+ */
209
+ async __distinct(field, filter, options) {
210
+ const db = this.getDatabase();
211
+ const collection = await this.getCollection(db);
212
+ options = {
213
+ ...options,
214
+ session: options?.session || this.getSession()
215
+ };
216
+ try {
217
+ return await collection.distinct(field, filter || {}, options);
218
+ }
219
+ catch (e) {
220
+ await this.$onError?.(e, this);
221
+ throw e;
222
+ }
223
+ }
224
+ /**
225
+ * Execute an aggregation framework pipeline against the collection, needs MongoDB \>= 2.2
213
226
  *
214
- * @param {mongodb.Document[]} pipeline - The pipeline of aggregation stages to apply to the collection.
215
- * @param {mongodb.AggregateOptions} options - The options to customize the aggregation.
216
- * @returns {Promise<mongodb.ChangeStream<T>>} - A promise that resolves to a Change Stream that represents the result of the aggregation.
227
+ * @param pipeline - An array of aggregation pipelines to execute
228
+ * @param options - Optional settings for the command
217
229
  * @protected
218
230
  */
219
231
  async __aggregate(pipeline, options) {
@@ -234,10 +246,9 @@ class MongoService extends core_1.ApiService {
234
246
  /**
235
247
  * Fetches the first document that matches the filter
236
248
  *
237
- * @param {mongodb.Filter<T>} filter - The filter object to match documents against
238
- * @param {mongodb.FindOptions} [options] - The options to use when querying the collection
249
+ * @param filter - Query for find Operation
250
+ * @param options - Optional settings for the command
239
251
  * @protected
240
- * @returns {Promise<PartialDTO<T> | undefined>} - A promise that resolves to the first matching document, or undefined if no match is found
241
252
  */
242
253
  async __findOne(filter, options) {
243
254
  const db = this.getDatabase();
@@ -255,11 +266,11 @@ class MongoService extends core_1.ApiService {
255
266
  }
256
267
  }
257
268
  /**
258
- * Creates a cursor for a filter that can be used to iterate over results from MongoDB.
269
+ * Creates a cursor for a filter that can be used to iterate over results from MongoDB
259
270
  *
260
- * @param {mongodb.Filter<T>} filter - The filter to apply when searching for results.
261
- * @param {mongodb.FindOptions} [options] - The options to customize the search behavior.
262
- * @returns {mongodb.Cursor<T>} - The cursor object that can be used to iterate over the results.
271
+ * @param filter - The filter predicate. If unspecified,
272
+ * then all documents in the collection will match the predicate
273
+ * @param options - Optional settings for the command
263
274
  * @protected
264
275
  */
265
276
  async __find(filter, options) {
@@ -278,13 +289,12 @@ class MongoService extends core_1.ApiService {
278
289
  }
279
290
  }
280
291
  /**
281
- * Update a single document in a collection.
292
+ * Update a single document in a collection
282
293
  *
283
- * @param {mongodb.Filter<T>} filter - The filter to select the document(s) to update.
284
- * @param {mongodb.UpdateFilter<T>} update - The update operation to be applied on the selected document(s).
285
- * @param {mongodb.UpdateOptions} [options] - Optional settings for the update operation.
294
+ * @param filter - The filter used to select the document to update
295
+ * @param update - The update operations to be applied to the document
296
+ * @param options - Optional settings for the command
286
297
  * @protected
287
- * @returns {Promise<mongodb.UpdateResult>} - A promise that resolves to the result of the update operation.
288
298
  */
289
299
  async __updateOne(filter, update, options) {
290
300
  const db = this.getDatabase();
@@ -304,13 +314,12 @@ class MongoService extends core_1.ApiService {
304
314
  /**
305
315
  * Find a document and update it in one atomic operation. Requires a write lock for the duration of the operation.
306
316
  *
307
- * @param {mongodb.Filter<T>} filter - The filter to select the document to be updated.
308
- * @param {mongodb.UpdateFilter<T>} doc - The update document.
309
- * @param {mongodb.FindOneAndUpdateOptions} [options] - Optional options for the find one and update operation.
310
- * @returns {Promise<T | null>} - A promise that resolves to the updated document or null if no document matched the filter.
317
+ * @param filter - The filter used to select the document to update
318
+ * @param update - Update operations to be performed on the document
319
+ * @param options - Optional settings for the command
311
320
  * @protected
312
321
  */
313
- async __findOneAndUpdate(filter, doc, options) {
322
+ async __findOneAndUpdate(filter, update, options) {
314
323
  const db = this.getDatabase();
315
324
  const collection = await this.getCollection(db);
316
325
  const opts = {
@@ -320,7 +329,7 @@ class MongoService extends core_1.ApiService {
320
329
  session: options?.session || this.getSession(),
321
330
  };
322
331
  try {
323
- return await collection.findOneAndUpdate(filter || {}, doc, opts);
332
+ return await collection.findOneAndUpdate(filter || {}, update, opts);
324
333
  }
325
334
  catch (e) {
326
335
  await this.$onError?.(e, this);
@@ -328,15 +337,14 @@ class MongoService extends core_1.ApiService {
328
337
  }
329
338
  }
330
339
  /**
331
- * Update multiple documents in a collection.
340
+ * Update multiple documents in a collection
332
341
  *
333
- * @param {mongodb.Filter<T>} filter - The filter used to select the documents to be updated.
334
- * @param {mongodb.UpdateFilter<T> | Partial<T>} doc - The updates to be applied to the selected documents.
335
- * @param {StrictOmit<mongodb.UpdateOptions, 'upsert'>} [options] - The optional settings for the update operation.
336
- * @return {Promise<mongodb.UpdateResult>} - A Promise that resolves to the result of the update operation.
342
+ * @param filter - The filter used to select the documents to update
343
+ * @param update - The update operations to be applied to the documents
344
+ * @param options - Optional settings for the command
337
345
  * @protected
338
346
  */
339
- async __updateMany(filter, doc, options) {
347
+ async __updateMany(filter, update, options) {
340
348
  const db = this.getDatabase();
341
349
  const collection = await this.getCollection(db);
342
350
  options = {
@@ -345,7 +353,7 @@ class MongoService extends core_1.ApiService {
345
353
  upsert: false
346
354
  };
347
355
  try {
348
- return await collection.updateMany(filter || {}, doc, options);
356
+ return await collection.updateMany(filter || {}, update, options);
349
357
  }
350
358
  catch (e) {
351
359
  await this.$onError?.(e, this);
@@ -357,7 +365,6 @@ class MongoService extends core_1.ApiService {
357
365
  *
358
366
  * @protected
359
367
  *
360
- * @returns {Promise<mongodb.Db>} The database connection.
361
368
  * @throws {Error} If the context or database is not set.
362
369
  */
363
370
  getDatabase() {
@@ -373,7 +380,6 @@ class MongoService extends core_1.ApiService {
373
380
  *
374
381
  * @protected
375
382
  *
376
- * @returns {Promise<mongodb.ClientSession>} The database connection.
377
383
  * @throws {Error} If the context or database is not set.
378
384
  */
379
385
  getSession() {
@@ -384,9 +390,8 @@ class MongoService extends core_1.ApiService {
384
390
  /**
385
391
  * Retrieves a MongoDB collection from the given database.
386
392
  *
387
- * @param {mongodb.Db} db - The MongoDB database.
393
+ * @param db - The MongoDB database.
388
394
  * @protected
389
- * @returns {Promise<mongodb.Collection<T>>} A promise that resolves to the MongoDB collection.
390
395
  */
391
396
  async getCollection(db) {
392
397
  return db.collection(this.getCollectionName());
@@ -395,7 +400,7 @@ class MongoService extends core_1.ApiService {
395
400
  * Retrieves the collection name.
396
401
  *
397
402
  * @protected
398
- * @returns {string} The collection name.
403
+ * @returns The collection name.
399
404
  * @throws {Error} If the collection name is not defined.
400
405
  */
401
406
  getCollectionName() {
@@ -410,7 +415,7 @@ class MongoService extends core_1.ApiService {
410
415
  * Retrieves the resource name.
411
416
  *
412
417
  * @protected
413
- * @returns {string} The collection name.
418
+ * @returns {string} The resource name.
414
419
  * @throws {Error} If the collection name is not defined.
415
420
  */
416
421
  getResourceName() {
@@ -424,9 +429,9 @@ class MongoService extends core_1.ApiService {
424
429
  /**
425
430
  * Generates an encoder for the specified operation.
426
431
  *
427
- * @param {string} operation - The operation to generate the encoder for. Must be either 'create' or 'update'.
432
+ * @param operation - The operation to generate the encoder for. Must be either 'create' or 'update'.
428
433
  * @protected
429
- * @returns {IsObject.Validator} - The generated encoder for the specified operation.
434
+ * @returns - The generated encoder for the specified operation.
430
435
  */
431
436
  _generateEncoder(operation) {
432
437
  const dataType = this.getDataType();
@@ -441,7 +446,7 @@ class MongoService extends core_1.ApiService {
441
446
  * Generates an encoder for the specified operation.
442
447
  *
443
448
  * @protected
444
- * @returns {IsObject.Validator} - The generated encoder for the specified operation.
449
+ * @returns - The generated encoder for the specified operation.
445
450
  */
446
451
  _generateDecoder() {
447
452
  const dataType = this.getDataType();
@@ -151,6 +151,24 @@ export class MongoCollectionService extends MongoService {
151
151
  const r = await this.__deleteMany(filter, omit(options, 'filter'));
152
152
  return r.deletedCount;
153
153
  }
154
+ async distinct(field, options) {
155
+ const info = {
156
+ crud: 'read',
157
+ method: 'distinct',
158
+ options
159
+ };
160
+ return this._intercept(async () => {
161
+ const filter = MongoAdapter.prepareFilter([
162
+ await this._getDocumentFilter(info),
163
+ options?.filter,
164
+ ]);
165
+ return this._distinct(field, { ...options, filter });
166
+ }, info);
167
+ }
168
+ async _distinct(field, options) {
169
+ const filter = MongoAdapter.prepareFilter(options?.filter);
170
+ return await this.__distinct(field, filter, omit(options, 'filter'));
171
+ }
154
172
  /**
155
173
  * Checks if an object with the given id exists.
156
174
  *
@@ -361,21 +379,33 @@ export class MongoCollectionService extends MongoService {
361
379
  }, info);
362
380
  }
363
381
  async _update(id, input, options) {
364
- const encode = this.getEncoder('update');
365
- const doc = encode(input, { coerce: true });
366
- const patch = MongoAdapter.preparePatch(doc);
382
+ const isUpdateFilter = Array.isArray(input) ||
383
+ !!Object.keys(input).find(x => x.startsWith('$'));
384
+ const isDocument = !Array.isArray(input) &&
385
+ !!Object.keys(input).find(x => !x.startsWith('$'));
386
+ if (isUpdateFilter && isDocument)
387
+ throw new TypeError('You must pass one of MongoDB UpdateFilter or a partial document, not both');
388
+ let update;
389
+ if (isDocument) {
390
+ const encode = this.getEncoder('update');
391
+ const doc = encode(input, { coerce: true });
392
+ update = MongoAdapter.preparePatch(doc);
393
+ update.$set = update.$set || {};
394
+ }
395
+ else
396
+ update = input;
397
+ const filter = MongoAdapter.prepareFilter([
398
+ MongoAdapter.prepareKeyValues(id, [this.collectionKey]),
399
+ options?.filter
400
+ ]);
367
401
  const mongoOptions = {
368
402
  ...options,
369
403
  includeResultMetadata: false,
370
404
  upsert: undefined,
371
405
  projection: MongoAdapter.prepareProjection(this.getDataType(), options),
372
406
  };
373
- const filter = MongoAdapter.prepareFilter([
374
- MongoAdapter.prepareKeyValues(id, [this.collectionKey]),
375
- options?.filter
376
- ]);
377
407
  const decode = this.getDecoder();
378
- const out = await this.__findOneAndUpdate(filter, patch, mongoOptions);
408
+ const out = await this.__findOneAndUpdate(filter, update, mongoOptions);
379
409
  return out ? decode(out, { coerce: true }) : undefined;
380
410
  }
381
411
  /**
@@ -403,22 +433,33 @@ export class MongoCollectionService extends MongoService {
403
433
  }, info);
404
434
  }
405
435
  async _updateOnly(id, input, options) {
436
+ const isUpdateFilter = Array.isArray(input) ||
437
+ !!Object.keys(input).find(x => x.startsWith('$'));
438
+ const isDocument = !Array.isArray(input) &&
439
+ !!Object.keys(input).find(x => !x.startsWith('$'));
440
+ if (isUpdateFilter && isDocument)
441
+ throw new TypeError('You must pass one of MongoDB UpdateFilter or a partial document, not both');
442
+ let update;
443
+ if (isDocument) {
444
+ const encode = this.getEncoder('update');
445
+ const doc = encode(input, { coerce: true });
446
+ update = MongoAdapter.preparePatch(doc);
447
+ if (!Object.keys(doc).length)
448
+ return 0;
449
+ }
450
+ else
451
+ update = input;
406
452
  const filter = MongoAdapter.prepareFilter([
407
453
  MongoAdapter.prepareKeyValues(id, [this.collectionKey]),
408
454
  options?.filter
409
455
  ]);
410
- const encode = this.getEncoder('update');
411
- const doc = encode(input, { coerce: true });
412
- if (!Object.keys(doc).length)
413
- return 0;
414
- const patch = MongoAdapter.preparePatch(doc);
415
456
  const mongoOptions = {
416
457
  ...options,
417
458
  includeResultMetadata: false,
418
459
  upsert: undefined,
419
460
  projection: MongoAdapter.prepareProjection(this.getDataType(), options),
420
461
  };
421
- const out = await this.__updateOne(filter, patch, mongoOptions);
462
+ const out = await this.__updateOne(filter, update, mongoOptions);
422
463
  return out.matchedCount;
423
464
  }
424
465
  /**
@@ -444,18 +485,28 @@ export class MongoCollectionService extends MongoService {
444
485
  }, info);
445
486
  }
446
487
  async _updateMany(input, options) {
447
- const encode = this.getEncoder('update');
448
- const doc = encode(input, { coerce: true });
449
- if (!Object.keys(doc).length)
450
- return 0;
451
- const patch = MongoAdapter.preparePatch(doc);
452
- patch.$set = patch.$set || {};
488
+ const isUpdateFilter = Array.isArray(input) ||
489
+ !!Object.keys(input).find(x => x.startsWith('$'));
490
+ const isDocument = !Array.isArray(input) &&
491
+ !!Object.keys(input).find(x => !x.startsWith('$'));
492
+ if (isUpdateFilter && isDocument)
493
+ throw new TypeError('You must pass one of MongoDB UpdateFilter or a partial document, not both');
494
+ let update;
495
+ if (isDocument) {
496
+ const encode = this.getEncoder('update');
497
+ const doc = encode(input, { coerce: true });
498
+ update = MongoAdapter.preparePatch(doc);
499
+ if (!Object.keys(doc).length)
500
+ return 0;
501
+ }
502
+ else
503
+ update = input;
453
504
  const mongoOptions = {
454
505
  ...omit(options, 'filter'),
455
506
  upsert: undefined
456
507
  };
457
508
  const filter = MongoAdapter.prepareFilter(options?.filter);
458
- const r = await this.__updateMany(filter, patch, mongoOptions);
509
+ const r = await this.__updateMany(filter, update, mongoOptions);
459
510
  return r.matchedCount;
460
511
  }
461
512
  /**