@opra/mongodb 1.0.0-alpha.1 → 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.
@@ -167,6 +167,8 @@ function prepareFilterAst(ast, negative) {
167
167
  },
168
168
  }, !negative),
169
169
  };
170
+ default:
171
+ break;
170
172
  }
171
173
  throw new Error(`Unimplemented ComparisonExpression operation (right side is ${ast.right.kind})`);
172
174
  }
@@ -6,13 +6,14 @@ const defaultPrimaryKey = ['_id'];
6
6
  function prepareKeyValues(keyValue, primaryKey) {
7
7
  primaryKey = primaryKey || defaultPrimaryKey;
8
8
  const b = (0, putil_isplainobject_1.default)(keyValue);
9
- if (primaryKey.length > 1 && !b)
10
- new TypeError(`Argument "keyValue" must be an object that contains all key values`);
9
+ if (primaryKey.length > 1 && !b) {
10
+ throw new TypeError(`Argument "keyValue" must be an object that contains all key values`);
11
+ }
11
12
  if (primaryKey.length > 1 || b) {
12
13
  return primaryKey.reduce((o, k) => {
13
14
  o[k] = keyValue[k];
14
15
  if (o[k] == null)
15
- new Error(`Value of key "${k}" is required`);
16
+ throw new Error(`Value of key "${k}" is required`);
16
17
  return o;
17
18
  }, {});
18
19
  }
@@ -7,10 +7,11 @@ function preparePatch(doc, options) {
7
7
  return trg;
8
8
  }
9
9
  exports.default = preparePatch;
10
- function _preparePatch(src, trg = {}, path, options) {
10
+ function _preparePatch(src, trg, path, options) {
11
11
  let f;
12
12
  let key;
13
13
  let field;
14
+ trg = trg || {};
14
15
  const fieldPrefix = options?.fieldPrefix;
15
16
  for (const [k, v] of Object.entries(src)) {
16
17
  f = k.startsWith('*') ? k.substring(1) : k;
package/cjs/index.js CHANGED
@@ -2,7 +2,7 @@
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-nested-service.js"), exports);
6
5
  tslib_1.__exportStar(require("./mongo-collection-service.js"), exports);
6
+ tslib_1.__exportStar(require("./mongo-nested-service.js"), exports);
7
7
  tslib_1.__exportStar(require("./mongo-service.js"), exports);
8
8
  tslib_1.__exportStar(require("./mongo-singleton-service.js"), exports);
@@ -17,7 +17,7 @@ var MongoAdapter;
17
17
  async function parseRequest(context) {
18
18
  const { operation } = context;
19
19
  if (operation.composition?.startsWith('Entity.') && operation.compositionOptions?.type) {
20
- const { compositionOptions } = operation;
20
+ const controller = operation.owner;
21
21
  switch (operation.composition) {
22
22
  case 'Entity.Create': {
23
23
  const data = await context.getBody();
@@ -27,7 +27,8 @@ var MongoAdapter;
27
27
  return { method: 'create', data, options };
28
28
  }
29
29
  case 'Entity.Delete': {
30
- const key = context.pathParams[compositionOptions.keyParameter];
30
+ const keyParam = operation.parameters.find(p => p.keyParam) || controller.parameters.find(p => p.keyParam);
31
+ const key = keyParam && context.pathParams[String(keyParam.name)];
31
32
  const options = {
32
33
  filter: context.queryParams.filter,
33
34
  };
@@ -51,7 +52,8 @@ var MongoAdapter;
51
52
  return { method: 'findMany', options };
52
53
  }
53
54
  case 'Entity.Get': {
54
- const key = context.pathParams[compositionOptions.keyParameter];
55
+ const keyParam = operation.parameters.find(p => p.keyParam) || controller.parameters.find(p => p.keyParam);
56
+ const key = keyParam && context.pathParams[String(keyParam.name)];
55
57
  const options = {
56
58
  projection: context.queryParams.projection,
57
59
  filter: context.queryParams.filter,
@@ -60,7 +62,8 @@ var MongoAdapter;
60
62
  }
61
63
  case 'Entity.Update': {
62
64
  const data = await context.getBody();
63
- const key = context.pathParams[compositionOptions.keyParameter];
65
+ const keyParam = operation.parameters.find(p => p.keyParam) || controller.parameters.find(p => p.keyParam);
66
+ const key = keyParam && context.pathParams[String(keyParam.name)];
64
67
  const options = {
65
68
  projection: context.queryParams.projection,
66
69
  filter: context.queryParams.filter,
@@ -74,6 +77,8 @@ var MongoAdapter;
74
77
  };
75
78
  return { method: 'updateMany', data, options };
76
79
  }
80
+ default:
81
+ break;
77
82
  }
78
83
  }
79
84
  throw new Error(`This operation is not compatible to MongoDB adapter`);
@@ -1,8 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MongoCollectionService = void 0;
4
- const mongodb_1 = require("mongodb");
5
4
  const common_1 = require("@opra/common");
5
+ const mongodb_1 = require("mongodb");
6
6
  const mongo_adapter_js_1 = require("./mongo-adapter.js");
7
7
  const mongo_entity_service_js_1 = require("./mongo-entity-service.js");
8
8
  /**
@@ -2,9 +2,9 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MongoEntityService = void 0;
4
4
  const tslib_1 = require("tslib");
5
- const lodash_omit_1 = tslib_1.__importDefault(require("lodash.omit"));
6
5
  const assert = tslib_1.__importStar(require("node:assert"));
7
6
  const common_1 = require("@opra/common");
7
+ const lodash_omit_1 = tslib_1.__importDefault(require("lodash.omit"));
8
8
  const mongo_adapter_js_1 = require("./mongo-adapter.js");
9
9
  const mongo_service_js_1 = require("./mongo-service.js");
10
10
  /**
@@ -30,8 +30,8 @@ class MongoEntityService extends mongo_service_js_1.MongoService {
30
30
  * @protected
31
31
  */
32
32
  async _create(input, options) {
33
- const encode = this.getEncoder('create');
34
- const doc = encode(input);
33
+ const inputCodec = this.getInputCodec('create');
34
+ const doc = inputCodec(input);
35
35
  assert.ok(doc._id, 'You must provide the "_id" field');
36
36
  const r = await this._dbInsertOne(doc, options);
37
37
  if (r.insertedId) {
@@ -99,14 +99,15 @@ class MongoEntityService extends mongo_service_js_1.MongoService {
99
99
  const filter = mongo_adapter_js_1.MongoAdapter.prepareFilter([mongo_adapter_js_1.MongoAdapter.prepareKeyValues(id, ['_id']), options?.filter]);
100
100
  const mongoOptions = {
101
101
  ...options,
102
- projection: mongo_adapter_js_1.MongoAdapter.prepareProjection(this.getDataType(), options?.projection),
102
+ projection: mongo_adapter_js_1.MongoAdapter.prepareProjection(this.dataType, options?.projection),
103
103
  limit: undefined,
104
104
  skip: undefined,
105
105
  sort: undefined,
106
106
  };
107
- const decode = this.getDecoder();
108
107
  const out = await this._dbFindOne(filter, mongoOptions);
109
- return out ? decode(out) : undefined;
108
+ const outputCodec = this.getOutputCodec('find');
109
+ if (out)
110
+ return outputCodec(out);
110
111
  }
111
112
  /**
112
113
  * Finds a document in the collection that matches the specified options.
@@ -119,12 +120,13 @@ class MongoEntityService extends mongo_service_js_1.MongoService {
119
120
  const mongoOptions = {
120
121
  ...(0, lodash_omit_1.default)(options, 'filter'),
121
122
  sort: options?.sort ? mongo_adapter_js_1.MongoAdapter.prepareSort(options.sort) : undefined,
122
- projection: mongo_adapter_js_1.MongoAdapter.prepareProjection(this.getDataType(), options?.projection),
123
+ projection: mongo_adapter_js_1.MongoAdapter.prepareProjection(this.dataType, options?.projection),
123
124
  limit: undefined,
124
125
  };
125
- const decode = this.getDecoder();
126
126
  const out = await this._dbFindOne(filter, mongoOptions);
127
- return out ? decode(out, { coerce: true }) : undefined;
127
+ const outputCodec = this.getOutputCodec('find');
128
+ if (out)
129
+ return outputCodec(out);
128
130
  }
129
131
  /**
130
132
  * Finds multiple documents in the MongoDB collection.
@@ -151,16 +153,16 @@ class MongoEntityService extends mongo_service_js_1.MongoService {
151
153
  stages.push({ $sort: sort });
152
154
  }
153
155
  stages.push({ $limit: limit });
154
- const dataType = this.getDataType();
156
+ const dataType = this.dataType;
155
157
  const projection = mongo_adapter_js_1.MongoAdapter.prepareProjection(dataType, options?.projection);
156
158
  if (projection)
157
159
  stages.push({ $project: projection });
158
- const decode = this.getDecoder();
159
160
  const cursor = await this._dbAggregate(stages, mongoOptions);
160
161
  /** Execute db command */
161
162
  try {
162
163
  /** Fetch the cursor and decode the result objects */
163
- return (await cursor.toArray()).map((r) => decode(r));
164
+ const outputCodec = this.getOutputCodec('find');
165
+ return (await cursor.toArray()).map((r) => outputCodec(r));
164
166
  }
165
167
  finally {
166
168
  if (!cursor.closed)
@@ -205,11 +207,11 @@ class MongoEntityService extends mongo_service_js_1.MongoService {
205
207
  dataStages.push({ $sort: sort });
206
208
  }
207
209
  dataStages.push({ $limit: limit });
208
- const dataType = this.getDataType();
210
+ const dataType = this.dataType;
209
211
  const projection = mongo_adapter_js_1.MongoAdapter.prepareProjection(dataType, options?.projection);
210
212
  if (projection)
211
213
  dataStages.push({ $project: projection });
212
- const decode = this.getDecoder();
214
+ const outputCodec = this.getOutputCodec('find');
213
215
  /** Execute db command */
214
216
  const cursor = await this._dbAggregate(stages, mongoOptions);
215
217
  try {
@@ -217,7 +219,7 @@ class MongoEntityService extends mongo_service_js_1.MongoService {
217
219
  const facetResult = await cursor.toArray();
218
220
  return {
219
221
  count: facetResult[0].count[0]?.totalMatches || 0,
220
- items: facetResult[0].data?.map((r) => decode(r)),
222
+ items: facetResult[0].data?.map((r) => outputCodec(r)),
221
223
  };
222
224
  }
223
225
  finally {
@@ -237,12 +239,13 @@ class MongoEntityService extends mongo_service_js_1.MongoService {
237
239
  async _update(id, input, options) {
238
240
  const isUpdateFilter = Array.isArray(input) || !!Object.keys(input).find(x => x.startsWith('$'));
239
241
  const isDocument = !Array.isArray(input) && !!Object.keys(input).find(x => !x.startsWith('$'));
240
- if (isUpdateFilter && isDocument)
242
+ if (isUpdateFilter && isDocument) {
241
243
  throw new TypeError('You must pass one of MongoDB UpdateFilter or a partial document, not both');
244
+ }
242
245
  let update;
243
246
  if (isDocument) {
244
- const encode = this.getEncoder('update');
245
- const doc = encode(input, { coerce: true });
247
+ const inputCodec = this.getInputCodec('update');
248
+ const doc = inputCodec(input);
246
249
  delete doc._id;
247
250
  update = mongo_adapter_js_1.MongoAdapter.preparePatch(doc);
248
251
  update.$set = update.$set || {};
@@ -254,11 +257,12 @@ class MongoEntityService extends mongo_service_js_1.MongoService {
254
257
  ...options,
255
258
  includeResultMetadata: false,
256
259
  upsert: undefined,
257
- projection: mongo_adapter_js_1.MongoAdapter.prepareProjection(this.getDataType(), options?.projection),
260
+ projection: mongo_adapter_js_1.MongoAdapter.prepareProjection(this.dataType, options?.projection),
258
261
  };
259
- const decode = this.getDecoder();
260
262
  const out = await this._dbFindOneAndUpdate(filter, update, mongoOptions);
261
- return out ? decode(out, { coerce: true }) : undefined;
263
+ const outputCodec = this.getOutputCodec('update');
264
+ if (out)
265
+ return outputCodec(out);
262
266
  }
263
267
  /**
264
268
  * Updates a document in the collection with the specified ID.
@@ -271,12 +275,13 @@ class MongoEntityService extends mongo_service_js_1.MongoService {
271
275
  async _updateOnly(id, input, options) {
272
276
  const isUpdateFilter = Array.isArray(input) || !!Object.keys(input).find(x => x.startsWith('$'));
273
277
  const isDocument = !Array.isArray(input) && !!Object.keys(input).find(x => !x.startsWith('$'));
274
- if (isUpdateFilter && isDocument)
278
+ if (isUpdateFilter && isDocument) {
275
279
  throw new TypeError('You must pass one of MongoDB UpdateFilter or a partial document, not both');
280
+ }
276
281
  let update;
277
282
  if (isDocument) {
278
- const encode = this.getEncoder('update');
279
- const doc = encode(input, { coerce: true });
283
+ const inputCodec = this.getInputCodec('update');
284
+ const doc = inputCodec(input);
280
285
  delete doc._id;
281
286
  update = mongo_adapter_js_1.MongoAdapter.preparePatch(doc);
282
287
  if (!Object.keys(doc).length)
@@ -289,7 +294,7 @@ class MongoEntityService extends mongo_service_js_1.MongoService {
289
294
  ...options,
290
295
  includeResultMetadata: false,
291
296
  upsert: undefined,
292
- projection: mongo_adapter_js_1.MongoAdapter.prepareProjection(this.getDataType(), options?.projection),
297
+ projection: mongo_adapter_js_1.MongoAdapter.prepareProjection(this.dataType, options?.projection),
293
298
  };
294
299
  const out = await this._dbUpdateOne(filter, update, mongoOptions);
295
300
  return out.matchedCount;
@@ -304,12 +309,13 @@ class MongoEntityService extends mongo_service_js_1.MongoService {
304
309
  async _updateMany(input, options) {
305
310
  const isUpdateFilter = Array.isArray(input) || !!Object.keys(input).find(x => x.startsWith('$'));
306
311
  const isDocument = !Array.isArray(input) && !!Object.keys(input).find(x => !x.startsWith('$'));
307
- if (isUpdateFilter && isDocument)
312
+ if (isUpdateFilter && isDocument) {
308
313
  throw new TypeError('You must pass one of MongoDB UpdateFilter or a partial document, not both');
314
+ }
309
315
  let update;
310
316
  if (isDocument) {
311
- const encode = this.getEncoder('update');
312
- const doc = encode(input, { coerce: true });
317
+ const inputCodec = this.getInputCodec('update');
318
+ const doc = inputCodec(input);
313
319
  delete doc._id;
314
320
  update = mongo_adapter_js_1.MongoAdapter.preparePatch(doc);
315
321
  if (!Object.keys(doc).length)
@@ -2,8 +2,8 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MongoNestedService = void 0;
4
4
  const tslib_1 = require("tslib");
5
- const lodash_omit_1 = tslib_1.__importDefault(require("lodash.omit"));
6
5
  const common_1 = require("@opra/common");
6
+ const lodash_omit_1 = tslib_1.__importDefault(require("lodash.omit"));
7
7
  const mongo_adapter_js_1 = require("./mongo-adapter.js");
8
8
  const mongo_service_js_1 = require("./mongo-service.js");
9
9
  /**
@@ -27,6 +27,18 @@ class MongoNestedService extends mongo_service_js_1.MongoService {
27
27
  this.defaultLimit = options?.defaultLimit || 10;
28
28
  this.$nestedFilter = options?.$nestedFilter;
29
29
  }
30
+ /**
31
+ * Retrieves the data type of the array field
32
+ *
33
+ * @returns {ComplexType} The complex data type of the field.
34
+ * @throws {NotAcceptableError} If the data type is not a ComplexType.
35
+ */
36
+ get dataType() {
37
+ const t = super.dataType.getField(this.fieldName).type;
38
+ if (!(t instanceof common_1.ComplexType))
39
+ throw new common_1.NotAcceptableError(`Data type "${t.name}" is not a ComplexType`);
40
+ return t;
41
+ }
30
42
  /**
31
43
  * Asserts whether a resource with the specified parentId and id exists.
32
44
  * Throws a ResourceNotFoundError if the resource does not exist.
@@ -38,8 +50,9 @@ class MongoNestedService extends mongo_service_js_1.MongoService {
38
50
  * @throws {ResourceNotAvailableError} - If the resource does not exist.
39
51
  */
40
52
  async assert(documentId, id, options) {
41
- if (!(await this.exists(documentId, id, options)))
53
+ if (!(await this.exists(documentId, id, options))) {
42
54
  throw new common_1.ResourceNotAvailableError(this.getResourceName() + '.' + this.nestedKey, documentId + '/' + id);
55
+ }
43
56
  }
44
57
  /**
45
58
  * Adds a single item into the array field.
@@ -66,8 +79,8 @@ class MongoNestedService extends mongo_service_js_1.MongoService {
66
79
  return this._intercept(() => this._create(documentId, input, options), info);
67
80
  }
68
81
  async _create(documentId, input, options) {
69
- const encode = this.getEncoder('create');
70
- const doc = encode(input);
82
+ const inputCodec = this.getInputCodec('create');
83
+ const doc = inputCodec(input);
71
84
  doc._id = doc._id || this._generateId();
72
85
  const docFilter = mongo_adapter_js_1.MongoAdapter.prepareKeyValues(documentId, ['_id']);
73
86
  const r = await this._dbUpdateOne(docFilter, {
@@ -340,14 +353,14 @@ class MongoNestedService extends mongo_service_js_1.MongoService {
340
353
  stages.push({ $sort: sort });
341
354
  }
342
355
  stages.push({ $limit: limit });
343
- const dataType = this.getDataType();
356
+ const dataType = this.dataType;
344
357
  const projection = mongo_adapter_js_1.MongoAdapter.prepareProjection(dataType, options?.projection);
345
358
  if (projection)
346
359
  stages.push({ $project: projection });
347
- const decode = this.getDecoder();
348
360
  const cursor = await this._dbAggregate(stages, mongoOptions);
349
361
  try {
350
- const out = await (await cursor.toArray()).map((r) => decode(r));
362
+ const outputCodec = this.getOutputCodec('find');
363
+ const out = await (await cursor.toArray()).map((r) => outputCodec(r));
351
364
  return out;
352
365
  }
353
366
  finally {
@@ -414,19 +427,19 @@ class MongoNestedService extends mongo_service_js_1.MongoService {
414
427
  dataStages.push({ $sort: sort });
415
428
  }
416
429
  dataStages.push({ $limit: limit });
417
- const dataType = this.getDataType();
430
+ const dataType = this.dataType;
418
431
  const projection = mongo_adapter_js_1.MongoAdapter.prepareProjection(dataType, options?.projection);
419
432
  if (projection)
420
433
  dataStages.push({ $project: projection });
421
- const decode = this.getDecoder();
422
434
  const cursor = await this._dbAggregate(stages, {
423
435
  ...mongoOptions,
424
436
  });
425
437
  try {
426
438
  const facetResult = await cursor.toArray();
439
+ const outputCodec = this.getOutputCodec('find');
427
440
  return {
428
441
  count: facetResult[0].count[0].totalMatches || 0,
429
- items: facetResult[0].data.map((r) => decode(r)),
442
+ items: facetResult[0].data.map((r) => outputCodec(r)),
430
443
  };
431
444
  }
432
445
  finally {
@@ -445,8 +458,9 @@ class MongoNestedService extends mongo_service_js_1.MongoService {
445
458
  */
446
459
  async get(documentId, nestedId, options) {
447
460
  const out = await this.findById(documentId, nestedId, options);
448
- if (!out)
461
+ if (!out) {
449
462
  throw new common_1.ResourceNotAvailableError(this.getResourceName() + '.' + this.nestedKey, documentId + '/' + nestedId);
463
+ }
450
464
  return out;
451
465
  }
452
466
  /**
@@ -525,8 +539,8 @@ class MongoNestedService extends mongo_service_js_1.MongoService {
525
539
  }, info);
526
540
  }
527
541
  async _updateMany(documentId, input, options) {
528
- const encode = this.getEncoder('update');
529
- const doc = encode(input, { coerce: true });
542
+ const inputCodec = this.getInputCodec('update');
543
+ const doc = inputCodec(input);
530
544
  if (!Object.keys(doc).length)
531
545
  return 0;
532
546
  const matchFilter = mongo_adapter_js_1.MongoAdapter.prepareFilter([
@@ -547,18 +561,6 @@ class MongoNestedService extends mongo_service_js_1.MongoService {
547
561
  return await this._count(documentId, options);
548
562
  return r.modifiedCount || 0;
549
563
  }
550
- /**
551
- * Retrieves the data type of the array field
552
- *
553
- * @returns {ComplexType} The complex data type of the field.
554
- * @throws {NotAcceptableError} If the data type is not a ComplexType.
555
- */
556
- getDataType() {
557
- const t = super.getDataType().getField(this.fieldName).type;
558
- if (!(t instanceof common_1.ComplexType))
559
- throw new common_1.NotAcceptableError(`Data type "${t.name}" is not a ComplexType`);
560
- return t;
561
- }
562
564
  /**
563
565
  * Retrieves the common filter used for querying array elements.
564
566
  * This method is mostly used for security issues like securing multi-tenant applications.
@@ -1,9 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MongoService = void 0;
4
- const mongodb_1 = require("mongodb");
5
4
  const common_1 = require("@opra/common");
6
5
  const core_1 = require("@opra/core");
6
+ const mongodb_1 = require("mongodb");
7
7
  /**
8
8
  * Class representing a MongoDB service for interacting with a collection.
9
9
  * @extends ServiceBase
@@ -19,8 +19,9 @@ class MongoService extends core_1.ServiceBase {
19
19
  */
20
20
  constructor(dataType, options) {
21
21
  super();
22
- this._encoders = {};
23
- this._dataType = dataType;
22
+ this._inputCodecs = {};
23
+ this._outputCodecs = {};
24
+ this._dataType_ = dataType;
24
25
  this.db = options?.db;
25
26
  this.$documentFilter = this.$documentFilter || options?.documentFilter;
26
27
  this.$interceptor = this.$interceptor || options?.interceptor;
@@ -55,7 +56,7 @@ class MongoService extends core_1.ServiceBase {
55
56
  *
56
57
  * @protected
57
58
  * @returns {string} The resource name.
58
- * @throws {Error} If the collection name is not defined.
59
+ * @throws {Error} If the resource name is not defined.
59
60
  */
60
61
  getResourceName() {
61
62
  const out = typeof this.$resourceName === 'function'
@@ -66,42 +67,44 @@ class MongoService extends core_1.ServiceBase {
66
67
  throw new Error('resourceName is not defined');
67
68
  }
68
69
  /**
69
- * Retrieves the data type of the document
70
+ * Retrieves the OPRA data type
70
71
  *
71
72
  * @throws {NotAcceptableError} If the data type is not a ComplexType.
72
73
  */
73
- getDataType() {
74
- return this.context.document.node.getComplexType(this._dataType);
74
+ get dataType() {
75
+ if (!this._dataType)
76
+ this._dataType = this.context.document.node.getComplexType(this._dataType_);
77
+ return this._dataType;
75
78
  }
76
79
  /**
77
- * Retrieves the encoder for the specified operation.
80
+ * Retrieves the codec for the specified operation.
78
81
  *
79
82
  * @param operation - The operation to retrieve the encoder for. Valid values are 'create' and 'update'.
80
83
  */
81
- getEncoder(operation) {
82
- let encoder = this._encoders[operation];
83
- if (encoder)
84
- return encoder;
84
+ getInputCodec(operation) {
85
+ let validator = this._inputCodecs[operation];
86
+ if (validator)
87
+ return validator;
85
88
  const options = { projection: '*' };
86
89
  if (operation === 'update')
87
- options.partial = true;
88
- const dataType = this.getDataType();
89
- encoder = dataType.generateCodec('encode', options);
90
- this._encoders[operation] = encoder;
91
- return encoder;
90
+ options.partial = 'deep';
91
+ const dataType = this.dataType;
92
+ validator = dataType.generateCodec('decode', options);
93
+ this._inputCodecs[operation] = validator;
94
+ return validator;
92
95
  }
93
96
  /**
94
- * Retrieves the decoder.
97
+ * Retrieves the codec.
95
98
  */
96
- getDecoder() {
97
- let decoder = this._decoder;
98
- if (decoder)
99
- return decoder;
100
- const options = { projection: '*', partial: true };
101
- const dataType = this.getDataType();
102
- decoder = dataType.generateCodec('decode', options);
103
- this._decoder = decoder;
104
- return decoder;
99
+ getOutputCodec(operation) {
100
+ let validator = this._outputCodecs[operation];
101
+ if (validator)
102
+ return validator;
103
+ const options = { projection: '*', partial: 'deep' };
104
+ const dataType = this.dataType;
105
+ validator = dataType.generateCodec('decode', options);
106
+ this._outputCodecs[operation] = validator;
107
+ return validator;
105
108
  }
106
109
  /**
107
110
  * Executes the provided function within a transaction.
@@ -114,7 +117,7 @@ class MongoService extends core_1.ServiceBase {
114
117
  if (session)
115
118
  return callback(session);
116
119
  // Backup old session property
117
- const hasOldSession = this.hasOwnProperty('session');
120
+ const hasOldSession = Object.prototype.hasOwnProperty.call(this, 'session');
118
121
  const oldSessionGetter = hasOldSession ? this.session : undefined;
119
122
  const db = this.getDatabase();
120
123
  const client = db.client;
@@ -1,8 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MongoSingletonService = void 0;
4
- const mongodb_1 = require("mongodb");
5
4
  const common_1 = require("@opra/common");
5
+ const mongodb_1 = require("mongodb");
6
6
  const mongo_adapter_js_1 = require("./mongo-adapter.js");
7
7
  const mongo_entity_service_js_1 = require("./mongo-entity-service.js");
8
8
  /**
@@ -164,6 +164,8 @@ function prepareFilterAst(ast, negative) {
164
164
  },
165
165
  }, !negative),
166
166
  };
167
+ default:
168
+ break;
167
169
  }
168
170
  throw new Error(`Unimplemented ComparisonExpression operation (right side is ${ast.right.kind})`);
169
171
  }
@@ -3,13 +3,14 @@ const defaultPrimaryKey = ['_id'];
3
3
  export default function prepareKeyValues(keyValue, primaryKey) {
4
4
  primaryKey = primaryKey || defaultPrimaryKey;
5
5
  const b = isPlainObject(keyValue);
6
- if (primaryKey.length > 1 && !b)
7
- new TypeError(`Argument "keyValue" must be an object that contains all key values`);
6
+ if (primaryKey.length > 1 && !b) {
7
+ throw new TypeError(`Argument "keyValue" must be an object that contains all key values`);
8
+ }
8
9
  if (primaryKey.length > 1 || b) {
9
10
  return primaryKey.reduce((o, k) => {
10
11
  o[k] = keyValue[k];
11
12
  if (o[k] == null)
12
- new Error(`Value of key "${k}" is required`);
13
+ throw new Error(`Value of key "${k}" is required`);
13
14
  return o;
14
15
  }, {});
15
16
  }
@@ -4,10 +4,11 @@ export default function preparePatch(doc, options) {
4
4
  trg.$set = trg.$set || {};
5
5
  return trg;
6
6
  }
7
- function _preparePatch(src, trg = {}, path, options) {
7
+ function _preparePatch(src, trg, path, options) {
8
8
  let f;
9
9
  let key;
10
10
  let field;
11
+ trg = trg || {};
11
12
  const fieldPrefix = options?.fieldPrefix;
12
13
  for (const [k, v] of Object.entries(src)) {
13
14
  f = k.startsWith('*') ? k.substring(1) : k;
package/esm/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export * from './mongo-adapter.js';
2
- export * from './mongo-nested-service.js';
3
2
  export * from './mongo-collection-service.js';
3
+ export * from './mongo-nested-service.js';
4
4
  export * from './mongo-service.js';
5
5
  export * from './mongo-singleton-service.js';
@@ -13,7 +13,7 @@ export var MongoAdapter;
13
13
  async function parseRequest(context) {
14
14
  const { operation } = context;
15
15
  if (operation.composition?.startsWith('Entity.') && operation.compositionOptions?.type) {
16
- const { compositionOptions } = operation;
16
+ const controller = operation.owner;
17
17
  switch (operation.composition) {
18
18
  case 'Entity.Create': {
19
19
  const data = await context.getBody();
@@ -23,7 +23,8 @@ export var MongoAdapter;
23
23
  return { method: 'create', data, options };
24
24
  }
25
25
  case 'Entity.Delete': {
26
- const key = context.pathParams[compositionOptions.keyParameter];
26
+ const keyParam = operation.parameters.find(p => p.keyParam) || controller.parameters.find(p => p.keyParam);
27
+ const key = keyParam && context.pathParams[String(keyParam.name)];
27
28
  const options = {
28
29
  filter: context.queryParams.filter,
29
30
  };
@@ -47,7 +48,8 @@ export var MongoAdapter;
47
48
  return { method: 'findMany', options };
48
49
  }
49
50
  case 'Entity.Get': {
50
- const key = context.pathParams[compositionOptions.keyParameter];
51
+ const keyParam = operation.parameters.find(p => p.keyParam) || controller.parameters.find(p => p.keyParam);
52
+ const key = keyParam && context.pathParams[String(keyParam.name)];
51
53
  const options = {
52
54
  projection: context.queryParams.projection,
53
55
  filter: context.queryParams.filter,
@@ -56,7 +58,8 @@ export var MongoAdapter;
56
58
  }
57
59
  case 'Entity.Update': {
58
60
  const data = await context.getBody();
59
- const key = context.pathParams[compositionOptions.keyParameter];
61
+ const keyParam = operation.parameters.find(p => p.keyParam) || controller.parameters.find(p => p.keyParam);
62
+ const key = keyParam && context.pathParams[String(keyParam.name)];
60
63
  const options = {
61
64
  projection: context.queryParams.projection,
62
65
  filter: context.queryParams.filter,
@@ -70,6 +73,8 @@ export var MongoAdapter;
70
73
  };
71
74
  return { method: 'updateMany', data, options };
72
75
  }
76
+ default:
77
+ break;
73
78
  }
74
79
  }
75
80
  throw new Error(`This operation is not compatible to MongoDB adapter`);
@@ -1,5 +1,5 @@
1
- import { ObjectId } from 'mongodb';
2
1
  import { ResourceNotAvailableError } from '@opra/common';
2
+ import { ObjectId } from 'mongodb';
3
3
  import { MongoAdapter } from './mongo-adapter.js';
4
4
  import { MongoEntityService } from './mongo-entity-service.js';
5
5
  /**
@@ -1,6 +1,6 @@
1
- import omit from 'lodash.omit';
2
1
  import * as assert from 'node:assert';
3
2
  import { InternalServerError } from '@opra/common';
3
+ import omit from 'lodash.omit';
4
4
  import { MongoAdapter } from './mongo-adapter.js';
5
5
  import { MongoService } from './mongo-service.js';
6
6
  /**
@@ -26,8 +26,8 @@ export class MongoEntityService extends MongoService {
26
26
  * @protected
27
27
  */
28
28
  async _create(input, options) {
29
- const encode = this.getEncoder('create');
30
- const doc = encode(input);
29
+ const inputCodec = this.getInputCodec('create');
30
+ const doc = inputCodec(input);
31
31
  assert.ok(doc._id, 'You must provide the "_id" field');
32
32
  const r = await this._dbInsertOne(doc, options);
33
33
  if (r.insertedId) {
@@ -95,14 +95,15 @@ export class MongoEntityService extends MongoService {
95
95
  const filter = MongoAdapter.prepareFilter([MongoAdapter.prepareKeyValues(id, ['_id']), options?.filter]);
96
96
  const mongoOptions = {
97
97
  ...options,
98
- projection: MongoAdapter.prepareProjection(this.getDataType(), options?.projection),
98
+ projection: MongoAdapter.prepareProjection(this.dataType, options?.projection),
99
99
  limit: undefined,
100
100
  skip: undefined,
101
101
  sort: undefined,
102
102
  };
103
- const decode = this.getDecoder();
104
103
  const out = await this._dbFindOne(filter, mongoOptions);
105
- return out ? decode(out) : undefined;
104
+ const outputCodec = this.getOutputCodec('find');
105
+ if (out)
106
+ return outputCodec(out);
106
107
  }
107
108
  /**
108
109
  * Finds a document in the collection that matches the specified options.
@@ -115,12 +116,13 @@ export class MongoEntityService extends MongoService {
115
116
  const mongoOptions = {
116
117
  ...omit(options, 'filter'),
117
118
  sort: options?.sort ? MongoAdapter.prepareSort(options.sort) : undefined,
118
- projection: MongoAdapter.prepareProjection(this.getDataType(), options?.projection),
119
+ projection: MongoAdapter.prepareProjection(this.dataType, options?.projection),
119
120
  limit: undefined,
120
121
  };
121
- const decode = this.getDecoder();
122
122
  const out = await this._dbFindOne(filter, mongoOptions);
123
- return out ? decode(out, { coerce: true }) : undefined;
123
+ const outputCodec = this.getOutputCodec('find');
124
+ if (out)
125
+ return outputCodec(out);
124
126
  }
125
127
  /**
126
128
  * Finds multiple documents in the MongoDB collection.
@@ -147,16 +149,16 @@ export class MongoEntityService extends MongoService {
147
149
  stages.push({ $sort: sort });
148
150
  }
149
151
  stages.push({ $limit: limit });
150
- const dataType = this.getDataType();
152
+ const dataType = this.dataType;
151
153
  const projection = MongoAdapter.prepareProjection(dataType, options?.projection);
152
154
  if (projection)
153
155
  stages.push({ $project: projection });
154
- const decode = this.getDecoder();
155
156
  const cursor = await this._dbAggregate(stages, mongoOptions);
156
157
  /** Execute db command */
157
158
  try {
158
159
  /** Fetch the cursor and decode the result objects */
159
- return (await cursor.toArray()).map((r) => decode(r));
160
+ const outputCodec = this.getOutputCodec('find');
161
+ return (await cursor.toArray()).map((r) => outputCodec(r));
160
162
  }
161
163
  finally {
162
164
  if (!cursor.closed)
@@ -201,11 +203,11 @@ export class MongoEntityService extends MongoService {
201
203
  dataStages.push({ $sort: sort });
202
204
  }
203
205
  dataStages.push({ $limit: limit });
204
- const dataType = this.getDataType();
206
+ const dataType = this.dataType;
205
207
  const projection = MongoAdapter.prepareProjection(dataType, options?.projection);
206
208
  if (projection)
207
209
  dataStages.push({ $project: projection });
208
- const decode = this.getDecoder();
210
+ const outputCodec = this.getOutputCodec('find');
209
211
  /** Execute db command */
210
212
  const cursor = await this._dbAggregate(stages, mongoOptions);
211
213
  try {
@@ -213,7 +215,7 @@ export class MongoEntityService extends MongoService {
213
215
  const facetResult = await cursor.toArray();
214
216
  return {
215
217
  count: facetResult[0].count[0]?.totalMatches || 0,
216
- items: facetResult[0].data?.map((r) => decode(r)),
218
+ items: facetResult[0].data?.map((r) => outputCodec(r)),
217
219
  };
218
220
  }
219
221
  finally {
@@ -233,12 +235,13 @@ export class MongoEntityService extends MongoService {
233
235
  async _update(id, input, options) {
234
236
  const isUpdateFilter = Array.isArray(input) || !!Object.keys(input).find(x => x.startsWith('$'));
235
237
  const isDocument = !Array.isArray(input) && !!Object.keys(input).find(x => !x.startsWith('$'));
236
- if (isUpdateFilter && isDocument)
238
+ if (isUpdateFilter && isDocument) {
237
239
  throw new TypeError('You must pass one of MongoDB UpdateFilter or a partial document, not both');
240
+ }
238
241
  let update;
239
242
  if (isDocument) {
240
- const encode = this.getEncoder('update');
241
- const doc = encode(input, { coerce: true });
243
+ const inputCodec = this.getInputCodec('update');
244
+ const doc = inputCodec(input);
242
245
  delete doc._id;
243
246
  update = MongoAdapter.preparePatch(doc);
244
247
  update.$set = update.$set || {};
@@ -250,11 +253,12 @@ export class MongoEntityService extends MongoService {
250
253
  ...options,
251
254
  includeResultMetadata: false,
252
255
  upsert: undefined,
253
- projection: MongoAdapter.prepareProjection(this.getDataType(), options?.projection),
256
+ projection: MongoAdapter.prepareProjection(this.dataType, options?.projection),
254
257
  };
255
- const decode = this.getDecoder();
256
258
  const out = await this._dbFindOneAndUpdate(filter, update, mongoOptions);
257
- return out ? decode(out, { coerce: true }) : undefined;
259
+ const outputCodec = this.getOutputCodec('update');
260
+ if (out)
261
+ return outputCodec(out);
258
262
  }
259
263
  /**
260
264
  * Updates a document in the collection with the specified ID.
@@ -267,12 +271,13 @@ export class MongoEntityService extends MongoService {
267
271
  async _updateOnly(id, input, options) {
268
272
  const isUpdateFilter = Array.isArray(input) || !!Object.keys(input).find(x => x.startsWith('$'));
269
273
  const isDocument = !Array.isArray(input) && !!Object.keys(input).find(x => !x.startsWith('$'));
270
- if (isUpdateFilter && isDocument)
274
+ if (isUpdateFilter && isDocument) {
271
275
  throw new TypeError('You must pass one of MongoDB UpdateFilter or a partial document, not both');
276
+ }
272
277
  let update;
273
278
  if (isDocument) {
274
- const encode = this.getEncoder('update');
275
- const doc = encode(input, { coerce: true });
279
+ const inputCodec = this.getInputCodec('update');
280
+ const doc = inputCodec(input);
276
281
  delete doc._id;
277
282
  update = MongoAdapter.preparePatch(doc);
278
283
  if (!Object.keys(doc).length)
@@ -285,7 +290,7 @@ export class MongoEntityService extends MongoService {
285
290
  ...options,
286
291
  includeResultMetadata: false,
287
292
  upsert: undefined,
288
- projection: MongoAdapter.prepareProjection(this.getDataType(), options?.projection),
293
+ projection: MongoAdapter.prepareProjection(this.dataType, options?.projection),
289
294
  };
290
295
  const out = await this._dbUpdateOne(filter, update, mongoOptions);
291
296
  return out.matchedCount;
@@ -300,12 +305,13 @@ export class MongoEntityService extends MongoService {
300
305
  async _updateMany(input, options) {
301
306
  const isUpdateFilter = Array.isArray(input) || !!Object.keys(input).find(x => x.startsWith('$'));
302
307
  const isDocument = !Array.isArray(input) && !!Object.keys(input).find(x => !x.startsWith('$'));
303
- if (isUpdateFilter && isDocument)
308
+ if (isUpdateFilter && isDocument) {
304
309
  throw new TypeError('You must pass one of MongoDB UpdateFilter or a partial document, not both');
310
+ }
305
311
  let update;
306
312
  if (isDocument) {
307
- const encode = this.getEncoder('update');
308
- const doc = encode(input, { coerce: true });
313
+ const inputCodec = this.getInputCodec('update');
314
+ const doc = inputCodec(input);
309
315
  delete doc._id;
310
316
  update = MongoAdapter.preparePatch(doc);
311
317
  if (!Object.keys(doc).length)
@@ -1,5 +1,5 @@
1
- import omit from 'lodash.omit';
2
1
  import { ComplexType, NotAcceptableError, ResourceNotAvailableError } from '@opra/common';
2
+ import omit from 'lodash.omit';
3
3
  import { MongoAdapter } from './mongo-adapter.js';
4
4
  import { MongoService } from './mongo-service.js';
5
5
  /**
@@ -23,6 +23,18 @@ export class MongoNestedService extends MongoService {
23
23
  this.defaultLimit = options?.defaultLimit || 10;
24
24
  this.$nestedFilter = options?.$nestedFilter;
25
25
  }
26
+ /**
27
+ * Retrieves the data type of the array field
28
+ *
29
+ * @returns {ComplexType} The complex data type of the field.
30
+ * @throws {NotAcceptableError} If the data type is not a ComplexType.
31
+ */
32
+ get dataType() {
33
+ const t = super.dataType.getField(this.fieldName).type;
34
+ if (!(t instanceof ComplexType))
35
+ throw new NotAcceptableError(`Data type "${t.name}" is not a ComplexType`);
36
+ return t;
37
+ }
26
38
  /**
27
39
  * Asserts whether a resource with the specified parentId and id exists.
28
40
  * Throws a ResourceNotFoundError if the resource does not exist.
@@ -34,8 +46,9 @@ export class MongoNestedService extends MongoService {
34
46
  * @throws {ResourceNotAvailableError} - If the resource does not exist.
35
47
  */
36
48
  async assert(documentId, id, options) {
37
- if (!(await this.exists(documentId, id, options)))
49
+ if (!(await this.exists(documentId, id, options))) {
38
50
  throw new ResourceNotAvailableError(this.getResourceName() + '.' + this.nestedKey, documentId + '/' + id);
51
+ }
39
52
  }
40
53
  /**
41
54
  * Adds a single item into the array field.
@@ -62,8 +75,8 @@ export class MongoNestedService extends MongoService {
62
75
  return this._intercept(() => this._create(documentId, input, options), info);
63
76
  }
64
77
  async _create(documentId, input, options) {
65
- const encode = this.getEncoder('create');
66
- const doc = encode(input);
78
+ const inputCodec = this.getInputCodec('create');
79
+ const doc = inputCodec(input);
67
80
  doc._id = doc._id || this._generateId();
68
81
  const docFilter = MongoAdapter.prepareKeyValues(documentId, ['_id']);
69
82
  const r = await this._dbUpdateOne(docFilter, {
@@ -336,14 +349,14 @@ export class MongoNestedService extends MongoService {
336
349
  stages.push({ $sort: sort });
337
350
  }
338
351
  stages.push({ $limit: limit });
339
- const dataType = this.getDataType();
352
+ const dataType = this.dataType;
340
353
  const projection = MongoAdapter.prepareProjection(dataType, options?.projection);
341
354
  if (projection)
342
355
  stages.push({ $project: projection });
343
- const decode = this.getDecoder();
344
356
  const cursor = await this._dbAggregate(stages, mongoOptions);
345
357
  try {
346
- const out = await (await cursor.toArray()).map((r) => decode(r));
358
+ const outputCodec = this.getOutputCodec('find');
359
+ const out = await (await cursor.toArray()).map((r) => outputCodec(r));
347
360
  return out;
348
361
  }
349
362
  finally {
@@ -410,19 +423,19 @@ export class MongoNestedService extends MongoService {
410
423
  dataStages.push({ $sort: sort });
411
424
  }
412
425
  dataStages.push({ $limit: limit });
413
- const dataType = this.getDataType();
426
+ const dataType = this.dataType;
414
427
  const projection = MongoAdapter.prepareProjection(dataType, options?.projection);
415
428
  if (projection)
416
429
  dataStages.push({ $project: projection });
417
- const decode = this.getDecoder();
418
430
  const cursor = await this._dbAggregate(stages, {
419
431
  ...mongoOptions,
420
432
  });
421
433
  try {
422
434
  const facetResult = await cursor.toArray();
435
+ const outputCodec = this.getOutputCodec('find');
423
436
  return {
424
437
  count: facetResult[0].count[0].totalMatches || 0,
425
- items: facetResult[0].data.map((r) => decode(r)),
438
+ items: facetResult[0].data.map((r) => outputCodec(r)),
426
439
  };
427
440
  }
428
441
  finally {
@@ -441,8 +454,9 @@ export class MongoNestedService extends MongoService {
441
454
  */
442
455
  async get(documentId, nestedId, options) {
443
456
  const out = await this.findById(documentId, nestedId, options);
444
- if (!out)
457
+ if (!out) {
445
458
  throw new ResourceNotAvailableError(this.getResourceName() + '.' + this.nestedKey, documentId + '/' + nestedId);
459
+ }
446
460
  return out;
447
461
  }
448
462
  /**
@@ -521,8 +535,8 @@ export class MongoNestedService extends MongoService {
521
535
  }, info);
522
536
  }
523
537
  async _updateMany(documentId, input, options) {
524
- const encode = this.getEncoder('update');
525
- const doc = encode(input, { coerce: true });
538
+ const inputCodec = this.getInputCodec('update');
539
+ const doc = inputCodec(input);
526
540
  if (!Object.keys(doc).length)
527
541
  return 0;
528
542
  const matchFilter = MongoAdapter.prepareFilter([
@@ -543,18 +557,6 @@ export class MongoNestedService extends MongoService {
543
557
  return await this._count(documentId, options);
544
558
  return r.modifiedCount || 0;
545
559
  }
546
- /**
547
- * Retrieves the data type of the array field
548
- *
549
- * @returns {ComplexType} The complex data type of the field.
550
- * @throws {NotAcceptableError} If the data type is not a ComplexType.
551
- */
552
- getDataType() {
553
- const t = super.getDataType().getField(this.fieldName).type;
554
- if (!(t instanceof ComplexType))
555
- throw new NotAcceptableError(`Data type "${t.name}" is not a ComplexType`);
556
- return t;
557
- }
558
560
  /**
559
561
  * Retrieves the common filter used for querying array elements.
560
562
  * This method is mostly used for security issues like securing multi-tenant applications.
@@ -1,6 +1,6 @@
1
- import { ObjectId } from 'mongodb';
2
1
  import { DATATYPE_METADATA } from '@opra/common';
3
2
  import { ServiceBase } from '@opra/core';
3
+ import { ObjectId } from 'mongodb';
4
4
  /**
5
5
  * Class representing a MongoDB service for interacting with a collection.
6
6
  * @extends ServiceBase
@@ -16,8 +16,9 @@ export class MongoService extends ServiceBase {
16
16
  */
17
17
  constructor(dataType, options) {
18
18
  super();
19
- this._encoders = {};
20
- this._dataType = dataType;
19
+ this._inputCodecs = {};
20
+ this._outputCodecs = {};
21
+ this._dataType_ = dataType;
21
22
  this.db = options?.db;
22
23
  this.$documentFilter = this.$documentFilter || options?.documentFilter;
23
24
  this.$interceptor = this.$interceptor || options?.interceptor;
@@ -52,7 +53,7 @@ export class MongoService extends ServiceBase {
52
53
  *
53
54
  * @protected
54
55
  * @returns {string} The resource name.
55
- * @throws {Error} If the collection name is not defined.
56
+ * @throws {Error} If the resource name is not defined.
56
57
  */
57
58
  getResourceName() {
58
59
  const out = typeof this.$resourceName === 'function'
@@ -63,42 +64,44 @@ export class MongoService extends ServiceBase {
63
64
  throw new Error('resourceName is not defined');
64
65
  }
65
66
  /**
66
- * Retrieves the data type of the document
67
+ * Retrieves the OPRA data type
67
68
  *
68
69
  * @throws {NotAcceptableError} If the data type is not a ComplexType.
69
70
  */
70
- getDataType() {
71
- return this.context.document.node.getComplexType(this._dataType);
71
+ get dataType() {
72
+ if (!this._dataType)
73
+ this._dataType = this.context.document.node.getComplexType(this._dataType_);
74
+ return this._dataType;
72
75
  }
73
76
  /**
74
- * Retrieves the encoder for the specified operation.
77
+ * Retrieves the codec for the specified operation.
75
78
  *
76
79
  * @param operation - The operation to retrieve the encoder for. Valid values are 'create' and 'update'.
77
80
  */
78
- getEncoder(operation) {
79
- let encoder = this._encoders[operation];
80
- if (encoder)
81
- return encoder;
81
+ getInputCodec(operation) {
82
+ let validator = this._inputCodecs[operation];
83
+ if (validator)
84
+ return validator;
82
85
  const options = { projection: '*' };
83
86
  if (operation === 'update')
84
- options.partial = true;
85
- const dataType = this.getDataType();
86
- encoder = dataType.generateCodec('encode', options);
87
- this._encoders[operation] = encoder;
88
- return encoder;
87
+ options.partial = 'deep';
88
+ const dataType = this.dataType;
89
+ validator = dataType.generateCodec('decode', options);
90
+ this._inputCodecs[operation] = validator;
91
+ return validator;
89
92
  }
90
93
  /**
91
- * Retrieves the decoder.
94
+ * Retrieves the codec.
92
95
  */
93
- getDecoder() {
94
- let decoder = this._decoder;
95
- if (decoder)
96
- return decoder;
97
- const options = { projection: '*', partial: true };
98
- const dataType = this.getDataType();
99
- decoder = dataType.generateCodec('decode', options);
100
- this._decoder = decoder;
101
- 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;
102
105
  }
103
106
  /**
104
107
  * Executes the provided function within a transaction.
@@ -111,7 +114,7 @@ export class MongoService extends ServiceBase {
111
114
  if (session)
112
115
  return callback(session);
113
116
  // Backup old session property
114
- const hasOldSession = this.hasOwnProperty('session');
117
+ const hasOldSession = Object.prototype.hasOwnProperty.call(this, 'session');
115
118
  const oldSessionGetter = hasOldSession ? this.session : undefined;
116
119
  const db = this.getDatabase();
117
120
  const client = db.client;
@@ -1,5 +1,5 @@
1
- import { ObjectId } from 'mongodb';
2
1
  import { ResourceNotAvailableError } from '@opra/common';
2
+ import { ObjectId } from 'mongodb';
3
3
  import { MongoAdapter } from './mongo-adapter.js';
4
4
  import { MongoEntityService } from './mongo-entity-service.js';
5
5
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opra/mongodb",
3
- "version": "1.0.0-alpha.1",
3
+ "version": "1.0.0-alpha.10",
4
4
  "description": "Opra MongoDB adapter package",
5
5
  "author": "Panates",
6
6
  "license": "MIT",
@@ -17,22 +17,27 @@
17
17
  "build:esm": "tsc -b tsconfig-build-esm.json",
18
18
  "postbuild": "cp README.md package.json ../../LICENSE ../../build/mongodb && cp ../../package.cjs.json ../../build/mongodb/cjs/package.json",
19
19
  "lint": "eslint . --max-warnings=0",
20
+ "lint:fix": "eslint . --max-warnings=0 --fix",
20
21
  "format": "prettier . --write --log-level=warn",
21
- "test": "jest",
22
- "cover": "jest --collect-coverage",
22
+ "test": "jest --passWithNoTests",
23
+ "cover": "jest --passWithNoTests --collect-coverage",
23
24
  "clean": "npm run clean:src && npm run clean:dist && npm run clean:cover",
24
25
  "clean:src": "ts-cleanup -s src --all && ts-cleanup -s test --all",
25
26
  "clean:dist": "rimraf ../../build/mongodb",
26
27
  "clean:cover": "rimraf ../../coverage/mongodb"
27
28
  },
29
+ "dependencies": {
30
+ "lodash.omit": "^4.5.0",
31
+ "putil-isplainobject": "^1.1.5"
32
+ },
28
33
  "devDependencies": {
29
34
  "@faker-js/faker": "^8.4.1",
30
- "mongodb": "^6.7.0",
35
+ "mongodb": "^6.8.0",
31
36
  "ts-gems": "^3.4.0"
32
37
  },
33
38
  "peerDependencies": {
34
- "@opra/common": "^0.33.13",
35
- "@opra/core": "^0.33.13",
39
+ "@opra/common": "^1.0.0-alpha.10",
40
+ "@opra/core": "^1.0.0-alpha.10",
36
41
  "mongodb": ">= 6.0.0"
37
42
  },
38
43
  "type": "module",
@@ -1,4 +1,4 @@
1
- import mongodb, { Document } from 'mongodb';
2
1
  import { ComplexType, FieldsProjection } from '@opra/common';
2
+ import mongodb, { Document } from 'mongodb';
3
3
  export default function prepareProjection(dataType: ComplexType, projection?: string | string[] | Document): mongodb.Document | undefined;
4
4
  export declare function prepare(dataType: ComplexType, target: mongodb.Document, projection?: FieldsProjection): void;
package/types/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export * from './mongo-adapter.js';
2
- export * from './mongo-nested-service.js';
3
2
  export * from './mongo-collection-service.js';
3
+ export * from './mongo-nested-service.js';
4
4
  export * from './mongo-service.js';
5
5
  export * from './mongo-singleton-service.js';
@@ -1,6 +1,6 @@
1
- import mongodb, { ClientSession, ObjectId } from 'mongodb';
2
1
  import { OpraFilter } from '@opra/common';
3
2
  import { HttpContext } from '@opra/core';
3
+ import mongodb, { ClientSession, ObjectId } from 'mongodb';
4
4
  import _prepareFilter from './adapter-utils/prepare-filter.js';
5
5
  import _prepareKeyValues from './adapter-utils/prepare-key-values.js';
6
6
  import _preparePatch from './adapter-utils/prepare-patch.js';
@@ -1,6 +1,6 @@
1
+ import { ComplexType } from '@opra/common';
1
2
  import mongodb from 'mongodb';
2
3
  import { PartialDTO, PatchDTO, Type } from 'ts-gems';
3
- import { ComplexType } from '@opra/common';
4
4
  import { MongoAdapter } from './mongo-adapter.js';
5
5
  import { MongoCollectionService } from './mongo-collection-service.js';
6
6
  import { MongoService } from './mongo-service.js';
@@ -87,6 +87,13 @@ export declare class MongoNestedService<T extends mongodb.Document> extends Mong
87
87
  * @constructor
88
88
  */
89
89
  constructor(dataType: Type | string, fieldName: string, options?: MongoNestedService.Options);
90
+ /**
91
+ * Retrieves the data type of the array field
92
+ *
93
+ * @returns {ComplexType} The complex data type of the field.
94
+ * @throws {NotAcceptableError} If the data type is not a ComplexType.
95
+ */
96
+ get dataType(): ComplexType;
90
97
  /**
91
98
  * Asserts whether a resource with the specified parentId and id exists.
92
99
  * Throws a ResourceNotFoundError if the resource does not exist.
@@ -239,13 +246,6 @@ export declare class MongoNestedService<T extends mongodb.Document> extends Mong
239
246
  */
240
247
  updateMany(documentId: MongoAdapter.AnyId, input: PatchDTO<T>, options?: MongoNestedService.UpdateManyOptions<T>): Promise<number>;
241
248
  protected _updateMany(documentId: MongoAdapter.AnyId, input: PatchDTO<T>, options?: MongoNestedService.UpdateManyOptions<T>): Promise<number>;
242
- /**
243
- * Retrieves the data type of the array field
244
- *
245
- * @returns {ComplexType} The complex data type of the field.
246
- * @throws {NotAcceptableError} If the data type is not a ComplexType.
247
- */
248
- getDataType(): ComplexType;
249
249
  /**
250
250
  * Retrieves the common filter used for querying array elements.
251
251
  * This method is mostly used for security issues like securing multi-tenant applications.
@@ -1,9 +1,9 @@
1
- import mongodb, { Document, TransactionOptions } from 'mongodb';
2
- import { PartialDTO, StrictOmit, Type } from 'ts-gems';
3
- import { IsObject } from 'valgen';
4
1
  import * as OpraCommon from '@opra/common';
5
2
  import { ComplexType } from '@opra/common';
6
3
  import { ServiceBase } from '@opra/core';
4
+ import mongodb, { Document, TransactionOptions } from 'mongodb';
5
+ import { PartialDTO, StrictOmit, Type } from 'ts-gems';
6
+ import { IsObject } from 'valgen';
7
7
  import { MongoAdapter } from './mongo-adapter.js';
8
8
  /**
9
9
  * The namespace for the MongoService.
@@ -37,7 +37,7 @@ export declare namespace MongoService {
37
37
  * @interface
38
38
  */
39
39
  interface CreateOptions extends mongodb.InsertOneOptions {
40
- fields?: string[];
40
+ projection?: string[];
41
41
  }
42
42
  /**
43
43
  * Represents options for "count" operation
@@ -138,9 +138,10 @@ export declare namespace MongoService {
138
138
  * @template T - The type of the documents in the collection.
139
139
  */
140
140
  export declare class MongoService<T extends mongodb.Document = mongodb.Document> extends ServiceBase {
141
- protected _encoders: Record<string, IsObject.Validator<T>>;
142
- protected _decoder?: IsObject.Validator<T>;
143
- protected _dataType: Type | string;
141
+ protected _dataType_: Type | string;
142
+ protected _dataType: ComplexType;
143
+ protected _inputCodecs: Record<string, IsObject.Validator<T>>;
144
+ protected _outputCodecs: Record<string, IsObject.Validator<T>>;
144
145
  /**
145
146
  * Represents the name of a collection in MongoDB
146
147
  */
@@ -171,7 +172,7 @@ export declare class MongoService<T extends mongodb.Document = mongodb.Document>
171
172
  */
172
173
  $onError?: (error: unknown, _this: any) => void | Promise<void>;
173
174
  /**
174
- * Represents a common filter function for a MongoEntityService.
175
+ * Represents a common filter function for a MongoService.
175
176
  *
176
177
  * @type {FilterInput | Function}
177
178
  */
@@ -206,25 +207,25 @@ export declare class MongoService<T extends mongodb.Document = mongodb.Document>
206
207
  *
207
208
  * @protected
208
209
  * @returns {string} The resource name.
209
- * @throws {Error} If the collection name is not defined.
210
+ * @throws {Error} If the resource name is not defined.
210
211
  */
211
212
  getResourceName(): string;
212
213
  /**
213
- * Retrieves the data type of the document
214
+ * Retrieves the OPRA data type
214
215
  *
215
216
  * @throws {NotAcceptableError} If the data type is not a ComplexType.
216
217
  */
217
- getDataType(): ComplexType;
218
+ get dataType(): ComplexType;
218
219
  /**
219
- * Retrieves the encoder for the specified operation.
220
+ * Retrieves the codec for the specified operation.
220
221
  *
221
222
  * @param operation - The operation to retrieve the encoder for. Valid values are 'create' and 'update'.
222
223
  */
223
- getEncoder(operation: 'create' | 'update'): IsObject.Validator<T>;
224
+ getInputCodec(operation: string): IsObject.Validator<T>;
224
225
  /**
225
- * Retrieves the decoder.
226
+ * Retrieves the codec.
226
227
  */
227
- getDecoder(): IsObject.Validator<T>;
228
+ getOutputCodec(operation: string): IsObject.Validator<T>;
228
229
  /**
229
230
  * Executes the provided function within a transaction.
230
231
  *