@opra/mongodb 0.31.13 → 0.32.0
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 +65 -10
- package/cjs/adapter-utils/prepare-patch.js +1 -0
- package/cjs/adapter-utils/prepare-projection.js +40 -40
- package/cjs/index.js +2 -0
- package/cjs/mongo-array-service.js +344 -0
- package/cjs/mongo-collection-service.js +243 -81
- package/cjs/mongo-service.js +119 -13
- package/cjs/mongo-singleton-service.js +61 -48
- package/cjs/types.js +2 -0
- package/esm/adapter-utils/prepare-filter.js +65 -10
- package/esm/adapter-utils/prepare-patch.js +1 -0
- package/esm/adapter-utils/prepare-projection.js +38 -37
- package/esm/index.js +2 -0
- package/esm/mongo-array-service.js +339 -0
- package/esm/mongo-collection-service.js +243 -81
- package/esm/mongo-service.js +119 -13
- package/esm/mongo-singleton-service.js +61 -47
- package/esm/types.js +1 -0
- package/package.json +4 -4
- package/types/adapter-utils/prepare-filter.d.ts +10 -2
- package/types/adapter-utils/prepare-projection.d.ts +7 -3
- package/types/index.d.ts +2 -0
- package/types/mongo-array-service.d.ts +193 -0
- package/types/mongo-collection-service.d.ts +120 -18
- package/types/mongo-service.d.ts +91 -11
- package/types/mongo-singleton-service.d.ts +42 -7
- package/types/types.d.ts +2 -0
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.MongoSingletonService = void 0;
|
|
4
|
-
const tslib_1 = require("tslib");
|
|
5
4
|
const mongodb_1 = require("mongodb");
|
|
6
5
|
const common_1 = require("@opra/common");
|
|
7
|
-
const OpraCommon = tslib_1.__importStar(require("@opra/common"));
|
|
8
6
|
const mongo_adapter_js_1 = require("./mongo-adapter.js");
|
|
9
7
|
const mongo_service_js_1 = require("./mongo-service.js");
|
|
10
8
|
/**
|
|
@@ -14,75 +12,90 @@ const mongo_service_js_1 = require("./mongo-service.js");
|
|
|
14
12
|
class MongoSingletonService extends mongo_service_js_1.MongoService {
|
|
15
13
|
constructor(dataType, options) {
|
|
16
14
|
super(dataType, options);
|
|
15
|
+
this.collectionKey = options?.collectionKey || '_id';
|
|
17
16
|
this._id = options?._id || new mongodb_1.ObjectId('655608925cad472b75fc6485');
|
|
18
17
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Fetches the Document. Throws error undefined if not found.
|
|
20
|
+
*
|
|
21
|
+
* @param options
|
|
22
|
+
*/
|
|
23
|
+
async assert(options) {
|
|
24
|
+
const out = await this.get(options);
|
|
25
|
+
if (!out)
|
|
26
|
+
throw new common_1.ResourceNotFoundError(this.resourceName || this.getCollectionName());
|
|
27
|
+
return out;
|
|
24
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Inserts the document into MongoDB
|
|
31
|
+
*
|
|
32
|
+
* @param doc
|
|
33
|
+
* @param options
|
|
34
|
+
*/
|
|
25
35
|
async create(doc, options) {
|
|
26
|
-
const r = await this.
|
|
27
|
-
if (r.insertedId)
|
|
28
|
-
|
|
29
|
-
if (out)
|
|
30
|
-
return out;
|
|
31
|
-
}
|
|
36
|
+
const r = await this.__insertOne({ ...doc, _id: this._id }, options);
|
|
37
|
+
if (r.insertedId)
|
|
38
|
+
return doc;
|
|
32
39
|
/* istanbul ignore next */
|
|
33
40
|
throw new Error('Unknown error while creating document');
|
|
34
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Delete the document from a collection
|
|
44
|
+
*
|
|
45
|
+
* @param options
|
|
46
|
+
*/
|
|
35
47
|
async delete(options) {
|
|
36
|
-
const filter = { _id: this._id };
|
|
37
|
-
const
|
|
38
|
-
if (optionsFilter)
|
|
39
|
-
filter.$and = [...(Array.isArray(optionsFilter) ? optionsFilter : [optionsFilter])];
|
|
40
|
-
const r = await this._rawDeleteOne(filter, options);
|
|
48
|
+
const filter = mongo_adapter_js_1.MongoAdapter.prepareFilter([{ _id: this._id }, options?.filter]);
|
|
49
|
+
const r = await this.__deleteOne(filter, options);
|
|
41
50
|
return r.deletedCount;
|
|
42
51
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
52
|
+
/**
|
|
53
|
+
* Checks if the document exists.
|
|
54
|
+
* Returns true if document exists, false otherwise
|
|
55
|
+
*
|
|
56
|
+
*/
|
|
57
|
+
async exists() {
|
|
58
|
+
return !!(await this.get({ pick: ['_id'] }));
|
|
48
59
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
60
|
+
/**
|
|
61
|
+
* Fetches the document
|
|
62
|
+
*
|
|
63
|
+
* @param options
|
|
64
|
+
*/
|
|
65
|
+
async get(options) {
|
|
66
|
+
const filter = mongo_adapter_js_1.MongoAdapter.prepareFilter([{ _id: this._id }, options?.filter]);
|
|
54
67
|
const mongoOptions = {
|
|
55
68
|
...options,
|
|
56
|
-
projection: mongo_adapter_js_1.MongoAdapter.prepareProjection(this.
|
|
69
|
+
projection: mongo_adapter_js_1.MongoAdapter.prepareProjection(this.getDataType(), options),
|
|
57
70
|
sort: undefined,
|
|
58
71
|
skip: undefined,
|
|
59
72
|
limit: undefined
|
|
60
73
|
};
|
|
61
|
-
|
|
62
|
-
if (out) {
|
|
63
|
-
if (this.transformData)
|
|
64
|
-
return this.transformData(out);
|
|
65
|
-
return out;
|
|
66
|
-
}
|
|
74
|
+
return await this.__findOne(filter, mongoOptions);
|
|
67
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* Updates a single document.
|
|
78
|
+
* Returns updated document
|
|
79
|
+
*
|
|
80
|
+
* @param doc
|
|
81
|
+
* @param options
|
|
82
|
+
*/
|
|
68
83
|
async update(doc, options) {
|
|
69
|
-
// Prevent
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
84
|
+
// Prevent updating _id field
|
|
85
|
+
delete doc._id;
|
|
86
|
+
const filter = mongo_adapter_js_1.MongoAdapter.prepareFilter([
|
|
87
|
+
{ _id: this._id },
|
|
88
|
+
options?.filter
|
|
89
|
+
]);
|
|
74
90
|
const patch = mongo_adapter_js_1.MongoAdapter.preparePatch(doc);
|
|
75
91
|
const mongoOptions = {
|
|
76
92
|
...options,
|
|
77
|
-
|
|
93
|
+
includeResultMetadata: false,
|
|
94
|
+
upsert: undefined,
|
|
95
|
+
projection: mongo_adapter_js_1.MongoAdapter.prepareProjection(this.getDataType(), options),
|
|
78
96
|
};
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
if (optionsFilter)
|
|
82
|
-
filter.$and = [...(Array.isArray(optionsFilter) ? optionsFilter : [optionsFilter])];
|
|
83
|
-
const r = await this._rawUpdateOne(filter, patch, mongoOptions);
|
|
84
|
-
if (r.matchedCount)
|
|
85
|
-
return await this.get(options);
|
|
97
|
+
const out = await this.__findOneAndUpdate(filter, patch, mongoOptions);
|
|
98
|
+
return out || undefined;
|
|
86
99
|
}
|
|
87
100
|
}
|
|
88
101
|
exports.MongoSingletonService = MongoSingletonService;
|
package/cjs/types.js
ADDED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import '@opra/core';
|
|
2
1
|
import { OpraFilter } from '@opra/common';
|
|
3
2
|
const opMap = {
|
|
4
3
|
'=': '$eq',
|
|
@@ -10,15 +9,71 @@ const opMap = {
|
|
|
10
9
|
'in': '$in',
|
|
11
10
|
'!in': '$nin',
|
|
12
11
|
};
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
return
|
|
12
|
+
/**
|
|
13
|
+
* Convert filter expressions to MongoDB filter objects.
|
|
14
|
+
* This method also merges multiple expressions into one single filter object
|
|
15
|
+
* @param filters
|
|
16
|
+
*/
|
|
17
|
+
export default function prepareFilter(filters, options) {
|
|
18
|
+
const filtersArray = Array.isArray(filters) ? filters : [filters];
|
|
19
|
+
if (!filtersArray.length)
|
|
20
|
+
return {};
|
|
21
|
+
let i = 0;
|
|
22
|
+
const out = {};
|
|
23
|
+
for (const filter of filtersArray) {
|
|
24
|
+
if (!filter)
|
|
25
|
+
continue;
|
|
26
|
+
let x;
|
|
27
|
+
if (typeof filter === 'string')
|
|
28
|
+
x = prepareFilterAst(OpraFilter.parse(filter));
|
|
29
|
+
else if (filter instanceof OpraFilter.Expression)
|
|
30
|
+
x = prepareFilterAst(filter);
|
|
31
|
+
else
|
|
32
|
+
x = filter;
|
|
33
|
+
if (Array.isArray(x))
|
|
34
|
+
x = { $and: out };
|
|
35
|
+
// Merge $and arrays
|
|
36
|
+
if (x.$and) {
|
|
37
|
+
out.$and = out.$and || [];
|
|
38
|
+
out.$and.push(...x.$and);
|
|
39
|
+
delete x.$and;
|
|
40
|
+
}
|
|
41
|
+
// Merge $or arrays
|
|
42
|
+
if (x.$or) {
|
|
43
|
+
out.$or = out.$or || [];
|
|
44
|
+
out.$or.push(...x.$or);
|
|
45
|
+
delete x.$or;
|
|
46
|
+
}
|
|
47
|
+
for (const k of Object.keys(x)) {
|
|
48
|
+
// If result object has filter field we convert it to $and
|
|
49
|
+
if (out[k]) {
|
|
50
|
+
out.$and = out.$and || [];
|
|
51
|
+
out.$and.push({ [k]: out[k] });
|
|
52
|
+
out.$and.push({ [k]: x[k] });
|
|
53
|
+
delete out[k];
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
out[k] = x[k];
|
|
57
|
+
}
|
|
58
|
+
i++;
|
|
59
|
+
}
|
|
60
|
+
return i
|
|
61
|
+
? (options?.fieldPrefix ? addPrefix(out, options.fieldPrefix) : out)
|
|
62
|
+
: undefined;
|
|
63
|
+
}
|
|
64
|
+
function addPrefix(source, prefix) {
|
|
65
|
+
if (typeof source !== 'object')
|
|
66
|
+
return source;
|
|
67
|
+
if (Array.isArray(source))
|
|
68
|
+
return source.map(v => addPrefix(v, prefix));
|
|
69
|
+
const out = {};
|
|
70
|
+
for (const [k, v] of Object.entries(source)) {
|
|
71
|
+
if (k.startsWith('$'))
|
|
72
|
+
out[k] = addPrefix(v, prefix);
|
|
73
|
+
else
|
|
74
|
+
out[prefix + k] = addPrefix(v, prefix);
|
|
75
|
+
}
|
|
76
|
+
return out;
|
|
22
77
|
}
|
|
23
78
|
function prepareFilterAst(ast, negative) {
|
|
24
79
|
if (!ast)
|
|
@@ -1,46 +1,47 @@
|
|
|
1
1
|
import { ComplexType, pathToObjectTree } from '@opra/common';
|
|
2
|
-
export default function prepareProjection(dataType,
|
|
2
|
+
export default function prepareProjection(dataType, options) {
|
|
3
3
|
const out = {};
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
4
|
+
const pick = options?.pick && pathToObjectTree(options.pick);
|
|
5
|
+
const include = options?.include && pathToObjectTree(options.include);
|
|
6
|
+
const omit = options?.omit && pathToObjectTree(options.omit);
|
|
7
|
+
// const exclusionProjection = !pick && !!omit;
|
|
8
|
+
_prepareProjection(dataType, out, {
|
|
9
|
+
pickActivated: !!pick,
|
|
10
|
+
pick,
|
|
11
|
+
include,
|
|
12
|
+
omit
|
|
13
|
+
});
|
|
13
14
|
return Object.keys(out).length ? out : undefined;
|
|
14
15
|
}
|
|
15
|
-
export function
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
export function _prepareProjection(dataType, target,
|
|
17
|
+
// exclusionProjection: boolean,
|
|
18
|
+
options) {
|
|
19
|
+
// const defaultFields = options?.defaultFields ?? !options?.pick;
|
|
20
|
+
const optionsOmit = options?.omit;
|
|
21
|
+
const optionsPick = options?.pick;
|
|
22
|
+
const optionsInclude = options?.include;
|
|
23
|
+
const pickActivated = options?.pickActivated;
|
|
18
24
|
for (const [k, f] of dataType.fields.entries()) {
|
|
19
|
-
|
|
25
|
+
const fieldOmit = optionsOmit?.[k];
|
|
26
|
+
const fieldInclude = optionsInclude?.[k];
|
|
27
|
+
const fieldPick = optionsPick?.[k];
|
|
28
|
+
if (fieldOmit === true ||
|
|
29
|
+
!((pickActivated && fieldPick) ||
|
|
30
|
+
(!pickActivated && (!f.exclusive || fieldInclude))))
|
|
31
|
+
continue;
|
|
32
|
+
if (f.type instanceof ComplexType &&
|
|
33
|
+
(typeof fieldInclude === 'object' ||
|
|
34
|
+
typeof fieldPick === 'object' ||
|
|
35
|
+
typeof fieldOmit === 'object')) {
|
|
36
|
+
target[k] = {};
|
|
37
|
+
_prepareProjection(f.type, target[k], {
|
|
38
|
+
pickActivated: fieldPick != null && fieldPick !== true,
|
|
39
|
+
include: typeof fieldInclude === 'object' ? fieldInclude : undefined,
|
|
40
|
+
pick: typeof fieldPick === 'object' ? fieldPick : undefined,
|
|
41
|
+
omit: typeof fieldOmit === 'object' ? fieldOmit : undefined
|
|
42
|
+
});
|
|
20
43
|
continue;
|
|
21
|
-
n = (defaultFields && !f.exclusive) ||
|
|
22
|
-
pick === true || pick?.[k] || include === true || include?.[k];
|
|
23
|
-
if (n) {
|
|
24
|
-
if (f.type instanceof ComplexType && (typeof n === 'object' || typeof omit?.[k] === 'object')) {
|
|
25
|
-
target[k] = {};
|
|
26
|
-
_prepareInclusionProjection(f.type, target[k], pick?.[k] || include?.[k], undefined, omit?.[k], defaultFields);
|
|
27
|
-
continue;
|
|
28
|
-
}
|
|
29
|
-
target[k] = 1;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
export function _prepareExclusionProjection(dataType, target, omit, omitExclusiveFields) {
|
|
34
|
-
let n;
|
|
35
|
-
for (const [k, f] of dataType.fields.entries()) {
|
|
36
|
-
n = omit?.[k] || (omitExclusiveFields && f.exclusive);
|
|
37
|
-
if (n) {
|
|
38
|
-
if (f.type instanceof ComplexType && typeof n === 'object') {
|
|
39
|
-
target[k] = {};
|
|
40
|
-
_prepareExclusionProjection(f.type, target[k], omit?.[k], omitExclusiveFields);
|
|
41
|
-
continue;
|
|
42
|
-
}
|
|
43
|
-
target[k] = 0;
|
|
44
44
|
}
|
|
45
|
+
target[k] = 1;
|
|
45
46
|
}
|
|
46
47
|
}
|
package/esm/index.js
CHANGED
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
import omit from 'lodash.omit';
|
|
2
|
+
import { ObjectId } from 'mongodb';
|
|
3
|
+
import { ComplexType, NotAcceptableError, ResourceNotFoundError } from '@opra/common';
|
|
4
|
+
import { MongoAdapter } from './mongo-adapter.js';
|
|
5
|
+
import { MongoService } from './mongo-service.js';
|
|
6
|
+
/**
|
|
7
|
+
*
|
|
8
|
+
* @class MongoArrayService
|
|
9
|
+
*/
|
|
10
|
+
export class MongoArrayService extends MongoService {
|
|
11
|
+
constructor(dataType, fieldName, options) {
|
|
12
|
+
super(dataType, options);
|
|
13
|
+
this._encoders = {};
|
|
14
|
+
this.fieldName = fieldName;
|
|
15
|
+
this.defaultLimit = options?.defaultLimit || 10;
|
|
16
|
+
this.collectionKey = options?.collectionKey || '_id';
|
|
17
|
+
this.arrayKey = options?.arrayKey || '_id';
|
|
18
|
+
}
|
|
19
|
+
getArrayDataType() {
|
|
20
|
+
const t = this.getDataType()
|
|
21
|
+
.getField(this.fieldName).type;
|
|
22
|
+
if (!(t instanceof ComplexType))
|
|
23
|
+
throw new NotAcceptableError(`Data type "${t.name}" is not a ComplexType`);
|
|
24
|
+
return t;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Checks if array item exists. Throws error if not.
|
|
28
|
+
*
|
|
29
|
+
* @param parentId
|
|
30
|
+
* @param id
|
|
31
|
+
*/
|
|
32
|
+
async assert(parentId, id) {
|
|
33
|
+
if (!(await this.exists(parentId, id)))
|
|
34
|
+
throw new ResourceNotFoundError((this.resourceName || this.getCollectionName()) + '.' + this.arrayKey, parentId + '/' + id);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Adds a single item into array field.
|
|
38
|
+
*
|
|
39
|
+
* @param parentId
|
|
40
|
+
* @param input
|
|
41
|
+
* @param options
|
|
42
|
+
*/
|
|
43
|
+
async create(parentId, input, options) {
|
|
44
|
+
const encode = this._getEncoder('create');
|
|
45
|
+
const doc = encode(input);
|
|
46
|
+
doc[this.arrayKey] = doc[this.arrayKey] || this._generateId();
|
|
47
|
+
const docFilter = MongoAdapter.prepareKeyValues(parentId, [this.collectionKey]);
|
|
48
|
+
const r = await this.__updateOne(docFilter, {
|
|
49
|
+
$push: { [this.fieldName]: doc }
|
|
50
|
+
}, options);
|
|
51
|
+
if (r.modifiedCount)
|
|
52
|
+
try {
|
|
53
|
+
return this.get(parentId, doc[this.arrayKey], { ...options, filter: undefined, skip: undefined });
|
|
54
|
+
}
|
|
55
|
+
catch (e) {
|
|
56
|
+
Error.captureStackTrace(e);
|
|
57
|
+
throw e;
|
|
58
|
+
}
|
|
59
|
+
throw new ResourceNotFoundError(this.resourceName || this.getCollectionName(), parentId);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Gets the number of array items matching the filter.
|
|
63
|
+
* @param parentId
|
|
64
|
+
* @param options
|
|
65
|
+
*/
|
|
66
|
+
async count(parentId, options) {
|
|
67
|
+
const matchFilter = MongoAdapter.prepareKeyValues(parentId, [this.collectionKey]);
|
|
68
|
+
const stages = [
|
|
69
|
+
{ $match: matchFilter },
|
|
70
|
+
{ $unwind: { path: "$" + this.fieldName } },
|
|
71
|
+
{ $replaceRoot: { newRoot: "$" + this.fieldName } }
|
|
72
|
+
];
|
|
73
|
+
if (options?.filter) {
|
|
74
|
+
const optionsFilter = MongoAdapter.prepareFilter(options.filter);
|
|
75
|
+
stages.push({ $match: optionsFilter });
|
|
76
|
+
}
|
|
77
|
+
stages.push({ $count: '*' });
|
|
78
|
+
const r = await this.__aggregate(stages, options);
|
|
79
|
+
try {
|
|
80
|
+
const n = await r.next();
|
|
81
|
+
return n?.['*'] || 0;
|
|
82
|
+
}
|
|
83
|
+
finally {
|
|
84
|
+
await r.close();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Removes one item from an array field
|
|
89
|
+
*
|
|
90
|
+
* @param parentId
|
|
91
|
+
* @param id
|
|
92
|
+
* @param options
|
|
93
|
+
*/
|
|
94
|
+
async delete(parentId, id, options) {
|
|
95
|
+
const matchFilter = MongoAdapter.prepareKeyValues(parentId, [this.collectionKey]);
|
|
96
|
+
const pullFilter = MongoAdapter.prepareFilter([
|
|
97
|
+
MongoAdapter.prepareKeyValues(id, [this.arrayKey]),
|
|
98
|
+
options?.filter
|
|
99
|
+
]);
|
|
100
|
+
const r = await this.__updateOne(matchFilter, {
|
|
101
|
+
$pull: { [this.fieldName]: pullFilter }
|
|
102
|
+
}, options);
|
|
103
|
+
return r.modifiedCount ? 1 : 0;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Removes multiple items from an array field
|
|
107
|
+
*
|
|
108
|
+
* @param parentId
|
|
109
|
+
* @param options
|
|
110
|
+
*/
|
|
111
|
+
async deleteMany(parentId, options) {
|
|
112
|
+
const docFilter = MongoAdapter.prepareKeyValues(parentId, [this.collectionKey]);
|
|
113
|
+
// Count matching items, we will use this as result
|
|
114
|
+
const matchCount = await this.count(parentId, options);
|
|
115
|
+
const pullFilter = MongoAdapter.prepareFilter(options?.filter) || {};
|
|
116
|
+
const r = await this.__updateOne(docFilter, {
|
|
117
|
+
$pull: { [this.fieldName]: pullFilter }
|
|
118
|
+
}, options);
|
|
119
|
+
if (r.modifiedCount)
|
|
120
|
+
return matchCount;
|
|
121
|
+
return 0;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Returns true if item exists, false otherwise
|
|
125
|
+
*
|
|
126
|
+
* @param parentId
|
|
127
|
+
* @param id
|
|
128
|
+
*/
|
|
129
|
+
async exists(parentId, id) {
|
|
130
|
+
return !!(await this.findById(parentId, id, { pick: ['_id'] }));
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Fetches the first item in an array field that matches by id.
|
|
134
|
+
*
|
|
135
|
+
* @param parentId
|
|
136
|
+
* @param id
|
|
137
|
+
* @param options
|
|
138
|
+
*/
|
|
139
|
+
async findById(parentId, id, options) {
|
|
140
|
+
let filter = MongoAdapter.prepareKeyValues(id, [this.arrayKey]);
|
|
141
|
+
if (options?.filter)
|
|
142
|
+
filter = MongoAdapter.prepareFilter([filter, options?.filter]);
|
|
143
|
+
return await this.findOne(parentId, { ...options, filter });
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Fetches the first item in an array field that matches the filter. Returns undefined if not found.
|
|
147
|
+
*
|
|
148
|
+
* @param parentId
|
|
149
|
+
* @param options
|
|
150
|
+
*/
|
|
151
|
+
async findOne(parentId, options) {
|
|
152
|
+
const rows = await this.findMany(parentId, {
|
|
153
|
+
...options,
|
|
154
|
+
limit: 1
|
|
155
|
+
});
|
|
156
|
+
return rows?.[0];
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Fetches all items in an array field that matches the filter
|
|
160
|
+
*
|
|
161
|
+
* @param parentId
|
|
162
|
+
* @param options
|
|
163
|
+
*/
|
|
164
|
+
async findMany(parentId, options) {
|
|
165
|
+
const matchFilter = MongoAdapter.prepareKeyValues(parentId, [this.collectionKey]);
|
|
166
|
+
const mongoOptions = {
|
|
167
|
+
...omit(options, ['pick', 'include', 'omit', 'sort', 'skip', 'limit', 'filter', 'count'])
|
|
168
|
+
};
|
|
169
|
+
const limit = options?.limit || this.defaultLimit;
|
|
170
|
+
const stages = [
|
|
171
|
+
{ $match: matchFilter },
|
|
172
|
+
{ $unwind: { path: "$" + this.fieldName } },
|
|
173
|
+
{ $replaceRoot: { newRoot: "$" + this.fieldName } }
|
|
174
|
+
];
|
|
175
|
+
let dataStages = stages;
|
|
176
|
+
if (options?.count) {
|
|
177
|
+
dataStages = [];
|
|
178
|
+
stages.push({
|
|
179
|
+
$facet: {
|
|
180
|
+
data: dataStages,
|
|
181
|
+
count: [{ $count: 'totalMatches' }]
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
if (options?.filter) {
|
|
186
|
+
const optionsFilter = MongoAdapter.prepareFilter(options?.filter);
|
|
187
|
+
dataStages.push({ $match: optionsFilter });
|
|
188
|
+
}
|
|
189
|
+
if (options?.skip)
|
|
190
|
+
dataStages.push({ $skip: options.skip });
|
|
191
|
+
if (options?.sort) {
|
|
192
|
+
const sort = MongoAdapter.prepareSort(options.sort);
|
|
193
|
+
if (sort)
|
|
194
|
+
dataStages.push({ $sort: sort });
|
|
195
|
+
}
|
|
196
|
+
dataStages.push({ $limit: limit });
|
|
197
|
+
const dataType = this.getArrayDataType();
|
|
198
|
+
const projection = MongoAdapter.prepareProjection(dataType, options);
|
|
199
|
+
if (projection)
|
|
200
|
+
dataStages.push({ $project: projection });
|
|
201
|
+
const cursor = await this.__aggregate(stages, {
|
|
202
|
+
...mongoOptions
|
|
203
|
+
});
|
|
204
|
+
try {
|
|
205
|
+
if (options?.count) {
|
|
206
|
+
const facetResult = await cursor.toArray();
|
|
207
|
+
this.context.response.totalMatches = facetResult[0].count[0].totalMatches || 0;
|
|
208
|
+
return facetResult[0].data;
|
|
209
|
+
}
|
|
210
|
+
else
|
|
211
|
+
return await cursor.toArray();
|
|
212
|
+
}
|
|
213
|
+
finally {
|
|
214
|
+
if (!cursor.closed)
|
|
215
|
+
await cursor.close();
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Fetches the first item in an array field that matches the item id. Throws error undefined if not found.
|
|
220
|
+
*
|
|
221
|
+
* @param parentId
|
|
222
|
+
* @param id
|
|
223
|
+
* @param options
|
|
224
|
+
*/
|
|
225
|
+
async get(parentId, id, options) {
|
|
226
|
+
const out = await this.findById(parentId, id, options);
|
|
227
|
+
if (!out)
|
|
228
|
+
throw new ResourceNotFoundError((this.resourceName || this.getCollectionName()) + '.' + this.arrayKey, parentId + '/' + id);
|
|
229
|
+
return out;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Update a single item in array field
|
|
233
|
+
*
|
|
234
|
+
* @param parentId
|
|
235
|
+
* @param id
|
|
236
|
+
* @param input
|
|
237
|
+
* @param options
|
|
238
|
+
*/
|
|
239
|
+
async update(parentId, id, input, options) {
|
|
240
|
+
await this.updateOnly(parentId, id, input, options);
|
|
241
|
+
try {
|
|
242
|
+
return await this.findById(parentId, id, options);
|
|
243
|
+
}
|
|
244
|
+
catch (e) {
|
|
245
|
+
Error.captureStackTrace(e);
|
|
246
|
+
throw e;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Update a single item in array field
|
|
251
|
+
* Returns how many master documents updated (not array items)
|
|
252
|
+
*
|
|
253
|
+
* @param parentId
|
|
254
|
+
* @param id
|
|
255
|
+
* @param doc
|
|
256
|
+
* @param options
|
|
257
|
+
*/
|
|
258
|
+
async updateOnly(parentId, id, doc, options) {
|
|
259
|
+
let filter = MongoAdapter.prepareKeyValues(id, [this.arrayKey]);
|
|
260
|
+
if (options?.filter)
|
|
261
|
+
filter = MongoAdapter.prepareFilter([filter, options?.filter]);
|
|
262
|
+
return await this.updateMany(parentId, doc, { ...options, filter });
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Update multiple items in array field, returns how many master documents updated (not array items)
|
|
266
|
+
*
|
|
267
|
+
* @param parentId
|
|
268
|
+
* @param input
|
|
269
|
+
* @param options
|
|
270
|
+
*/
|
|
271
|
+
async updateMany(parentId, input, options) {
|
|
272
|
+
const encode = this._getEncoder('update');
|
|
273
|
+
const doc = encode(input);
|
|
274
|
+
const matchFilter = MongoAdapter.prepareFilter([
|
|
275
|
+
MongoAdapter.prepareKeyValues(parentId, [this.collectionKey]),
|
|
276
|
+
{ [this.fieldName]: { $exists: true } }
|
|
277
|
+
]);
|
|
278
|
+
if (options?.filter) {
|
|
279
|
+
const elemMatch = MongoAdapter.prepareFilter(options?.filter, { fieldPrefix: 'elem.' });
|
|
280
|
+
options = options || {};
|
|
281
|
+
options.arrayFilters = [elemMatch];
|
|
282
|
+
}
|
|
283
|
+
const update = MongoAdapter.preparePatch(doc, {
|
|
284
|
+
fieldPrefix: this.fieldName + (options?.filter ? '.$[elem].' : '.$[].')
|
|
285
|
+
});
|
|
286
|
+
const r = await this.__updateOne(matchFilter, update, options);
|
|
287
|
+
return r.modifiedCount;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Update multiple items in array field and returns number of updated array items
|
|
291
|
+
*
|
|
292
|
+
* @param parentId
|
|
293
|
+
* @param doc
|
|
294
|
+
* @param options
|
|
295
|
+
*/
|
|
296
|
+
async updateManyReturnCount(parentId, doc, options) {
|
|
297
|
+
const r = await this.updateMany(parentId, doc, options);
|
|
298
|
+
return r
|
|
299
|
+
// Count matching items that fits filter criteria
|
|
300
|
+
? await this.count(parentId, options)
|
|
301
|
+
: 0;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Generates Id value
|
|
305
|
+
*
|
|
306
|
+
* @protected
|
|
307
|
+
*/
|
|
308
|
+
_generateId() {
|
|
309
|
+
return new ObjectId();
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Generates a new Validator for encoding or returns from cache if already generated before
|
|
313
|
+
* @param operation
|
|
314
|
+
* @protected
|
|
315
|
+
*/
|
|
316
|
+
_getEncoder(operation) {
|
|
317
|
+
let encoder = this._encoders[operation];
|
|
318
|
+
if (encoder)
|
|
319
|
+
return encoder;
|
|
320
|
+
encoder = this._generateEncoder(operation);
|
|
321
|
+
this._encoders[operation] = encoder;
|
|
322
|
+
return encoder;
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Generates a new Valgen Validator for encode operation
|
|
326
|
+
*
|
|
327
|
+
* @param operation
|
|
328
|
+
* @protected
|
|
329
|
+
*/
|
|
330
|
+
_generateEncoder(operation) {
|
|
331
|
+
const dataType = this.getArrayDataType();
|
|
332
|
+
const options = {};
|
|
333
|
+
if (operation === 'update') {
|
|
334
|
+
options.omit = ['_id'];
|
|
335
|
+
options.partial = true;
|
|
336
|
+
}
|
|
337
|
+
return dataType.generateCodec('encode', options);
|
|
338
|
+
}
|
|
339
|
+
}
|