@opra/mongodb 0.31.13 → 0.32.0

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,10 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MongoSingletonService = void 0;
4
- const tslib_1 = require("tslib");
5
4
  const mongodb_1 = require("mongodb");
6
5
  const common_1 = require("@opra/common");
7
- const OpraCommon = tslib_1.__importStar(require("@opra/common"));
8
6
  const mongo_adapter_js_1 = require("./mongo-adapter.js");
9
7
  const mongo_service_js_1 = require("./mongo-service.js");
10
8
  /**
@@ -14,75 +12,90 @@ const mongo_service_js_1 = require("./mongo-service.js");
14
12
  class MongoSingletonService extends mongo_service_js_1.MongoService {
15
13
  constructor(dataType, options) {
16
14
  super(dataType, options);
15
+ this.collectionKey = options?.collectionKey || '_id';
17
16
  this._id = options?._id || new mongodb_1.ObjectId('655608925cad472b75fc6485');
18
17
  }
19
- get resource() {
20
- const resource = this.context.request.resource;
21
- if (resource instanceof OpraCommon.Singleton)
22
- return resource;
23
- throw new TypeError(`"${resource}" resource is not a Singleton`);
18
+ /**
19
+ * Fetches the Document. Throws error undefined if not found.
20
+ *
21
+ * @param options
22
+ */
23
+ async assert(options) {
24
+ const out = await this.get(options);
25
+ if (!out)
26
+ throw new common_1.ResourceNotFoundError(this.resourceName || this.getCollectionName());
27
+ return out;
24
28
  }
29
+ /**
30
+ * Inserts the document into MongoDB
31
+ *
32
+ * @param doc
33
+ * @param options
34
+ */
25
35
  async create(doc, options) {
26
- const r = await this._rawInsertOne({ ...doc, _id: this._id }, options);
27
- if (r.insertedId) {
28
- const out = await this.get(options);
29
- if (out)
30
- return out;
31
- }
36
+ const r = await this.__insertOne({ ...doc, _id: this._id }, options);
37
+ if (r.insertedId)
38
+ return doc;
32
39
  /* istanbul ignore next */
33
40
  throw new Error('Unknown error while creating document');
34
41
  }
42
+ /**
43
+ * Delete the document from a collection
44
+ *
45
+ * @param options
46
+ */
35
47
  async delete(options) {
36
- const filter = { _id: this._id };
37
- const optionsFilter = mongo_adapter_js_1.MongoAdapter.prepareFilter(options?.filter);
38
- if (optionsFilter)
39
- filter.$and = [...(Array.isArray(optionsFilter) ? optionsFilter : [optionsFilter])];
40
- const r = await this._rawDeleteOne(filter, options);
48
+ const filter = mongo_adapter_js_1.MongoAdapter.prepareFilter([{ _id: this._id }, options?.filter]);
49
+ const r = await this.__deleteOne(filter, options);
41
50
  return r.deletedCount;
42
51
  }
43
- async get(options) {
44
- const out = await this.find(options);
45
- if (!out)
46
- throw new common_1.ResourceNotFoundError(this.resource.name);
47
- return out;
52
+ /**
53
+ * Checks if the document exists.
54
+ * Returns true if document exists, false otherwise
55
+ *
56
+ */
57
+ async exists() {
58
+ return !!(await this.get({ pick: ['_id'] }));
48
59
  }
49
- async find(options) {
50
- const filter = { _id: this._id };
51
- const optionsFilter = mongo_adapter_js_1.MongoAdapter.prepareFilter(options?.filter);
52
- if (optionsFilter)
53
- filter.$and = [...(Array.isArray(optionsFilter) ? optionsFilter : [optionsFilter])];
60
+ /**
61
+ * Fetches the document
62
+ *
63
+ * @param options
64
+ */
65
+ async get(options) {
66
+ const filter = mongo_adapter_js_1.MongoAdapter.prepareFilter([{ _id: this._id }, options?.filter]);
54
67
  const mongoOptions = {
55
68
  ...options,
56
- projection: mongo_adapter_js_1.MongoAdapter.prepareProjection(this.resource.type, options),
69
+ projection: mongo_adapter_js_1.MongoAdapter.prepareProjection(this.getDataType(), options),
57
70
  sort: undefined,
58
71
  skip: undefined,
59
72
  limit: undefined
60
73
  };
61
- const out = await this._rawFindOne(filter, mongoOptions);
62
- if (out) {
63
- if (this.transformData)
64
- return this.transformData(out);
65
- return out;
66
- }
74
+ return await this.__findOne(filter, mongoOptions);
67
75
  }
76
+ /**
77
+ * Updates a single document.
78
+ * Returns updated document
79
+ *
80
+ * @param doc
81
+ * @param options
82
+ */
68
83
  async update(doc, options) {
69
- // Prevent upsert with different _id field
70
- if (options?.upsert)
71
- doc._id = this._id;
72
- else
73
- delete doc._id;
84
+ // Prevent updating _id field
85
+ delete doc._id;
86
+ const filter = mongo_adapter_js_1.MongoAdapter.prepareFilter([
87
+ { _id: this._id },
88
+ options?.filter
89
+ ]);
74
90
  const patch = mongo_adapter_js_1.MongoAdapter.preparePatch(doc);
75
91
  const mongoOptions = {
76
92
  ...options,
77
- upsert: undefined
93
+ includeResultMetadata: false,
94
+ upsert: undefined,
95
+ projection: mongo_adapter_js_1.MongoAdapter.prepareProjection(this.getDataType(), options),
78
96
  };
79
- const filter = { _id: this._id };
80
- const optionsFilter = mongo_adapter_js_1.MongoAdapter.prepareFilter(options?.filter);
81
- if (optionsFilter)
82
- filter.$and = [...(Array.isArray(optionsFilter) ? optionsFilter : [optionsFilter])];
83
- const r = await this._rawUpdateOne(filter, patch, mongoOptions);
84
- if (r.matchedCount)
85
- return await this.get(options);
97
+ const out = await this.__findOneAndUpdate(filter, patch, mongoOptions);
98
+ return out || undefined;
86
99
  }
87
100
  }
88
101
  exports.MongoSingletonService = MongoSingletonService;
package/cjs/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,4 +1,3 @@
1
- import '@opra/core';
2
1
  import { OpraFilter } from '@opra/common';
3
2
  const opMap = {
4
3
  '=': '$eq',
@@ -10,15 +9,71 @@ const opMap = {
10
9
  'in': '$in',
11
10
  '!in': '$nin',
12
11
  };
13
- export default function prepareFilter(filter) {
14
- if (!filter)
15
- return;
16
- if (typeof filter === 'string')
17
- return prepareFilterAst(OpraFilter.parse(filter));
18
- else if (filter instanceof OpraFilter.Expression)
19
- return prepareFilterAst(filter);
20
- else
21
- return filter;
12
+ /**
13
+ * Convert filter expressions to MongoDB filter objects.
14
+ * This method also merges multiple expressions into one single filter object
15
+ * @param filters
16
+ */
17
+ export default function prepareFilter(filters, options) {
18
+ const filtersArray = Array.isArray(filters) ? filters : [filters];
19
+ if (!filtersArray.length)
20
+ return {};
21
+ let i = 0;
22
+ const out = {};
23
+ for (const filter of filtersArray) {
24
+ if (!filter)
25
+ continue;
26
+ let x;
27
+ if (typeof filter === 'string')
28
+ x = prepareFilterAst(OpraFilter.parse(filter));
29
+ else if (filter instanceof OpraFilter.Expression)
30
+ x = prepareFilterAst(filter);
31
+ else
32
+ x = filter;
33
+ if (Array.isArray(x))
34
+ x = { $and: out };
35
+ // Merge $and arrays
36
+ if (x.$and) {
37
+ out.$and = out.$and || [];
38
+ out.$and.push(...x.$and);
39
+ delete x.$and;
40
+ }
41
+ // Merge $or arrays
42
+ if (x.$or) {
43
+ out.$or = out.$or || [];
44
+ out.$or.push(...x.$or);
45
+ delete x.$or;
46
+ }
47
+ for (const k of Object.keys(x)) {
48
+ // If result object has filter field we convert it to $and
49
+ if (out[k]) {
50
+ out.$and = out.$and || [];
51
+ out.$and.push({ [k]: out[k] });
52
+ out.$and.push({ [k]: x[k] });
53
+ delete out[k];
54
+ continue;
55
+ }
56
+ out[k] = x[k];
57
+ }
58
+ i++;
59
+ }
60
+ return i
61
+ ? (options?.fieldPrefix ? addPrefix(out, options.fieldPrefix) : out)
62
+ : undefined;
63
+ }
64
+ function addPrefix(source, prefix) {
65
+ if (typeof source !== 'object')
66
+ return source;
67
+ if (Array.isArray(source))
68
+ return source.map(v => addPrefix(v, prefix));
69
+ const out = {};
70
+ for (const [k, v] of Object.entries(source)) {
71
+ if (k.startsWith('$'))
72
+ out[k] = addPrefix(v, prefix);
73
+ else
74
+ out[prefix + k] = addPrefix(v, prefix);
75
+ }
76
+ return out;
22
77
  }
23
78
  function prepareFilterAst(ast, negative) {
24
79
  if (!ast)
@@ -1,6 +1,7 @@
1
1
  export default function preparePatch(doc, options) {
2
2
  const trg = {};
3
3
  _preparePatch(doc, trg, '', options);
4
+ trg.$set = trg.$set || {};
4
5
  return trg;
5
6
  }
6
7
  function _preparePatch(src, trg = {}, path, options) {
@@ -1,46 +1,47 @@
1
1
  import { ComplexType, pathToObjectTree } from '@opra/common';
2
- export default function prepareProjection(dataType, args) {
2
+ export default function prepareProjection(dataType, options) {
3
3
  const out = {};
4
- // omitExclusiveFields(dataType, out);
5
- const pick = args?.pick && pathToObjectTree(args.include ? [...args.pick, ...args.include] : args.pick);
6
- const include = !args?.pick && args?.include && pathToObjectTree(args.include);
7
- const omit = args?.omit && pathToObjectTree(args.omit);
8
- if (pick || include) {
9
- _prepareInclusionProjection(dataType, out, pick, include, omit);
10
- }
11
- else
12
- _prepareExclusionProjection(dataType, out, omit, !omit);
4
+ const pick = options?.pick && pathToObjectTree(options.pick);
5
+ const include = options?.include && pathToObjectTree(options.include);
6
+ const omit = options?.omit && pathToObjectTree(options.omit);
7
+ // const exclusionProjection = !pick && !!omit;
8
+ _prepareProjection(dataType, out, {
9
+ pickActivated: !!pick,
10
+ pick,
11
+ include,
12
+ omit
13
+ });
13
14
  return Object.keys(out).length ? out : undefined;
14
15
  }
15
- export function _prepareInclusionProjection(dataType, target, pick, include, omit, defaultFields) {
16
- defaultFields = defaultFields ?? !pick;
17
- let n;
16
+ export function _prepareProjection(dataType, target,
17
+ // exclusionProjection: boolean,
18
+ options) {
19
+ // const defaultFields = options?.defaultFields ?? !options?.pick;
20
+ const optionsOmit = options?.omit;
21
+ const optionsPick = options?.pick;
22
+ const optionsInclude = options?.include;
23
+ const pickActivated = options?.pickActivated;
18
24
  for (const [k, f] of dataType.fields.entries()) {
19
- if (omit?.[k] === true)
25
+ const fieldOmit = optionsOmit?.[k];
26
+ const fieldInclude = optionsInclude?.[k];
27
+ const fieldPick = optionsPick?.[k];
28
+ if (fieldOmit === true ||
29
+ !((pickActivated && fieldPick) ||
30
+ (!pickActivated && (!f.exclusive || fieldInclude))))
31
+ continue;
32
+ if (f.type instanceof ComplexType &&
33
+ (typeof fieldInclude === 'object' ||
34
+ typeof fieldPick === 'object' ||
35
+ typeof fieldOmit === 'object')) {
36
+ target[k] = {};
37
+ _prepareProjection(f.type, target[k], {
38
+ pickActivated: fieldPick != null && fieldPick !== true,
39
+ include: typeof fieldInclude === 'object' ? fieldInclude : undefined,
40
+ pick: typeof fieldPick === 'object' ? fieldPick : undefined,
41
+ omit: typeof fieldOmit === 'object' ? fieldOmit : undefined
42
+ });
20
43
  continue;
21
- n = (defaultFields && !f.exclusive) ||
22
- pick === true || pick?.[k] || include === true || include?.[k];
23
- if (n) {
24
- if (f.type instanceof ComplexType && (typeof n === 'object' || typeof omit?.[k] === 'object')) {
25
- target[k] = {};
26
- _prepareInclusionProjection(f.type, target[k], pick?.[k] || include?.[k], undefined, omit?.[k], defaultFields);
27
- continue;
28
- }
29
- target[k] = 1;
30
- }
31
- }
32
- }
33
- export function _prepareExclusionProjection(dataType, target, omit, omitExclusiveFields) {
34
- let n;
35
- for (const [k, f] of dataType.fields.entries()) {
36
- n = omit?.[k] || (omitExclusiveFields && f.exclusive);
37
- if (n) {
38
- if (f.type instanceof ComplexType && typeof n === 'object') {
39
- target[k] = {};
40
- _prepareExclusionProjection(f.type, target[k], omit?.[k], omitExclusiveFields);
41
- continue;
42
- }
43
- target[k] = 0;
44
44
  }
45
+ target[k] = 1;
45
46
  }
46
47
  }
package/esm/index.js CHANGED
@@ -1,4 +1,6 @@
1
1
  export * from './mongo-adapter.js';
2
+ export * from './mongo-array-service.js';
2
3
  export * from './mongo-collection-service.js';
3
4
  export * from './mongo-service.js';
4
5
  export * from './mongo-singleton-service.js';
6
+ export * from './types.js';
@@ -0,0 +1,339 @@
1
+ import omit from 'lodash.omit';
2
+ import { ObjectId } from 'mongodb';
3
+ import { ComplexType, NotAcceptableError, ResourceNotFoundError } from '@opra/common';
4
+ import { MongoAdapter } from './mongo-adapter.js';
5
+ import { MongoService } from './mongo-service.js';
6
+ /**
7
+ *
8
+ * @class MongoArrayService
9
+ */
10
+ export class MongoArrayService extends MongoService {
11
+ constructor(dataType, fieldName, options) {
12
+ super(dataType, options);
13
+ this._encoders = {};
14
+ this.fieldName = fieldName;
15
+ this.defaultLimit = options?.defaultLimit || 10;
16
+ this.collectionKey = options?.collectionKey || '_id';
17
+ this.arrayKey = options?.arrayKey || '_id';
18
+ }
19
+ getArrayDataType() {
20
+ const t = this.getDataType()
21
+ .getField(this.fieldName).type;
22
+ if (!(t instanceof ComplexType))
23
+ throw new NotAcceptableError(`Data type "${t.name}" is not a ComplexType`);
24
+ return t;
25
+ }
26
+ /**
27
+ * Checks if array item exists. Throws error if not.
28
+ *
29
+ * @param parentId
30
+ * @param id
31
+ */
32
+ async assert(parentId, id) {
33
+ if (!(await this.exists(parentId, id)))
34
+ throw new ResourceNotFoundError((this.resourceName || this.getCollectionName()) + '.' + this.arrayKey, parentId + '/' + id);
35
+ }
36
+ /**
37
+ * Adds a single item into array field.
38
+ *
39
+ * @param parentId
40
+ * @param input
41
+ * @param options
42
+ */
43
+ async create(parentId, input, options) {
44
+ const encode = this._getEncoder('create');
45
+ const doc = encode(input);
46
+ doc[this.arrayKey] = doc[this.arrayKey] || this._generateId();
47
+ const docFilter = MongoAdapter.prepareKeyValues(parentId, [this.collectionKey]);
48
+ const r = await this.__updateOne(docFilter, {
49
+ $push: { [this.fieldName]: doc }
50
+ }, options);
51
+ if (r.modifiedCount)
52
+ try {
53
+ return this.get(parentId, doc[this.arrayKey], { ...options, filter: undefined, skip: undefined });
54
+ }
55
+ catch (e) {
56
+ Error.captureStackTrace(e);
57
+ throw e;
58
+ }
59
+ throw new ResourceNotFoundError(this.resourceName || this.getCollectionName(), parentId);
60
+ }
61
+ /**
62
+ * Gets the number of array items matching the filter.
63
+ * @param parentId
64
+ * @param options
65
+ */
66
+ async count(parentId, options) {
67
+ const matchFilter = MongoAdapter.prepareKeyValues(parentId, [this.collectionKey]);
68
+ const stages = [
69
+ { $match: matchFilter },
70
+ { $unwind: { path: "$" + this.fieldName } },
71
+ { $replaceRoot: { newRoot: "$" + this.fieldName } }
72
+ ];
73
+ if (options?.filter) {
74
+ const optionsFilter = MongoAdapter.prepareFilter(options.filter);
75
+ stages.push({ $match: optionsFilter });
76
+ }
77
+ stages.push({ $count: '*' });
78
+ const r = await this.__aggregate(stages, options);
79
+ try {
80
+ const n = await r.next();
81
+ return n?.['*'] || 0;
82
+ }
83
+ finally {
84
+ await r.close();
85
+ }
86
+ }
87
+ /**
88
+ * Removes one item from an array field
89
+ *
90
+ * @param parentId
91
+ * @param id
92
+ * @param options
93
+ */
94
+ async delete(parentId, id, options) {
95
+ const matchFilter = MongoAdapter.prepareKeyValues(parentId, [this.collectionKey]);
96
+ const pullFilter = MongoAdapter.prepareFilter([
97
+ MongoAdapter.prepareKeyValues(id, [this.arrayKey]),
98
+ options?.filter
99
+ ]);
100
+ const r = await this.__updateOne(matchFilter, {
101
+ $pull: { [this.fieldName]: pullFilter }
102
+ }, options);
103
+ return r.modifiedCount ? 1 : 0;
104
+ }
105
+ /**
106
+ * Removes multiple items from an array field
107
+ *
108
+ * @param parentId
109
+ * @param options
110
+ */
111
+ async deleteMany(parentId, options) {
112
+ const docFilter = MongoAdapter.prepareKeyValues(parentId, [this.collectionKey]);
113
+ // Count matching items, we will use this as result
114
+ const matchCount = await this.count(parentId, options);
115
+ const pullFilter = MongoAdapter.prepareFilter(options?.filter) || {};
116
+ const r = await this.__updateOne(docFilter, {
117
+ $pull: { [this.fieldName]: pullFilter }
118
+ }, options);
119
+ if (r.modifiedCount)
120
+ return matchCount;
121
+ return 0;
122
+ }
123
+ /**
124
+ * Returns true if item exists, false otherwise
125
+ *
126
+ * @param parentId
127
+ * @param id
128
+ */
129
+ async exists(parentId, id) {
130
+ return !!(await this.findById(parentId, id, { pick: ['_id'] }));
131
+ }
132
+ /**
133
+ * Fetches the first item in an array field that matches by id.
134
+ *
135
+ * @param parentId
136
+ * @param id
137
+ * @param options
138
+ */
139
+ async findById(parentId, id, options) {
140
+ let filter = MongoAdapter.prepareKeyValues(id, [this.arrayKey]);
141
+ if (options?.filter)
142
+ filter = MongoAdapter.prepareFilter([filter, options?.filter]);
143
+ return await this.findOne(parentId, { ...options, filter });
144
+ }
145
+ /**
146
+ * Fetches the first item in an array field that matches the filter. Returns undefined if not found.
147
+ *
148
+ * @param parentId
149
+ * @param options
150
+ */
151
+ async findOne(parentId, options) {
152
+ const rows = await this.findMany(parentId, {
153
+ ...options,
154
+ limit: 1
155
+ });
156
+ return rows?.[0];
157
+ }
158
+ /**
159
+ * Fetches all items in an array field that matches the filter
160
+ *
161
+ * @param parentId
162
+ * @param options
163
+ */
164
+ async findMany(parentId, options) {
165
+ const matchFilter = MongoAdapter.prepareKeyValues(parentId, [this.collectionKey]);
166
+ const mongoOptions = {
167
+ ...omit(options, ['pick', 'include', 'omit', 'sort', 'skip', 'limit', 'filter', 'count'])
168
+ };
169
+ const limit = options?.limit || this.defaultLimit;
170
+ const stages = [
171
+ { $match: matchFilter },
172
+ { $unwind: { path: "$" + this.fieldName } },
173
+ { $replaceRoot: { newRoot: "$" + this.fieldName } }
174
+ ];
175
+ let dataStages = stages;
176
+ if (options?.count) {
177
+ dataStages = [];
178
+ stages.push({
179
+ $facet: {
180
+ data: dataStages,
181
+ count: [{ $count: 'totalMatches' }]
182
+ }
183
+ });
184
+ }
185
+ if (options?.filter) {
186
+ const optionsFilter = MongoAdapter.prepareFilter(options?.filter);
187
+ dataStages.push({ $match: optionsFilter });
188
+ }
189
+ if (options?.skip)
190
+ dataStages.push({ $skip: options.skip });
191
+ if (options?.sort) {
192
+ const sort = MongoAdapter.prepareSort(options.sort);
193
+ if (sort)
194
+ dataStages.push({ $sort: sort });
195
+ }
196
+ dataStages.push({ $limit: limit });
197
+ const dataType = this.getArrayDataType();
198
+ const projection = MongoAdapter.prepareProjection(dataType, options);
199
+ if (projection)
200
+ dataStages.push({ $project: projection });
201
+ const cursor = await this.__aggregate(stages, {
202
+ ...mongoOptions
203
+ });
204
+ try {
205
+ if (options?.count) {
206
+ const facetResult = await cursor.toArray();
207
+ this.context.response.totalMatches = facetResult[0].count[0].totalMatches || 0;
208
+ return facetResult[0].data;
209
+ }
210
+ else
211
+ return await cursor.toArray();
212
+ }
213
+ finally {
214
+ if (!cursor.closed)
215
+ await cursor.close();
216
+ }
217
+ }
218
+ /**
219
+ * Fetches the first item in an array field that matches the item id. Throws error undefined if not found.
220
+ *
221
+ * @param parentId
222
+ * @param id
223
+ * @param options
224
+ */
225
+ async get(parentId, id, options) {
226
+ const out = await this.findById(parentId, id, options);
227
+ if (!out)
228
+ throw new ResourceNotFoundError((this.resourceName || this.getCollectionName()) + '.' + this.arrayKey, parentId + '/' + id);
229
+ return out;
230
+ }
231
+ /**
232
+ * Update a single item in array field
233
+ *
234
+ * @param parentId
235
+ * @param id
236
+ * @param input
237
+ * @param options
238
+ */
239
+ async update(parentId, id, input, options) {
240
+ await this.updateOnly(parentId, id, input, options);
241
+ try {
242
+ return await this.findById(parentId, id, options);
243
+ }
244
+ catch (e) {
245
+ Error.captureStackTrace(e);
246
+ throw e;
247
+ }
248
+ }
249
+ /**
250
+ * Update a single item in array field
251
+ * Returns how many master documents updated (not array items)
252
+ *
253
+ * @param parentId
254
+ * @param id
255
+ * @param doc
256
+ * @param options
257
+ */
258
+ async updateOnly(parentId, id, doc, options) {
259
+ let filter = MongoAdapter.prepareKeyValues(id, [this.arrayKey]);
260
+ if (options?.filter)
261
+ filter = MongoAdapter.prepareFilter([filter, options?.filter]);
262
+ return await this.updateMany(parentId, doc, { ...options, filter });
263
+ }
264
+ /**
265
+ * Update multiple items in array field, returns how many master documents updated (not array items)
266
+ *
267
+ * @param parentId
268
+ * @param input
269
+ * @param options
270
+ */
271
+ async updateMany(parentId, input, options) {
272
+ const encode = this._getEncoder('update');
273
+ const doc = encode(input);
274
+ const matchFilter = MongoAdapter.prepareFilter([
275
+ MongoAdapter.prepareKeyValues(parentId, [this.collectionKey]),
276
+ { [this.fieldName]: { $exists: true } }
277
+ ]);
278
+ if (options?.filter) {
279
+ const elemMatch = MongoAdapter.prepareFilter(options?.filter, { fieldPrefix: 'elem.' });
280
+ options = options || {};
281
+ options.arrayFilters = [elemMatch];
282
+ }
283
+ const update = MongoAdapter.preparePatch(doc, {
284
+ fieldPrefix: this.fieldName + (options?.filter ? '.$[elem].' : '.$[].')
285
+ });
286
+ const r = await this.__updateOne(matchFilter, update, options);
287
+ return r.modifiedCount;
288
+ }
289
+ /**
290
+ * Update multiple items in array field and returns number of updated array items
291
+ *
292
+ * @param parentId
293
+ * @param doc
294
+ * @param options
295
+ */
296
+ async updateManyReturnCount(parentId, doc, options) {
297
+ const r = await this.updateMany(parentId, doc, options);
298
+ return r
299
+ // Count matching items that fits filter criteria
300
+ ? await this.count(parentId, options)
301
+ : 0;
302
+ }
303
+ /**
304
+ * Generates Id value
305
+ *
306
+ * @protected
307
+ */
308
+ _generateId() {
309
+ return new ObjectId();
310
+ }
311
+ /**
312
+ * Generates a new Validator for encoding or returns from cache if already generated before
313
+ * @param operation
314
+ * @protected
315
+ */
316
+ _getEncoder(operation) {
317
+ let encoder = this._encoders[operation];
318
+ if (encoder)
319
+ return encoder;
320
+ encoder = this._generateEncoder(operation);
321
+ this._encoders[operation] = encoder;
322
+ return encoder;
323
+ }
324
+ /**
325
+ * Generates a new Valgen Validator for encode operation
326
+ *
327
+ * @param operation
328
+ * @protected
329
+ */
330
+ _generateEncoder(operation) {
331
+ const dataType = this.getArrayDataType();
332
+ const options = {};
333
+ if (operation === 'update') {
334
+ options.omit = ['_id'];
335
+ options.partial = true;
336
+ }
337
+ return dataType.generateCodec('encode', options);
338
+ }
339
+ }