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