@opra/mongodb 1.2.3 → 1.3.1

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.
Files changed (41) hide show
  1. package/cjs/{mongo-adapter.js → adapter/mongo-adapter.js} +4 -6
  2. package/cjs/adapter/mongo-patch-generator.js +165 -0
  3. package/cjs/index.js +7 -6
  4. package/cjs/{mongo-collection-service.js → services/mongo-collection-service.js} +1 -1
  5. package/cjs/{mongo-entity-service.js → services/mongo-entity-service.js} +16 -7
  6. package/cjs/{mongo-nested-service.js → services/mongo-nested-service.js} +10 -3
  7. package/cjs/{mongo-service.js → services/mongo-service.js} +4 -2
  8. package/cjs/{mongo-singleton-service.js → services/mongo-singleton-service.js} +1 -1
  9. package/esm/{mongo-adapter.js → adapter/mongo-adapter.js} +4 -6
  10. package/esm/adapter/mongo-patch-generator.js +161 -0
  11. package/esm/index.js +7 -6
  12. package/esm/{mongo-collection-service.js → services/mongo-collection-service.js} +1 -1
  13. package/esm/{mongo-entity-service.js → services/mongo-entity-service.js} +16 -7
  14. package/esm/{mongo-nested-service.js → services/mongo-nested-service.js} +10 -3
  15. package/esm/{mongo-service.js → services/mongo-service.js} +4 -2
  16. package/esm/{mongo-singleton-service.js → services/mongo-singleton-service.js} +1 -1
  17. package/package.json +4 -4
  18. package/types/{mongo-adapter.d.ts → adapter/mongo-adapter.d.ts} +4 -6
  19. package/types/adapter/mongo-patch-generator.d.ts +25 -0
  20. package/types/{adapter-utils → adapter}/prepare-filter.d.ts +1 -1
  21. package/types/index.d.cts +7 -6
  22. package/types/index.d.ts +7 -6
  23. package/types/{mongo-collection-service.d.ts → services/mongo-collection-service.d.ts} +1 -1
  24. package/types/{mongo-entity-service.d.ts → services/mongo-entity-service.d.ts} +1 -0
  25. package/types/{mongo-nested-service.d.ts → services/mongo-nested-service.d.ts} +2 -2
  26. package/types/{mongo-service.d.ts → services/mongo-service.d.ts} +1 -1
  27. package/types/{mongo-singleton-service.d.ts → services/mongo-singleton-service.d.ts} +1 -1
  28. package/cjs/adapter-utils/prepare-patch.js +0 -37
  29. package/esm/adapter-utils/prepare-patch.js +0 -34
  30. package/types/adapter-utils/prepare-patch.d.ts +0 -3
  31. /package/cjs/{adapter-utils → adapter}/prepare-filter.js +0 -0
  32. /package/cjs/{adapter-utils → adapter}/prepare-key-values.js +0 -0
  33. /package/cjs/{adapter-utils → adapter}/prepare-projection.js +0 -0
  34. /package/cjs/{adapter-utils → adapter}/prepare-sort.js +0 -0
  35. /package/esm/{adapter-utils → adapter}/prepare-filter.js +0 -0
  36. /package/esm/{adapter-utils → adapter}/prepare-key-values.js +0 -0
  37. /package/esm/{adapter-utils → adapter}/prepare-projection.js +0 -0
  38. /package/esm/{adapter-utils → adapter}/prepare-sort.js +0 -0
  39. /package/types/{adapter-utils → adapter}/prepare-key-values.d.ts +0 -0
  40. /package/types/{adapter-utils → adapter}/prepare-projection.d.ts +0 -0
  41. /package/types/{adapter-utils → adapter}/prepare-sort.d.ts +0 -0
@@ -2,16 +2,14 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MongoAdapter = void 0;
4
4
  const tslib_1 = require("tslib");
5
- const prepare_filter_js_1 = tslib_1.__importDefault(require("./adapter-utils/prepare-filter.js"));
6
- const prepare_key_values_js_1 = tslib_1.__importDefault(require("./adapter-utils/prepare-key-values.js"));
7
- const prepare_patch_js_1 = tslib_1.__importDefault(require("./adapter-utils/prepare-patch.js"));
8
- const prepare_projection_js_1 = tslib_1.__importDefault(require("./adapter-utils/prepare-projection.js"));
9
- const prepare_sort_js_1 = tslib_1.__importDefault(require("./adapter-utils/prepare-sort.js"));
5
+ const prepare_filter_js_1 = tslib_1.__importDefault(require("./prepare-filter.js"));
6
+ const prepare_key_values_js_1 = tslib_1.__importDefault(require("./prepare-key-values.js"));
7
+ const prepare_projection_js_1 = tslib_1.__importDefault(require("./prepare-projection.js"));
8
+ const prepare_sort_js_1 = tslib_1.__importDefault(require("./prepare-sort.js"));
10
9
  var MongoAdapter;
11
10
  (function (MongoAdapter) {
12
11
  MongoAdapter.prepareFilter = prepare_filter_js_1.default;
13
12
  MongoAdapter.prepareKeyValues = prepare_key_values_js_1.default;
14
- MongoAdapter.preparePatch = prepare_patch_js_1.default;
15
13
  MongoAdapter.prepareProjection = prepare_projection_js_1.default;
16
14
  MongoAdapter.prepareSort = prepare_sort_js_1.default;
17
15
  async function parseRequest(context) {
@@ -0,0 +1,165 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MongoPatchGenerator = void 0;
4
+ const common_1 = require("@opra/common");
5
+ const FIELD_NAME_PATTERN = /^([-><*:])?(.+)$/;
6
+ class MongoPatchGenerator {
7
+ generatePatch(dataType, doc, options) {
8
+ const ctx = {};
9
+ this._processComplexType(ctx, dataType, options?.currentPath || '', doc);
10
+ const update = {};
11
+ if (ctx.$pull)
12
+ update.$pull = ctx.$pull;
13
+ if (ctx.$unset)
14
+ update.$unset = ctx.$unset;
15
+ if (ctx.$set)
16
+ update.$set = ctx.$set;
17
+ if (ctx.$push)
18
+ update.$push = ctx.$push;
19
+ return {
20
+ update,
21
+ arrayFilters: ctx.arrayFilters,
22
+ };
23
+ }
24
+ _processComplexType(ctx, dataType, path, input) {
25
+ if (input.$add) {
26
+ this._processAdd(ctx, dataType, path, input.$add);
27
+ }
28
+ if (input.$remove) {
29
+ this._processRemove(ctx, dataType, path, input.$remove);
30
+ }
31
+ const keys = Object.keys(input);
32
+ const pathDot = path + (path ? '.' : '');
33
+ let field;
34
+ let key;
35
+ let value;
36
+ let keyField;
37
+ let keyValue;
38
+ let arrayIndex = 0;
39
+ let arrayFilterName = '';
40
+ for (key of keys) {
41
+ const m = FIELD_NAME_PATTERN.exec(key);
42
+ if (!m)
43
+ continue;
44
+ key = m[2];
45
+ value = input[key];
46
+ field = dataType.fields.get(key);
47
+ if (!field) {
48
+ if (dataType.additionalFields === true) {
49
+ if (value === null) {
50
+ ctx.$unset = ctx.$unset || {};
51
+ ctx.$unset[pathDot + key] = 1;
52
+ }
53
+ else {
54
+ ctx.$set = ctx.$set || {};
55
+ ctx.$set[pathDot + key] = value;
56
+ }
57
+ }
58
+ continue;
59
+ }
60
+ if (field.readonly)
61
+ continue;
62
+ if (value === null) {
63
+ ctx.$unset = ctx.$unset || {};
64
+ ctx.$unset[pathDot + field.name] = 1;
65
+ continue;
66
+ }
67
+ if (field.type instanceof common_1.ComplexType) {
68
+ if (!value)
69
+ continue;
70
+ if (field.isArray) {
71
+ if (!value.length)
72
+ continue;
73
+ keyField = field.keyField || field.type.keyField;
74
+ if (keyField) {
75
+ for (let v of value) {
76
+ /** Increase arrayIndex and determine a new name for array filter */
77
+ arrayFilterName = 'f' + String(++arrayIndex);
78
+ /** Extract key value from object */
79
+ keyValue = v[keyField];
80
+ if (keyValue == null)
81
+ continue;
82
+ v = { ...v };
83
+ /** Remove key field from object */
84
+ delete v[keyField];
85
+ /** Add array filter */
86
+ ctx.arrayFilters = ctx.arrayFilters || {};
87
+ ctx.arrayFilters[`${arrayFilterName}.${keyField}`] = keyValue;
88
+ /** Process each object in array */
89
+ this._processComplexType(ctx, field.type, pathDot + field.name + `.$[${arrayFilterName}]`, v);
90
+ }
91
+ continue;
92
+ }
93
+ }
94
+ if (!(typeof value === 'object'))
95
+ continue;
96
+ /** Process nested object */
97
+ this._processComplexType(ctx, field.type, pathDot + field.name, value);
98
+ continue;
99
+ }
100
+ ctx.$set = ctx.$set || {};
101
+ ctx.$set[pathDot + field.name] = value;
102
+ }
103
+ }
104
+ _processAdd(ctx, dataType, path, input) {
105
+ let field;
106
+ let key;
107
+ let value;
108
+ const pathDot = path + (path ? '.' : '');
109
+ const keys = Object.keys(input);
110
+ let keyField;
111
+ for (key of keys) {
112
+ value = input[key];
113
+ field = dataType.fields.get(key);
114
+ if (!(field && field.isArray))
115
+ continue;
116
+ ctx.$push = ctx.$push || {};
117
+ if (field.type instanceof common_1.ComplexType) {
118
+ keyField = field.keyField || field.type.keyField;
119
+ if (keyField) {
120
+ if (Array.isArray(value)) {
121
+ value.forEach(v => {
122
+ if (!v[keyField]) {
123
+ throw new TypeError(`You must provide a key value of ${field.type.name} for $add operation.`);
124
+ }
125
+ });
126
+ ctx.$push[pathDot + key] = { $each: value };
127
+ }
128
+ else {
129
+ if (!value[keyField]) {
130
+ throw new TypeError(`You must provide a key value of ${field.type.name} for $add operation.`);
131
+ }
132
+ ctx.$push[pathDot + key] = value;
133
+ }
134
+ }
135
+ continue;
136
+ }
137
+ ctx.$push[pathDot + key] = Array.isArray(value) ? { $each: value } : value;
138
+ }
139
+ }
140
+ _processRemove(ctx, dataType, path, input) {
141
+ let field;
142
+ let key;
143
+ let value;
144
+ const pathDot = path + (path ? '.' : '');
145
+ const keys = Object.keys(input);
146
+ let keyField;
147
+ for (key of keys) {
148
+ value = input[key];
149
+ field = dataType.fields.get(key);
150
+ if (!(field && field.isArray))
151
+ continue;
152
+ ctx.$pull = ctx.$pull || {};
153
+ if (field.type instanceof common_1.ComplexType) {
154
+ keyField = field.keyField || field.type.keyField;
155
+ if (!keyField)
156
+ continue;
157
+ ctx.$pull[pathDot + key] = { $elemMatch: { [keyField]: Array.isArray(value) ? { $in: value } : value } };
158
+ }
159
+ else {
160
+ ctx.$pull[pathDot + key] = Array.isArray(value) ? { $in: value } : value;
161
+ }
162
+ }
163
+ }
164
+ }
165
+ exports.MongoPatchGenerator = MongoPatchGenerator;
package/cjs/index.js CHANGED
@@ -1,9 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const tslib_1 = require("tslib");
4
- tslib_1.__exportStar(require("./mongo-adapter.js"), exports);
5
- tslib_1.__exportStar(require("./mongo-collection-service.js"), exports);
6
- tslib_1.__exportStar(require("./mongo-entity-service.js"), exports);
7
- tslib_1.__exportStar(require("./mongo-nested-service.js"), exports);
8
- tslib_1.__exportStar(require("./mongo-service.js"), exports);
9
- tslib_1.__exportStar(require("./mongo-singleton-service.js"), exports);
4
+ tslib_1.__exportStar(require("./adapter/mongo-adapter.js"), exports);
5
+ tslib_1.__exportStar(require("./adapter/mongo-patch-generator.js"), exports);
6
+ tslib_1.__exportStar(require("./services/mongo-collection-service.js"), exports);
7
+ tslib_1.__exportStar(require("./services/mongo-entity-service.js"), exports);
8
+ tslib_1.__exportStar(require("./services/mongo-nested-service.js"), exports);
9
+ tslib_1.__exportStar(require("./services/mongo-service.js"), exports);
10
+ tslib_1.__exportStar(require("./services/mongo-singleton-service.js"), exports);
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MongoCollectionService = void 0;
4
4
  const common_1 = require("@opra/common");
5
- const mongo_adapter_js_1 = require("./mongo-adapter.js");
5
+ const mongo_adapter_js_1 = require("../adapter/mongo-adapter.js");
6
6
  const mongo_entity_service_js_1 = require("./mongo-entity-service.js");
7
7
  /**
8
8
  * @class MongoCollectionService
@@ -4,7 +4,8 @@ exports.MongoEntityService = void 0;
4
4
  const objects_1 = require("@jsopen/objects");
5
5
  const common_1 = require("@opra/common");
6
6
  const valgen_1 = require("valgen");
7
- const mongo_adapter_js_1 = require("./mongo-adapter.js");
7
+ const mongo_adapter_js_1 = require("../adapter/mongo-adapter.js");
8
+ const mongo_patch_generator_js_1 = require("../adapter/mongo-patch-generator.js");
8
9
  const mongo_service_js_1 = require("./mongo-service.js");
9
10
  /**
10
11
  * @class MongoEntityService
@@ -390,12 +391,20 @@ class MongoEntityService extends mongo_service_js_1.MongoService {
390
391
  if (input && inputRaw) {
391
392
  throw new TypeError('You must pass one of MongoDB UpdateFilter or a partial document, not both');
392
393
  }
393
- let update = { ...inputRaw };
394
- if (input) {
395
- const inputCodec = this._getInputCodec('update');
396
- const doc = inputCodec(input);
397
- delete doc._id;
398
- update = mongo_adapter_js_1.MongoAdapter.preparePatch(doc, {}, update);
394
+ if (inputRaw)
395
+ return inputRaw;
396
+ const inputCodec = this._getInputCodec('update');
397
+ const doc = inputCodec(input);
398
+ delete doc._id;
399
+ return this._generatePatch(command, doc);
400
+ }
401
+ _generatePatch(command, doc) {
402
+ const patchGenerator = new mongo_patch_generator_js_1.MongoPatchGenerator();
403
+ const { update, arrayFilters } = patchGenerator.generatePatch(this.dataType, doc);
404
+ command.options = command.options || {};
405
+ if (arrayFilters) {
406
+ command.options.arrayFilters = command.options.arrayFilters || [];
407
+ command.options.arrayFilters.push(arrayFilters);
399
408
  }
400
409
  return update;
401
410
  }
@@ -4,7 +4,8 @@ exports.MongoNestedService = void 0;
4
4
  const objects_1 = require("@jsopen/objects");
5
5
  const common_1 = require("@opra/common");
6
6
  const valgen_1 = require("valgen");
7
- const mongo_adapter_js_1 = require("./mongo-adapter.js");
7
+ const mongo_adapter_js_1 = require("../adapter/mongo-adapter.js");
8
+ const mongo_patch_generator_js_1 = require("../adapter/mongo-patch-generator.js");
8
9
  const mongo_service_js_1 = require("./mongo-service.js");
9
10
  /**
10
11
  * A class that provides methods to perform operations on an array field in a MongoDB collection.
@@ -687,9 +688,15 @@ class MongoNestedService extends mongo_service_js_1.MongoService {
687
688
  const elemMatch = mongo_adapter_js_1.MongoAdapter.prepareFilter([options?.filter], { fieldPrefix: 'elem.' });
688
689
  options.arrayFilters = [elemMatch];
689
690
  }
690
- const update = mongo_adapter_js_1.MongoAdapter.preparePatch(doc, {
691
- fieldPrefix: this.fieldName + (options?.filter ? '.$[elem].' : '.$[].'),
691
+ const patchGenerator = new mongo_patch_generator_js_1.MongoPatchGenerator();
692
+ const { update, arrayFilters } = patchGenerator.generatePatch(this.dataType, doc, {
693
+ currentPath: this.fieldName + (options?.filter ? '.$[elem]' : '.$[]'),
692
694
  });
695
+ command.options = command.options || {};
696
+ if (arrayFilters) {
697
+ command.options.arrayFilters = command.options.arrayFilters || [];
698
+ command.options.arrayFilters.push(arrayFilters);
699
+ }
693
700
  // Count matching items, we will use this as result
694
701
  const count = await this._count({
695
702
  crud: 'read',
@@ -4,7 +4,7 @@ exports.MongoService = void 0;
4
4
  const common_1 = require("@opra/common");
5
5
  const core_1 = require("@opra/core");
6
6
  const mongodb_1 = require("mongodb");
7
- const mongo_adapter_js_1 = require("./mongo-adapter.js");
7
+ const mongo_adapter_js_1 = require("../adapter/mongo-adapter.js");
8
8
  const transactionKey = Symbol.for('transaction');
9
9
  /**
10
10
  * Class representing a MongoDB service for interacting with a collection.
@@ -232,8 +232,10 @@ class MongoService extends core_1.ServiceBase {
232
232
  if (validator)
233
233
  return validator;
234
234
  const options = { projection: '*' };
235
- if (operation === 'update')
235
+ if (operation === 'update') {
236
236
  options.partial = 'deep';
237
+ options.allowPatchOperators = true;
238
+ }
237
239
  const dataType = this.dataType;
238
240
  validator = dataType.generateCodec('decode', options);
239
241
  this._inputCodecs[operation] = validator;
@@ -4,7 +4,7 @@ exports.MongoSingletonService = void 0;
4
4
  const objects_1 = require("@jsopen/objects");
5
5
  const common_1 = require("@opra/common");
6
6
  const mongodb_1 = require("mongodb");
7
- const mongo_adapter_js_1 = require("./mongo-adapter.js");
7
+ const mongo_adapter_js_1 = require("../adapter/mongo-adapter.js");
8
8
  const mongo_entity_service_js_1 = require("./mongo-entity-service.js");
9
9
  /**
10
10
  * A class that provides access to a MongoDB collection, with support for singleton document operations.
@@ -1,13 +1,11 @@
1
- import _prepareFilter from './adapter-utils/prepare-filter.js';
2
- import _prepareKeyValues from './adapter-utils/prepare-key-values.js';
3
- import _preparePatch from './adapter-utils/prepare-patch.js';
4
- import _prepareProjection from './adapter-utils/prepare-projection.js';
5
- import _prepareSort from './adapter-utils/prepare-sort.js';
1
+ import _prepareFilter from './prepare-filter.js';
2
+ import _prepareKeyValues from './prepare-key-values.js';
3
+ import _prepareProjection from './prepare-projection.js';
4
+ import _prepareSort from './prepare-sort.js';
6
5
  export var MongoAdapter;
7
6
  (function (MongoAdapter) {
8
7
  MongoAdapter.prepareFilter = _prepareFilter;
9
8
  MongoAdapter.prepareKeyValues = _prepareKeyValues;
10
- MongoAdapter.preparePatch = _preparePatch;
11
9
  MongoAdapter.prepareProjection = _prepareProjection;
12
10
  MongoAdapter.prepareSort = _prepareSort;
13
11
  async function parseRequest(context) {
@@ -0,0 +1,161 @@
1
+ import { ComplexType } from '@opra/common';
2
+ const FIELD_NAME_PATTERN = /^([-><*:])?(.+)$/;
3
+ export class MongoPatchGenerator {
4
+ generatePatch(dataType, doc, options) {
5
+ const ctx = {};
6
+ this._processComplexType(ctx, dataType, options?.currentPath || '', doc);
7
+ const update = {};
8
+ if (ctx.$pull)
9
+ update.$pull = ctx.$pull;
10
+ if (ctx.$unset)
11
+ update.$unset = ctx.$unset;
12
+ if (ctx.$set)
13
+ update.$set = ctx.$set;
14
+ if (ctx.$push)
15
+ update.$push = ctx.$push;
16
+ return {
17
+ update,
18
+ arrayFilters: ctx.arrayFilters,
19
+ };
20
+ }
21
+ _processComplexType(ctx, dataType, path, input) {
22
+ if (input.$add) {
23
+ this._processAdd(ctx, dataType, path, input.$add);
24
+ }
25
+ if (input.$remove) {
26
+ this._processRemove(ctx, dataType, path, input.$remove);
27
+ }
28
+ const keys = Object.keys(input);
29
+ const pathDot = path + (path ? '.' : '');
30
+ let field;
31
+ let key;
32
+ let value;
33
+ let keyField;
34
+ let keyValue;
35
+ let arrayIndex = 0;
36
+ let arrayFilterName = '';
37
+ for (key of keys) {
38
+ const m = FIELD_NAME_PATTERN.exec(key);
39
+ if (!m)
40
+ continue;
41
+ key = m[2];
42
+ value = input[key];
43
+ field = dataType.fields.get(key);
44
+ if (!field) {
45
+ if (dataType.additionalFields === true) {
46
+ if (value === null) {
47
+ ctx.$unset = ctx.$unset || {};
48
+ ctx.$unset[pathDot + key] = 1;
49
+ }
50
+ else {
51
+ ctx.$set = ctx.$set || {};
52
+ ctx.$set[pathDot + key] = value;
53
+ }
54
+ }
55
+ continue;
56
+ }
57
+ if (field.readonly)
58
+ continue;
59
+ if (value === null) {
60
+ ctx.$unset = ctx.$unset || {};
61
+ ctx.$unset[pathDot + field.name] = 1;
62
+ continue;
63
+ }
64
+ if (field.type instanceof ComplexType) {
65
+ if (!value)
66
+ continue;
67
+ if (field.isArray) {
68
+ if (!value.length)
69
+ continue;
70
+ keyField = field.keyField || field.type.keyField;
71
+ if (keyField) {
72
+ for (let v of value) {
73
+ /** Increase arrayIndex and determine a new name for array filter */
74
+ arrayFilterName = 'f' + String(++arrayIndex);
75
+ /** Extract key value from object */
76
+ keyValue = v[keyField];
77
+ if (keyValue == null)
78
+ continue;
79
+ v = { ...v };
80
+ /** Remove key field from object */
81
+ delete v[keyField];
82
+ /** Add array filter */
83
+ ctx.arrayFilters = ctx.arrayFilters || {};
84
+ ctx.arrayFilters[`${arrayFilterName}.${keyField}`] = keyValue;
85
+ /** Process each object in array */
86
+ this._processComplexType(ctx, field.type, pathDot + field.name + `.$[${arrayFilterName}]`, v);
87
+ }
88
+ continue;
89
+ }
90
+ }
91
+ if (!(typeof value === 'object'))
92
+ continue;
93
+ /** Process nested object */
94
+ this._processComplexType(ctx, field.type, pathDot + field.name, value);
95
+ continue;
96
+ }
97
+ ctx.$set = ctx.$set || {};
98
+ ctx.$set[pathDot + field.name] = value;
99
+ }
100
+ }
101
+ _processAdd(ctx, dataType, path, input) {
102
+ let field;
103
+ let key;
104
+ let value;
105
+ const pathDot = path + (path ? '.' : '');
106
+ const keys = Object.keys(input);
107
+ let keyField;
108
+ for (key of keys) {
109
+ value = input[key];
110
+ field = dataType.fields.get(key);
111
+ if (!(field && field.isArray))
112
+ continue;
113
+ ctx.$push = ctx.$push || {};
114
+ if (field.type instanceof ComplexType) {
115
+ keyField = field.keyField || field.type.keyField;
116
+ if (keyField) {
117
+ if (Array.isArray(value)) {
118
+ value.forEach(v => {
119
+ if (!v[keyField]) {
120
+ throw new TypeError(`You must provide a key value of ${field.type.name} for $add operation.`);
121
+ }
122
+ });
123
+ ctx.$push[pathDot + key] = { $each: value };
124
+ }
125
+ else {
126
+ if (!value[keyField]) {
127
+ throw new TypeError(`You must provide a key value of ${field.type.name} for $add operation.`);
128
+ }
129
+ ctx.$push[pathDot + key] = value;
130
+ }
131
+ }
132
+ continue;
133
+ }
134
+ ctx.$push[pathDot + key] = Array.isArray(value) ? { $each: value } : value;
135
+ }
136
+ }
137
+ _processRemove(ctx, dataType, path, input) {
138
+ let field;
139
+ let key;
140
+ let value;
141
+ const pathDot = path + (path ? '.' : '');
142
+ const keys = Object.keys(input);
143
+ let keyField;
144
+ for (key of keys) {
145
+ value = input[key];
146
+ field = dataType.fields.get(key);
147
+ if (!(field && field.isArray))
148
+ continue;
149
+ ctx.$pull = ctx.$pull || {};
150
+ if (field.type instanceof ComplexType) {
151
+ keyField = field.keyField || field.type.keyField;
152
+ if (!keyField)
153
+ continue;
154
+ ctx.$pull[pathDot + key] = { $elemMatch: { [keyField]: Array.isArray(value) ? { $in: value } : value } };
155
+ }
156
+ else {
157
+ ctx.$pull[pathDot + key] = Array.isArray(value) ? { $in: value } : value;
158
+ }
159
+ }
160
+ }
161
+ }
package/esm/index.js CHANGED
@@ -1,6 +1,7 @@
1
- export * from './mongo-adapter.js';
2
- export * from './mongo-collection-service.js';
3
- export * from './mongo-entity-service.js';
4
- export * from './mongo-nested-service.js';
5
- export * from './mongo-service.js';
6
- export * from './mongo-singleton-service.js';
1
+ export * from './adapter/mongo-adapter.js';
2
+ export * from './adapter/mongo-patch-generator.js';
3
+ export * from './services/mongo-collection-service.js';
4
+ export * from './services/mongo-entity-service.js';
5
+ export * from './services/mongo-nested-service.js';
6
+ export * from './services/mongo-service.js';
7
+ export * from './services/mongo-singleton-service.js';
@@ -1,5 +1,5 @@
1
1
  import { ResourceNotAvailableError } from '@opra/common';
2
- import { MongoAdapter } from './mongo-adapter.js';
2
+ import { MongoAdapter } from '../adapter/mongo-adapter.js';
3
3
  import { MongoEntityService } from './mongo-entity-service.js';
4
4
  /**
5
5
  * @class MongoCollectionService
@@ -1,7 +1,8 @@
1
1
  import { omit } from '@jsopen/objects';
2
2
  import { InternalServerError } from '@opra/common';
3
3
  import { isNotNullish } from 'valgen';
4
- import { MongoAdapter } from './mongo-adapter.js';
4
+ import { MongoAdapter } from '../adapter/mongo-adapter.js';
5
+ import { MongoPatchGenerator } from '../adapter/mongo-patch-generator.js';
5
6
  import { MongoService } from './mongo-service.js';
6
7
  /**
7
8
  * @class MongoEntityService
@@ -387,12 +388,20 @@ export class MongoEntityService extends MongoService {
387
388
  if (input && inputRaw) {
388
389
  throw new TypeError('You must pass one of MongoDB UpdateFilter or a partial document, not both');
389
390
  }
390
- let update = { ...inputRaw };
391
- if (input) {
392
- const inputCodec = this._getInputCodec('update');
393
- const doc = inputCodec(input);
394
- delete doc._id;
395
- update = MongoAdapter.preparePatch(doc, {}, update);
391
+ if (inputRaw)
392
+ return inputRaw;
393
+ const inputCodec = this._getInputCodec('update');
394
+ const doc = inputCodec(input);
395
+ delete doc._id;
396
+ return this._generatePatch(command, doc);
397
+ }
398
+ _generatePatch(command, doc) {
399
+ const patchGenerator = new MongoPatchGenerator();
400
+ const { update, arrayFilters } = patchGenerator.generatePatch(this.dataType, doc);
401
+ command.options = command.options || {};
402
+ if (arrayFilters) {
403
+ command.options.arrayFilters = command.options.arrayFilters || [];
404
+ command.options.arrayFilters.push(arrayFilters);
396
405
  }
397
406
  return update;
398
407
  }
@@ -1,7 +1,8 @@
1
1
  import { omit } from '@jsopen/objects';
2
2
  import { ComplexType, NotAcceptableError, ResourceNotAvailableError } from '@opra/common';
3
3
  import { isNotNullish } from 'valgen';
4
- import { MongoAdapter } from './mongo-adapter.js';
4
+ import { MongoAdapter } from '../adapter/mongo-adapter.js';
5
+ import { MongoPatchGenerator } from '../adapter/mongo-patch-generator.js';
5
6
  import { MongoService } from './mongo-service.js';
6
7
  /**
7
8
  * A class that provides methods to perform operations on an array field in a MongoDB collection.
@@ -684,9 +685,15 @@ export class MongoNestedService extends MongoService {
684
685
  const elemMatch = MongoAdapter.prepareFilter([options?.filter], { fieldPrefix: 'elem.' });
685
686
  options.arrayFilters = [elemMatch];
686
687
  }
687
- const update = MongoAdapter.preparePatch(doc, {
688
- fieldPrefix: this.fieldName + (options?.filter ? '.$[elem].' : '.$[].'),
688
+ const patchGenerator = new MongoPatchGenerator();
689
+ const { update, arrayFilters } = patchGenerator.generatePatch(this.dataType, doc, {
690
+ currentPath: this.fieldName + (options?.filter ? '.$[elem]' : '.$[]'),
689
691
  });
692
+ command.options = command.options || {};
693
+ if (arrayFilters) {
694
+ command.options.arrayFilters = command.options.arrayFilters || [];
695
+ command.options.arrayFilters.push(arrayFilters);
696
+ }
690
697
  // Count matching items, we will use this as result
691
698
  const count = await this._count({
692
699
  crud: 'read',
@@ -1,7 +1,7 @@
1
1
  import { DATATYPE_METADATA } from '@opra/common';
2
2
  import { ServiceBase } from '@opra/core';
3
3
  import { ObjectId } from 'mongodb';
4
- import { MongoAdapter } from './mongo-adapter.js';
4
+ import { MongoAdapter } from '../adapter/mongo-adapter.js';
5
5
  const transactionKey = Symbol.for('transaction');
6
6
  /**
7
7
  * Class representing a MongoDB service for interacting with a collection.
@@ -229,8 +229,10 @@ export class MongoService extends ServiceBase {
229
229
  if (validator)
230
230
  return validator;
231
231
  const options = { projection: '*' };
232
- if (operation === 'update')
232
+ if (operation === 'update') {
233
233
  options.partial = 'deep';
234
+ options.allowPatchOperators = true;
235
+ }
234
236
  const dataType = this.dataType;
235
237
  validator = dataType.generateCodec('decode', options);
236
238
  this._inputCodecs[operation] = validator;
@@ -1,7 +1,7 @@
1
1
  import { omit } from '@jsopen/objects';
2
2
  import { ResourceNotAvailableError } from '@opra/common';
3
3
  import { ObjectId } from 'mongodb';
4
- import { MongoAdapter } from './mongo-adapter.js';
4
+ import { MongoAdapter } from '../adapter/mongo-adapter.js';
5
5
  import { MongoEntityService } from './mongo-entity-service.js';
6
6
  /**
7
7
  * A class that provides access to a MongoDB collection, with support for singleton document operations.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opra/mongodb",
3
- "version": "1.2.3",
3
+ "version": "1.3.1",
4
4
  "description": "Opra MongoDB adapter package",
5
5
  "author": "Panates",
6
6
  "license": "MIT",
@@ -10,9 +10,9 @@
10
10
  "valgen": "^5.12.0"
11
11
  },
12
12
  "peerDependencies": {
13
- "@opra/common": "^1.2.3",
14
- "@opra/core": "^1.2.3",
15
- "@opra/http": "^1.2.3",
13
+ "@opra/common": "^1.3.1",
14
+ "@opra/core": "^1.3.1",
15
+ "@opra/http": "^1.3.1",
16
16
  "mongodb": ">= 6.0.0"
17
17
  },
18
18
  "type": "module",
@@ -1,17 +1,15 @@
1
1
  import { OpraFilter } from '@opra/common';
2
2
  import type { ExecutionContext } from '@opra/core';
3
3
  import mongodb, { ObjectId } from 'mongodb';
4
- import _prepareFilter from './adapter-utils/prepare-filter.js';
5
- import _prepareKeyValues from './adapter-utils/prepare-key-values.js';
6
- import _preparePatch from './adapter-utils/prepare-patch.js';
7
- import _prepareProjection from './adapter-utils/prepare-projection.js';
8
- import _prepareSort from './adapter-utils/prepare-sort.js';
4
+ import _prepareFilter from './prepare-filter.js';
5
+ import _prepareKeyValues from './prepare-key-values.js';
6
+ import _prepareProjection from './prepare-projection.js';
7
+ import _prepareSort from './prepare-sort.js';
9
8
  export declare namespace MongoAdapter {
10
9
  type AnyId = string | number | ObjectId;
11
10
  type FilterInput<T = any> = OpraFilter.Expression | mongodb.Filter<T> | string | undefined;
12
11
  const prepareFilter: typeof _prepareFilter;
13
12
  const prepareKeyValues: typeof _prepareKeyValues;
14
- const preparePatch: typeof _preparePatch;
15
13
  const prepareProjection: typeof _prepareProjection;
16
14
  const prepareSort: typeof _prepareSort;
17
15
  interface TransformedRequest {
@@ -0,0 +1,25 @@
1
+ import { ComplexType } from '@opra/common';
2
+ import type { UpdateFilter } from 'mongodb';
3
+ import type { PatchDTO } from 'ts-gems';
4
+ interface Context {
5
+ $set?: Record<string, any>;
6
+ $unset?: Record<string, any>;
7
+ $push?: Record<string, any>;
8
+ $pull?: Record<string, any>;
9
+ arrayFilters?: Record<string, any>;
10
+ }
11
+ export declare class MongoPatchGenerator {
12
+ generatePatch<T extends object>(dataType: ComplexType, doc: PatchDTO<T>, options?: MongoPatchGenerator.Options): {
13
+ update: UpdateFilter<T>;
14
+ arrayFilters?: Record<string, any>;
15
+ };
16
+ protected _processComplexType(ctx: Context, dataType: ComplexType, path: string, input: any): void;
17
+ protected _processAdd(ctx: Context, dataType: ComplexType, path: string, input: any): void;
18
+ protected _processRemove(ctx: Context, dataType: ComplexType, path: string, input: any): void;
19
+ }
20
+ export declare namespace MongoPatchGenerator {
21
+ interface Options {
22
+ currentPath?: string;
23
+ }
24
+ }
25
+ export {};
@@ -1,5 +1,5 @@
1
1
  import mongodb from 'mongodb';
2
- import type { MongoAdapter } from '../mongo-adapter.js';
2
+ import type { MongoAdapter } from './mongo-adapter';
3
3
  /**
4
4
  * Prepare the MongoDB filter based on the provided filters and options.
5
5
  *
package/types/index.d.cts CHANGED
@@ -1,6 +1,7 @@
1
- export * from './mongo-adapter.js';
2
- export * from './mongo-collection-service.js';
3
- export * from './mongo-entity-service.js';
4
- export * from './mongo-nested-service.js';
5
- export * from './mongo-service.js';
6
- export * from './mongo-singleton-service.js';
1
+ export * from './adapter/mongo-adapter.js';
2
+ export * from './adapter/mongo-patch-generator.js';
3
+ export * from './services/mongo-collection-service.js';
4
+ export * from './services/mongo-entity-service.js';
5
+ export * from './services/mongo-nested-service.js';
6
+ export * from './services/mongo-service.js';
7
+ export * from './services/mongo-singleton-service.js';
package/types/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
- export * from './mongo-adapter.js';
2
- export * from './mongo-collection-service.js';
3
- export * from './mongo-entity-service.js';
4
- export * from './mongo-nested-service.js';
5
- export * from './mongo-service.js';
6
- export * from './mongo-singleton-service.js';
1
+ export * from './adapter/mongo-adapter.js';
2
+ export * from './adapter/mongo-patch-generator.js';
3
+ export * from './services/mongo-collection-service.js';
4
+ export * from './services/mongo-entity-service.js';
5
+ export * from './services/mongo-nested-service.js';
6
+ export * from './services/mongo-service.js';
7
+ export * from './services/mongo-singleton-service.js';
@@ -1,6 +1,6 @@
1
1
  import mongodb, { type UpdateFilter } from 'mongodb';
2
2
  import type { PartialDTO, PatchDTO, RequiredSome, Type } from 'ts-gems';
3
- import { MongoAdapter } from './mongo-adapter.js';
3
+ import { MongoAdapter } from '../adapter/mongo-adapter.js';
4
4
  import { MongoEntityService } from './mongo-entity-service.js';
5
5
  /**
6
6
  *
@@ -187,6 +187,7 @@ export declare class MongoEntityService<T extends mongodb.Document> extends Mong
187
187
  */
188
188
  protected _replace(command: MongoEntityService.ReplaceCommand<T>): Promise<PartialDTO<T> | undefined>;
189
189
  protected _prepareUpdate(command: MongoEntityService.UpdateOneCommand<T> | MongoEntityService.UpdateManyCommand<T>): UpdateFilter<T>;
190
+ protected _generatePatch(command: MongoEntityService.UpdateOneCommand<T> | MongoEntityService.UpdateManyCommand<T>, doc: any): mongodb.UpdateFilter<T>;
190
191
  protected _executeCommand(command: MongoEntityService.CommandInfo, commandFn: () => any): Promise<any>;
191
192
  protected _beforeCreate(command: MongoEntityService.CreateCommand<T>): Promise<void>;
192
193
  protected _beforeDelete(command: MongoEntityService.DeleteCommand<T>): Promise<void>;
@@ -1,8 +1,8 @@
1
1
  import { ComplexType } from '@opra/common';
2
2
  import mongodb from 'mongodb';
3
3
  import type { DTO, PartialDTO, PatchDTO, RequiredSome, StrictOmit, Type } from 'ts-gems';
4
- import { MongoAdapter } from './mongo-adapter.js';
5
- import type { MongoEntityService } from './mongo-entity-service';
4
+ import { MongoAdapter } from '../adapter/mongo-adapter.js';
5
+ import type { MongoEntityService } from './mongo-entity-service.js';
6
6
  import { MongoService } from './mongo-service.js';
7
7
  /**
8
8
  *
@@ -3,7 +3,7 @@ import { ExecutionContext, ServiceBase } from '@opra/core';
3
3
  import mongodb, { ClientSession, type Document, type TransactionOptions } from 'mongodb';
4
4
  import type { Nullish, StrictOmit, Type } from 'ts-gems';
5
5
  import type { IsObject } from 'valgen';
6
- import { MongoAdapter } from './mongo-adapter.js';
6
+ import { MongoAdapter } from '../adapter/mongo-adapter.js';
7
7
  /**
8
8
  * The namespace for the MongoService.
9
9
  *
@@ -1,6 +1,6 @@
1
1
  import mongodb, { type UpdateFilter } from 'mongodb';
2
2
  import type { PartialDTO, PatchDTO, RequiredSome, Type } from 'ts-gems';
3
- import { MongoAdapter } from './mongo-adapter.js';
3
+ import { MongoAdapter } from '../adapter/mongo-adapter.js';
4
4
  import { MongoEntityService } from './mongo-entity-service.js';
5
5
  /**
6
6
  *
@@ -1,37 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.default = preparePatch;
4
- const objects_1 = require("@jsopen/objects");
5
- function preparePatch(doc, options, target) {
6
- target = target || {};
7
- _preparePatch(doc, target, '', options);
8
- target.$set = target.$set || {};
9
- return target;
10
- }
11
- function _preparePatch(src, trg, path, options) {
12
- let f;
13
- let key;
14
- let field;
15
- trg = trg || {};
16
- const fieldPrefix = options?.fieldPrefix;
17
- for (const [k, v] of Object.entries(src)) {
18
- f = k.startsWith('*') ? k.substring(1) : k;
19
- key = path ? path + '.' + f : f;
20
- field = (fieldPrefix ? fieldPrefix : '') + key;
21
- if (v == null) {
22
- trg.$unset = trg.$unset || {};
23
- trg.$unset[field] = '';
24
- continue;
25
- }
26
- if (v && typeof v === 'object' && !(0, objects_1.isBuiltIn)(v)) {
27
- // If field name starts with "*", do "replace" operation except "merge"
28
- if (!k.startsWith('*')) {
29
- _preparePatch(v, trg, key);
30
- continue;
31
- }
32
- }
33
- trg.$set = trg.$set || {};
34
- trg.$set[field] = v;
35
- }
36
- return trg;
37
- }
@@ -1,34 +0,0 @@
1
- import { isBuiltIn } from '@jsopen/objects';
2
- export default function preparePatch(doc, options, target) {
3
- target = target || {};
4
- _preparePatch(doc, target, '', options);
5
- target.$set = target.$set || {};
6
- return target;
7
- }
8
- function _preparePatch(src, trg, path, options) {
9
- let f;
10
- let key;
11
- let field;
12
- trg = trg || {};
13
- const fieldPrefix = options?.fieldPrefix;
14
- for (const [k, v] of Object.entries(src)) {
15
- f = k.startsWith('*') ? k.substring(1) : k;
16
- key = path ? path + '.' + f : f;
17
- field = (fieldPrefix ? fieldPrefix : '') + key;
18
- if (v == null) {
19
- trg.$unset = trg.$unset || {};
20
- trg.$unset[field] = '';
21
- continue;
22
- }
23
- if (v && typeof v === 'object' && !isBuiltIn(v)) {
24
- // If field name starts with "*", do "replace" operation except "merge"
25
- if (!k.startsWith('*')) {
26
- _preparePatch(v, trg, key);
27
- continue;
28
- }
29
- }
30
- trg.$set = trg.$set || {};
31
- trg.$set[field] = v;
32
- }
33
- return trg;
34
- }
@@ -1,3 +0,0 @@
1
- export default function preparePatch(doc: any, options?: {
2
- fieldPrefix?: string;
3
- }, target?: any): any;
File without changes
File without changes