@opra/mongodb 1.0.0-alpha.3 → 1.0.0-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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 {
@@ -241,8 +243,8 @@ class MongoEntityService extends mongo_service_js_1.MongoService {
241
243
  throw new TypeError('You must pass one of MongoDB UpdateFilter or a partial document, not both');
242
244
  let update;
243
245
  if (isDocument) {
244
- const encode = this.getEncoder('update');
245
- const doc = encode(input, { coerce: true });
246
+ const inputCodec = this.getInputCodec('update');
247
+ const doc = inputCodec(input);
246
248
  delete doc._id;
247
249
  update = mongo_adapter_js_1.MongoAdapter.preparePatch(doc);
248
250
  update.$set = update.$set || {};
@@ -254,11 +256,12 @@ class MongoEntityService extends mongo_service_js_1.MongoService {
254
256
  ...options,
255
257
  includeResultMetadata: false,
256
258
  upsert: undefined,
257
- projection: mongo_adapter_js_1.MongoAdapter.prepareProjection(this.getDataType(), options?.projection),
259
+ projection: mongo_adapter_js_1.MongoAdapter.prepareProjection(this.dataType, options?.projection),
258
260
  };
259
- const decode = this.getDecoder();
260
261
  const out = await this._dbFindOneAndUpdate(filter, update, mongoOptions);
261
- return out ? decode(out, { coerce: true }) : undefined;
262
+ const outputCodec = this.getOutputCodec('update');
263
+ if (out)
264
+ return outputCodec(out);
262
265
  }
263
266
  /**
264
267
  * Updates a document in the collection with the specified ID.
@@ -275,8 +278,8 @@ class MongoEntityService extends mongo_service_js_1.MongoService {
275
278
  throw new TypeError('You must pass one of MongoDB UpdateFilter or a partial document, not both');
276
279
  let update;
277
280
  if (isDocument) {
278
- const encode = this.getEncoder('update');
279
- const doc = encode(input, { coerce: true });
281
+ const inputCodec = this.getInputCodec('update');
282
+ const doc = inputCodec(input);
280
283
  delete doc._id;
281
284
  update = mongo_adapter_js_1.MongoAdapter.preparePatch(doc);
282
285
  if (!Object.keys(doc).length)
@@ -289,7 +292,7 @@ class MongoEntityService extends mongo_service_js_1.MongoService {
289
292
  ...options,
290
293
  includeResultMetadata: false,
291
294
  upsert: undefined,
292
- projection: mongo_adapter_js_1.MongoAdapter.prepareProjection(this.getDataType(), options?.projection),
295
+ projection: mongo_adapter_js_1.MongoAdapter.prepareProjection(this.dataType, options?.projection),
293
296
  };
294
297
  const out = await this._dbUpdateOne(filter, update, mongoOptions);
295
298
  return out.matchedCount;
@@ -308,8 +311,8 @@ class MongoEntityService extends mongo_service_js_1.MongoService {
308
311
  throw new TypeError('You must pass one of MongoDB UpdateFilter or a partial document, not both');
309
312
  let update;
310
313
  if (isDocument) {
311
- const encode = this.getEncoder('update');
312
- const doc = encode(input, { coerce: true });
314
+ const inputCodec = this.getInputCodec('update');
315
+ const doc = inputCodec(input);
313
316
  delete doc._id;
314
317
  update = mongo_adapter_js_1.MongoAdapter.preparePatch(doc);
315
318
  if (!Object.keys(doc).length)
@@ -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.
@@ -66,8 +78,8 @@ class MongoNestedService extends mongo_service_js_1.MongoService {
66
78
  return this._intercept(() => this._create(documentId, input, options), info);
67
79
  }
68
80
  async _create(documentId, input, options) {
69
- const encode = this.getEncoder('create');
70
- const doc = encode(input);
81
+ const inputCodec = this.getInputCodec('create');
82
+ const doc = inputCodec(input);
71
83
  doc._id = doc._id || this._generateId();
72
84
  const docFilter = mongo_adapter_js_1.MongoAdapter.prepareKeyValues(documentId, ['_id']);
73
85
  const r = await this._dbUpdateOne(docFilter, {
@@ -340,14 +352,14 @@ class MongoNestedService extends mongo_service_js_1.MongoService {
340
352
  stages.push({ $sort: sort });
341
353
  }
342
354
  stages.push({ $limit: limit });
343
- const dataType = this.getDataType();
355
+ const dataType = this.dataType;
344
356
  const projection = mongo_adapter_js_1.MongoAdapter.prepareProjection(dataType, options?.projection);
345
357
  if (projection)
346
358
  stages.push({ $project: projection });
347
- const decode = this.getDecoder();
348
359
  const cursor = await this._dbAggregate(stages, mongoOptions);
349
360
  try {
350
- const out = await (await cursor.toArray()).map((r) => decode(r));
361
+ const outputCodec = this.getOutputCodec('find');
362
+ const out = await (await cursor.toArray()).map((r) => outputCodec(r));
351
363
  return out;
352
364
  }
353
365
  finally {
@@ -414,19 +426,19 @@ class MongoNestedService extends mongo_service_js_1.MongoService {
414
426
  dataStages.push({ $sort: sort });
415
427
  }
416
428
  dataStages.push({ $limit: limit });
417
- const dataType = this.getDataType();
429
+ const dataType = this.dataType;
418
430
  const projection = mongo_adapter_js_1.MongoAdapter.prepareProjection(dataType, options?.projection);
419
431
  if (projection)
420
432
  dataStages.push({ $project: projection });
421
- const decode = this.getDecoder();
422
433
  const cursor = await this._dbAggregate(stages, {
423
434
  ...mongoOptions,
424
435
  });
425
436
  try {
426
437
  const facetResult = await cursor.toArray();
438
+ const outputCodec = this.getOutputCodec('find');
427
439
  return {
428
440
  count: facetResult[0].count[0].totalMatches || 0,
429
- items: facetResult[0].data.map((r) => decode(r)),
441
+ items: facetResult[0].data.map((r) => outputCodec(r)),
430
442
  };
431
443
  }
432
444
  finally {
@@ -525,8 +537,8 @@ class MongoNestedService extends mongo_service_js_1.MongoService {
525
537
  }, info);
526
538
  }
527
539
  async _updateMany(documentId, input, options) {
528
- const encode = this.getEncoder('update');
529
- const doc = encode(input, { coerce: true });
540
+ const inputCodec = this.getInputCodec('update');
541
+ const doc = inputCodec(input);
530
542
  if (!Object.keys(doc).length)
531
543
  return 0;
532
544
  const matchFilter = mongo_adapter_js_1.MongoAdapter.prepareFilter([
@@ -547,18 +559,6 @@ class MongoNestedService extends mongo_service_js_1.MongoService {
547
559
  return await this._count(documentId, options);
548
560
  return r.modifiedCount || 0;
549
561
  }
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
562
  /**
563
563
  * Retrieves the common filter used for querying array elements.
564
564
  * This method is mostly used for security issues like securing multi-tenant applications.
@@ -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;
@@ -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;
85
- const options = { projection: '*' };
84
+ getInputCodec(operation) {
85
+ let validator = this._inputCodecs[operation];
86
+ if (validator)
87
+ return validator;
88
+ const options = { projection: '*', ignoreReadonlyFields: true };
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', ignoreWriteonlyFields: true };
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.
@@ -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 {
@@ -237,8 +239,8 @@ export class MongoEntityService extends MongoService {
237
239
  throw new TypeError('You must pass one of MongoDB UpdateFilter or a partial document, not both');
238
240
  let update;
239
241
  if (isDocument) {
240
- const encode = this.getEncoder('update');
241
- const doc = encode(input, { coerce: true });
242
+ const inputCodec = this.getInputCodec('update');
243
+ const doc = inputCodec(input);
242
244
  delete doc._id;
243
245
  update = MongoAdapter.preparePatch(doc);
244
246
  update.$set = update.$set || {};
@@ -250,11 +252,12 @@ export class MongoEntityService extends MongoService {
250
252
  ...options,
251
253
  includeResultMetadata: false,
252
254
  upsert: undefined,
253
- projection: MongoAdapter.prepareProjection(this.getDataType(), options?.projection),
255
+ projection: MongoAdapter.prepareProjection(this.dataType, options?.projection),
254
256
  };
255
- const decode = this.getDecoder();
256
257
  const out = await this._dbFindOneAndUpdate(filter, update, mongoOptions);
257
- return out ? decode(out, { coerce: true }) : undefined;
258
+ const outputCodec = this.getOutputCodec('update');
259
+ if (out)
260
+ return outputCodec(out);
258
261
  }
259
262
  /**
260
263
  * Updates a document in the collection with the specified ID.
@@ -271,8 +274,8 @@ export class MongoEntityService extends MongoService {
271
274
  throw new TypeError('You must pass one of MongoDB UpdateFilter or a partial document, not both');
272
275
  let update;
273
276
  if (isDocument) {
274
- const encode = this.getEncoder('update');
275
- const doc = encode(input, { coerce: true });
277
+ const inputCodec = this.getInputCodec('update');
278
+ const doc = inputCodec(input);
276
279
  delete doc._id;
277
280
  update = MongoAdapter.preparePatch(doc);
278
281
  if (!Object.keys(doc).length)
@@ -285,7 +288,7 @@ export class MongoEntityService extends MongoService {
285
288
  ...options,
286
289
  includeResultMetadata: false,
287
290
  upsert: undefined,
288
- projection: MongoAdapter.prepareProjection(this.getDataType(), options?.projection),
291
+ projection: MongoAdapter.prepareProjection(this.dataType, options?.projection),
289
292
  };
290
293
  const out = await this._dbUpdateOne(filter, update, mongoOptions);
291
294
  return out.matchedCount;
@@ -304,8 +307,8 @@ export class MongoEntityService extends MongoService {
304
307
  throw new TypeError('You must pass one of MongoDB UpdateFilter or a partial document, not both');
305
308
  let update;
306
309
  if (isDocument) {
307
- const encode = this.getEncoder('update');
308
- const doc = encode(input, { coerce: true });
310
+ const inputCodec = this.getInputCodec('update');
311
+ const doc = inputCodec(input);
309
312
  delete doc._id;
310
313
  update = MongoAdapter.preparePatch(doc);
311
314
  if (!Object.keys(doc).length)
@@ -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.
@@ -62,8 +74,8 @@ export class MongoNestedService extends MongoService {
62
74
  return this._intercept(() => this._create(documentId, input, options), info);
63
75
  }
64
76
  async _create(documentId, input, options) {
65
- const encode = this.getEncoder('create');
66
- const doc = encode(input);
77
+ const inputCodec = this.getInputCodec('create');
78
+ const doc = inputCodec(input);
67
79
  doc._id = doc._id || this._generateId();
68
80
  const docFilter = MongoAdapter.prepareKeyValues(documentId, ['_id']);
69
81
  const r = await this._dbUpdateOne(docFilter, {
@@ -336,14 +348,14 @@ export class MongoNestedService extends MongoService {
336
348
  stages.push({ $sort: sort });
337
349
  }
338
350
  stages.push({ $limit: limit });
339
- const dataType = this.getDataType();
351
+ const dataType = this.dataType;
340
352
  const projection = MongoAdapter.prepareProjection(dataType, options?.projection);
341
353
  if (projection)
342
354
  stages.push({ $project: projection });
343
- const decode = this.getDecoder();
344
355
  const cursor = await this._dbAggregate(stages, mongoOptions);
345
356
  try {
346
- const out = await (await cursor.toArray()).map((r) => decode(r));
357
+ const outputCodec = this.getOutputCodec('find');
358
+ const out = await (await cursor.toArray()).map((r) => outputCodec(r));
347
359
  return out;
348
360
  }
349
361
  finally {
@@ -410,19 +422,19 @@ export class MongoNestedService extends MongoService {
410
422
  dataStages.push({ $sort: sort });
411
423
  }
412
424
  dataStages.push({ $limit: limit });
413
- const dataType = this.getDataType();
425
+ const dataType = this.dataType;
414
426
  const projection = MongoAdapter.prepareProjection(dataType, options?.projection);
415
427
  if (projection)
416
428
  dataStages.push({ $project: projection });
417
- const decode = this.getDecoder();
418
429
  const cursor = await this._dbAggregate(stages, {
419
430
  ...mongoOptions,
420
431
  });
421
432
  try {
422
433
  const facetResult = await cursor.toArray();
434
+ const outputCodec = this.getOutputCodec('find');
423
435
  return {
424
436
  count: facetResult[0].count[0].totalMatches || 0,
425
- items: facetResult[0].data.map((r) => decode(r)),
437
+ items: facetResult[0].data.map((r) => outputCodec(r)),
426
438
  };
427
439
  }
428
440
  finally {
@@ -521,8 +533,8 @@ export class MongoNestedService extends MongoService {
521
533
  }, info);
522
534
  }
523
535
  async _updateMany(documentId, input, options) {
524
- const encode = this.getEncoder('update');
525
- const doc = encode(input, { coerce: true });
536
+ const inputCodec = this.getInputCodec('update');
537
+ const doc = inputCodec(input);
526
538
  if (!Object.keys(doc).length)
527
539
  return 0;
528
540
  const matchFilter = MongoAdapter.prepareFilter([
@@ -543,18 +555,6 @@ export class MongoNestedService extends MongoService {
543
555
  return await this._count(documentId, options);
544
556
  return r.modifiedCount || 0;
545
557
  }
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
558
  /**
559
559
  * Retrieves the common filter used for querying array elements.
560
560
  * This method is mostly used for security issues like securing multi-tenant applications.
@@ -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;
@@ -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;
82
- const options = { projection: '*' };
81
+ getInputCodec(operation) {
82
+ let validator = this._inputCodecs[operation];
83
+ if (validator)
84
+ return validator;
85
+ const options = { projection: '*', ignoreReadonlyFields: true };
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', ignoreWriteonlyFields: true };
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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opra/mongodb",
3
- "version": "1.0.0-alpha.3",
3
+ "version": "1.0.0-alpha.4",
4
4
  "description": "Opra MongoDB adapter package",
5
5
  "author": "Panates",
6
6
  "license": "MIT",
@@ -18,8 +18,8 @@
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
20
  "format": "prettier . --write --log-level=warn",
21
- "test": "jest",
22
- "cover": "jest --collect-coverage",
21
+ "test": "jest --passWithNoTests",
22
+ "cover": "jest --passWithNoTests --collect-coverage",
23
23
  "clean": "npm run clean:src && npm run clean:dist && npm run clean:cover",
24
24
  "clean:src": "ts-cleanup -s src --all && ts-cleanup -s test --all",
25
25
  "clean:dist": "rimraf ../../build/mongodb",
@@ -31,8 +31,8 @@
31
31
  "ts-gems": "^3.4.0"
32
32
  },
33
33
  "peerDependencies": {
34
- "@opra/common": "^1.0.0-alpha.3",
35
- "@opra/core": "^1.0.0-alpha.3",
34
+ "@opra/common": "^1.0.0-alpha.4",
35
+ "@opra/core": "^1.0.0-alpha.4",
36
36
  "mongodb": ">= 6.0.0"
37
37
  },
38
38
  "type": "module",
@@ -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.
@@ -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
  */
@@ -210,21 +211,21 @@ export declare class MongoService<T extends mongodb.Document = mongodb.Document>
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
  *