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

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