@opra/mongodb 1.0.0-alpha.2 → 1.0.0-alpha.20

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,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,23 +19,24 @@ 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
- this.$documentFilter = this.$documentFilter || options?.documentFilter;
26
- this.$interceptor = this.$interceptor || options?.interceptor;
27
- this.$collectionName = options?.collectionName;
28
- if (!this.$collectionName) {
26
+ this.documentFilter = options?.documentFilter;
27
+ this.interceptor = options?.interceptor;
28
+ this.collectionName = options?.collectionName;
29
+ if (!this.collectionName) {
29
30
  if (typeof dataType === 'string')
30
- this.$collectionName = dataType;
31
+ this.collectionName = dataType;
31
32
  if (typeof dataType === 'function') {
32
33
  const metadata = Reflect.getMetadata(common_1.DATATYPE_METADATA, dataType);
33
34
  if (metadata)
34
- this.$collectionName = metadata.name;
35
+ this.collectionName = metadata.name;
35
36
  }
36
37
  }
37
- this.$resourceName = options?.resourceName;
38
- this.$idGenerator = options?.idGenerator;
38
+ this.resourceName = options?.resourceName;
39
+ this.idGenerator = options?.idGenerator;
39
40
  }
40
41
  /**
41
42
  * Retrieves the collection name.
@@ -45,7 +46,7 @@ class MongoService extends core_1.ServiceBase {
45
46
  * @throws {Error} If the collection name is not defined.
46
47
  */
47
48
  getCollectionName() {
48
- const out = typeof this.$collectionName === 'function' ? this.$collectionName(this) : this.$collectionName;
49
+ const out = typeof this.collectionName === 'function' ? this.collectionName(this) : this.collectionName;
49
50
  if (out)
50
51
  return out;
51
52
  throw new Error('collectionName is not defined');
@@ -55,53 +56,53 @@ 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
- const out = typeof this.$resourceName === 'function'
62
- ? this.$resourceName(this)
63
- : this.$resourceName || this.getCollectionName();
62
+ const out = typeof this.resourceName === 'function' ? this.resourceName(this) : this.resourceName || this.getCollectionName();
64
63
  if (out)
65
64
  return out;
66
65
  throw new Error('resourceName is not defined');
67
66
  }
68
67
  /**
69
- * Retrieves the data type of the document
68
+ * Retrieves the OPRA data type
70
69
  *
71
70
  * @throws {NotAcceptableError} If the data type is not a ComplexType.
72
71
  */
73
- getDataType() {
74
- return this.context.document.node.getComplexType(this._dataType);
72
+ get dataType() {
73
+ if (!this._dataType)
74
+ this._dataType = this.context.document.node.getComplexType(this._dataType_);
75
+ return this._dataType;
75
76
  }
76
77
  /**
77
- * Retrieves the encoder for the specified operation.
78
+ * Retrieves the codec for the specified operation.
78
79
  *
79
80
  * @param operation - The operation to retrieve the encoder for. Valid values are 'create' and 'update'.
80
81
  */
81
- getEncoder(operation) {
82
- let encoder = this._encoders[operation];
83
- if (encoder)
84
- return encoder;
82
+ getInputCodec(operation) {
83
+ let validator = this._inputCodecs[operation];
84
+ if (validator)
85
+ return validator;
85
86
  const options = { projection: '*' };
86
87
  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;
88
+ options.partial = 'deep';
89
+ const dataType = this.dataType;
90
+ validator = dataType.generateCodec('decode', options);
91
+ this._inputCodecs[operation] = validator;
92
+ return validator;
92
93
  }
93
94
  /**
94
- * Retrieves the decoder.
95
+ * Retrieves the codec.
95
96
  */
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;
97
+ getOutputCodec(operation) {
98
+ let validator = this._outputCodecs[operation];
99
+ if (validator)
100
+ return validator;
101
+ const options = { projection: '*', partial: 'deep' };
102
+ const dataType = this.dataType;
103
+ validator = dataType.generateCodec('decode', options);
104
+ this._outputCodecs[operation] = validator;
105
+ return validator;
105
106
  }
106
107
  /**
107
108
  * Executes the provided function within a transaction.
@@ -114,7 +115,7 @@ class MongoService extends core_1.ServiceBase {
114
115
  if (session)
115
116
  return callback(session);
116
117
  // Backup old session property
117
- const hasOldSession = this.hasOwnProperty('session');
118
+ const hasOldSession = Object.prototype.hasOwnProperty.call(this, 'session');
118
119
  const oldSessionGetter = hasOldSession ? this.session : undefined;
119
120
  const db = this.getDatabase();
120
121
  const client = db.client;
@@ -367,8 +368,8 @@ class MongoService extends core_1.ServiceBase {
367
368
  * @protected
368
369
  * @returns {MongoAdapter.AnyId} The generated ID.
369
370
  */
370
- _generateId() {
371
- return typeof this.$idGenerator === 'function' ? this.$idGenerator(this) : new mongodb_1.ObjectId();
371
+ _generateId(command) {
372
+ return typeof this.idGenerator === 'function' ? this.idGenerator(command, this) : new mongodb_1.ObjectId();
372
373
  }
373
374
  /**
374
375
  * Retrieves the common filter used for querying documents.
@@ -378,18 +379,29 @@ class MongoService extends core_1.ServiceBase {
378
379
  * @returns {FilterInput | Promise<FilterInput> | undefined} The common filter or a Promise
379
380
  * that resolves to the common filter, or undefined if not available.
380
381
  */
381
- _getDocumentFilter(info) {
382
- return typeof this.$documentFilter === 'function' ? this.$documentFilter(info, this) : this.$documentFilter;
382
+ _getDocumentFilter(command) {
383
+ return typeof this.documentFilter === 'function' ? this.documentFilter(command, this) : this.documentFilter;
383
384
  }
384
- async _intercept(callback, info) {
385
+ async _executeCommand(command, commandFn) {
386
+ let proto;
387
+ const next = async () => {
388
+ proto = proto ? Object.getPrototypeOf(proto) : this;
389
+ while (proto) {
390
+ if (proto.interceptor) {
391
+ return await proto.interceptor.call(this, next, command, this);
392
+ }
393
+ proto = Object.getPrototypeOf(proto);
394
+ if (!(proto instanceof MongoService))
395
+ break;
396
+ }
397
+ return commandFn();
398
+ };
385
399
  try {
386
- if (this.$interceptor)
387
- return this.$interceptor(callback, info, this);
388
- return callback();
400
+ return await next();
389
401
  }
390
402
  catch (e) {
391
- Error.captureStackTrace(e, this._intercept);
392
- await this.$onError?.(e, this);
403
+ Error.captureStackTrace(e, this._executeCommand);
404
+ await this.onError?.(e, this);
393
405
  throw e;
394
406
  }
395
407
  }
@@ -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
  /**
@@ -26,7 +26,7 @@ class MongoSingletonService extends mongo_entity_service_js_1.MongoEntityService
26
26
  /**
27
27
  * Asserts the existence of a resource based on the given options.
28
28
  *
29
- * @param {MongoSingletonService.ExistsOptions<T>} [options]
29
+ * @param {MongoEntityService.ExistsOptions<T>} [options]
30
30
  * @returns {Promise<void>} A Promise that resolves when the resource exists.
31
31
  * @throws {ResourceNotAvailableError} If the resource does not exist.
32
32
  */
@@ -38,72 +38,88 @@ class MongoSingletonService extends mongo_entity_service_js_1.MongoEntityService
38
38
  * Creates the document in the database.
39
39
  *
40
40
  * @param {PartialDTO<T>} input - The partial input to create the document with.
41
- * @param {MongoSingletonService.CreateOptions} [options] - The options for creating the document.
41
+ * @param {MongoEntityService.CreateOptions} [options] - The options for creating the document.
42
42
  * @return {Promise<PartialDTO<T>>} A promise that resolves to the partial output of the created document.
43
43
  * @throws {Error} Throws an error if an unknown error occurs while creating the document.
44
44
  */
45
45
  async create(input, options) {
46
- input._id = this._id;
47
- const info = {
46
+ const command = {
48
47
  crud: 'create',
49
48
  method: 'create',
50
49
  byId: false,
51
- documentId: this._id,
52
50
  input,
53
51
  options,
54
52
  };
55
- return this._intercept(() => this._create(input, options), info);
53
+ input._id = this._id;
54
+ return this._executeCommand(command, () => this._create(command));
56
55
  }
57
56
  /**
58
57
  * Deletes a record from the database
59
58
  *
60
- * @param {MongoSingletonService.DeleteOptions<T>} options - The options for deleting the record
59
+ * @param {MongoEntityService.DeleteOptions<T>} options - The options for deleting the record
61
60
  * @returns {Promise<number>} The number of records deleted
62
61
  */
63
62
  async delete(options) {
64
- const info = {
63
+ const command = {
65
64
  crud: 'delete',
66
65
  method: 'delete',
67
66
  byId: true,
68
67
  documentId: this._id,
69
68
  options,
70
69
  };
71
- return this._intercept(async () => {
72
- const filter = mongo_adapter_js_1.MongoAdapter.prepareFilter([await this._getDocumentFilter(info), options?.filter]);
73
- return this._delete(this._id, { ...options, filter });
74
- }, info);
70
+ return this._executeCommand(command, async () => {
71
+ const filter = mongo_adapter_js_1.MongoAdapter.prepareFilter([await this._getDocumentFilter(command), command.options?.filter]);
72
+ command.options = { ...command.options, filter };
73
+ return this._delete(command);
74
+ });
75
75
  }
76
76
  /**
77
77
  * Checks if the document exists in the database.
78
78
  *
79
+ * @param {MongoEntityService.FindOneOptions<T>} [options] - The options for finding the document.
79
80
  * @return {Promise<boolean>} - A promise that resolves to a boolean value indicating if the document exists.
80
81
  */
81
82
  async exists(options) {
82
- return !!(await this.find({ ...options, projection: ['_id'], skip: undefined }));
83
+ const command = {
84
+ crud: 'read',
85
+ method: 'exists',
86
+ byId: true,
87
+ documentId: this._id,
88
+ options,
89
+ };
90
+ return this._executeCommand(command, async () => {
91
+ const documentFilter = await this._getDocumentFilter(command);
92
+ const filter = mongo_adapter_js_1.MongoAdapter.prepareFilter([documentFilter, command.options?.filter]);
93
+ const findCommand = command;
94
+ findCommand.options = { ...command.options, filter, projection: ['_id'] };
95
+ return !!(await this._findById(findCommand));
96
+ });
83
97
  }
84
98
  /**
85
99
  * Fetches the document if it exists. Returns undefined if not found.
86
100
  *
87
- * @param {MongoSingletonService.FindOneOptions<T>} [options] - The options for finding the document.
101
+ * @param {MongoEntityService.FindOneOptions<T>} [options] - The options for finding the document.
88
102
  * @returns {Promise<PartialDTO<T> | undefined>} - A promise that resolves to the found document or undefined if not found.
89
103
  */
90
104
  async find(options) {
91
- const info = {
105
+ const command = {
92
106
  crud: 'read',
93
- method: 'findOne',
107
+ method: 'findById',
94
108
  byId: true,
95
109
  documentId: this._id,
96
110
  options,
97
111
  };
98
- return this._intercept(async () => {
99
- const filter = mongo_adapter_js_1.MongoAdapter.prepareFilter([await this._getDocumentFilter(info), options?.filter]);
100
- return this._findById(this._id, { ...options, filter });
101
- }, info);
112
+ return this._executeCommand(command, async () => {
113
+ const documentFilter = await this._getDocumentFilter(command);
114
+ const filter = mongo_adapter_js_1.MongoAdapter.prepareFilter([documentFilter, command.options?.filter]);
115
+ command.options = { ...command.options, filter };
116
+ return this._findById(command);
117
+ });
102
118
  }
103
119
  /**
104
120
  * Fetches the document from the Mongo collection service. Throws error if not found.
105
121
  *
106
- * @param {MongoSingletonService.FindOneOptions<T>} options - The options to customize the query.
122
+ * @param {MongoEntityService.FindOneOptions<T>} options - The options to customize the query.
107
123
  * @return {Promise<PartialDTO<T>>} - A promise that resolves to the fetched document.
108
124
  * @throws {ResourceNotAvailableError} - If the document is not found in the collection.
109
125
  */
@@ -117,45 +133,49 @@ class MongoSingletonService extends mongo_entity_service_js_1.MongoEntityService
117
133
  * Updates a document in the MongoDB collection.
118
134
  *
119
135
  * @param {PatchDTO<T>} input - The partial input to update the document.
120
- * @param {MongoSingletonService.UpdateOptions<T>} [options] - The update options.
136
+ * @param {MongoEntityService.UpdateOneOptions<T>} [options] - The update options.
121
137
  *
122
- * @return {Promise<number>} - A promise that resolves to the updated document or undefined if not found.
138
+ * @return {Promise<PartialDTO<T> | undefined>} - A promise that resolves to the updated document or undefined if not found.
123
139
  */
124
- async updateOnly(input, options) {
125
- const info = {
140
+ async update(input, options) {
141
+ const isUpdateFilter = Array.isArray(input) || !!Object.keys(input).find(x => x.startsWith('$'));
142
+ const command = {
126
143
  crud: 'update',
127
144
  method: 'update',
128
- byId: true,
129
145
  documentId: this._id,
130
- input,
146
+ byId: true,
147
+ input: isUpdateFilter ? undefined : input,
148
+ inputRaw: isUpdateFilter ? input : undefined,
131
149
  options,
132
150
  };
133
- return this._intercept(async () => {
134
- const filter = mongo_adapter_js_1.MongoAdapter.prepareFilter([await this._getDocumentFilter(info), options?.filter]);
135
- return this._updateOnly(this._id, input, { ...options, filter });
136
- }, info);
151
+ return this._executeCommand(command, async () => {
152
+ const filter = mongo_adapter_js_1.MongoAdapter.prepareFilter([await this._getDocumentFilter(command), command.options?.filter]);
153
+ command.options = { ...command.options, filter };
154
+ return this._update(command);
155
+ });
137
156
  }
138
157
  /**
139
158
  * Updates a document in the MongoDB collection.
140
159
  *
141
160
  * @param {PatchDTO<T>} input - The partial input to update the document.
142
- * @param {MongoSingletonService.UpdateOptions<T>} [options] - The update options.
161
+ * @param {MongoEntityService.UpdateOneOptions<T>} [options] - The update options.
143
162
  *
144
- * @return {Promise<PartialDTO<T> | undefined>} - A promise that resolves to the updated document or undefined if not found.
163
+ * @return {Promise<number>} - A promise that resolves to the updated document or undefined if not found.
145
164
  */
146
- async update(input, options) {
147
- const info = {
165
+ async updateOnly(input, options) {
166
+ const command = {
148
167
  crud: 'update',
149
- method: 'update',
150
- byId: true,
168
+ method: 'updateOnly',
151
169
  documentId: this._id,
170
+ byId: true,
152
171
  input,
153
172
  options,
154
173
  };
155
- return this._intercept(async () => {
156
- const filter = mongo_adapter_js_1.MongoAdapter.prepareFilter([await this._getDocumentFilter(info), options?.filter]);
157
- return this._update(this._id, input, { ...options, filter });
158
- }, info);
174
+ return this._executeCommand(command, async () => {
175
+ const filter = mongo_adapter_js_1.MongoAdapter.prepareFilter([await this._getDocumentFilter(command), command.options?.filter]);
176
+ command.options = { ...command.options, filter };
177
+ return this._updateOnly(command);
178
+ });
159
179
  }
160
180
  }
161
181
  exports.MongoSingletonService = MongoSingletonService;
@@ -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`);