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