@opra/mongodb 0.17.1 → 0.17.2
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/mongo-collection-resource.js +7 -11
- package/cjs/mongo-entity-service.js +45 -22
- package/cjs/mongo-singleton-resource.js +2 -8
- package/cjs/transform-filter.js +80 -65
- package/esm/mongo-collection-resource.js +8 -12
- package/esm/mongo-entity-service.js +45 -22
- package/esm/mongo-singleton-resource.js +3 -9
- package/esm/transform-filter.js +81 -66
- package/package.json +4 -4
- package/types/mongo-collection-resource.d.ts +10 -5
- package/types/mongo-entity-service.d.ts +5 -2
- package/types/mongo-singleton-resource.d.ts +3 -2
- package/types/transform-filter.d.ts +3 -2
|
@@ -3,10 +3,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.MongoCollectionResource = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const common_1 = require("@opra/common");
|
|
6
|
+
const core_1 = require("@opra/core");
|
|
6
7
|
const mongo_adapter_js_1 = require("./mongo-adapter.js");
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
// noinspection TypeScriptAbstractClassConstructorCanBeMadeProtected
|
|
9
|
+
class MongoCollectionResource extends core_1.CollectionResourceBase {
|
|
10
|
+
constructor(options) {
|
|
11
|
+
super(options);
|
|
10
12
|
}
|
|
11
13
|
async create(ctx) {
|
|
12
14
|
const prepared = mongo_adapter_js_1.MongoAdapter.transformRequest(ctx.request);
|
|
@@ -26,18 +28,12 @@ class MongoCollectionResource {
|
|
|
26
28
|
async get(ctx) {
|
|
27
29
|
const prepared = mongo_adapter_js_1.MongoAdapter.transformRequest(ctx.request);
|
|
28
30
|
const service = await this.getService(ctx);
|
|
29
|
-
|
|
30
|
-
if (!out)
|
|
31
|
-
throw new common_1.ResourceNotFoundError(service.collectionName, prepared.filter._id);
|
|
32
|
-
return out;
|
|
31
|
+
return service.findOne(prepared.filter, prepared.options);
|
|
33
32
|
}
|
|
34
33
|
async update(ctx) {
|
|
35
34
|
const prepared = mongo_adapter_js_1.MongoAdapter.transformRequest(ctx.request);
|
|
36
35
|
const service = await this.getService(ctx);
|
|
37
|
-
|
|
38
|
-
if (!out)
|
|
39
|
-
throw new common_1.ResourceNotFoundError(service.collectionName, prepared.filter._id);
|
|
40
|
-
return out;
|
|
36
|
+
return service.updateOne(prepared.filter, prepared.update, prepared.options);
|
|
41
37
|
}
|
|
42
38
|
async updateMany(ctx) {
|
|
43
39
|
const prepared = mongo_adapter_js_1.MongoAdapter.transformRequest(ctx.request);
|
|
@@ -2,13 +2,16 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.MongoEntityService = void 0;
|
|
4
4
|
class MongoEntityService {
|
|
5
|
-
constructor(
|
|
6
|
-
|
|
5
|
+
constructor(arg0, arg1) {
|
|
6
|
+
const options = typeof arg0 === 'object' ? arg0 : arg1;
|
|
7
|
+
if (typeof arg0 === 'string')
|
|
8
|
+
this._collectionName = arg0;
|
|
7
9
|
this.db = options?.db;
|
|
8
10
|
this.defaultLimit = options?.defaultLimit || 10;
|
|
9
11
|
}
|
|
10
12
|
async count(filter, options) {
|
|
11
|
-
const
|
|
13
|
+
const db = await this.getDatabase();
|
|
14
|
+
const collection = await this.getCollection(db);
|
|
12
15
|
options = {
|
|
13
16
|
...options,
|
|
14
17
|
session: options?.session || this.session
|
|
@@ -22,7 +25,8 @@ class MongoEntityService {
|
|
|
22
25
|
}
|
|
23
26
|
}
|
|
24
27
|
async deleteOne(filter, options) {
|
|
25
|
-
const
|
|
28
|
+
const db = await this.getDatabase();
|
|
29
|
+
const collection = await this.getCollection(db);
|
|
26
30
|
options = {
|
|
27
31
|
...options,
|
|
28
32
|
session: options?.session || this.session
|
|
@@ -37,7 +41,8 @@ class MongoEntityService {
|
|
|
37
41
|
}
|
|
38
42
|
}
|
|
39
43
|
async deleteMany(filter, options) {
|
|
40
|
-
const
|
|
44
|
+
const db = await this.getDatabase();
|
|
45
|
+
const collection = await this.getCollection(db);
|
|
41
46
|
options = {
|
|
42
47
|
...options,
|
|
43
48
|
session: options?.session || this.session
|
|
@@ -52,7 +57,8 @@ class MongoEntityService {
|
|
|
52
57
|
}
|
|
53
58
|
}
|
|
54
59
|
async findOne(filter, options) {
|
|
55
|
-
const
|
|
60
|
+
const db = await this.getDatabase();
|
|
61
|
+
const collection = await this.getCollection(db);
|
|
56
62
|
options = {
|
|
57
63
|
...options,
|
|
58
64
|
session: options?.session || this.session
|
|
@@ -65,35 +71,40 @@ class MongoEntityService {
|
|
|
65
71
|
await this._onError(e);
|
|
66
72
|
throw e;
|
|
67
73
|
}
|
|
68
|
-
if (this.onTransformRow)
|
|
69
|
-
out = this.onTransformRow(out);
|
|
70
74
|
return out;
|
|
71
75
|
}
|
|
72
76
|
async find(filter, options) {
|
|
73
|
-
const
|
|
77
|
+
const db = await this.getDatabase();
|
|
78
|
+
const collection = await this.getCollection(db);
|
|
74
79
|
options = {
|
|
75
80
|
...options,
|
|
76
81
|
limit: options?.limit || this.defaultLimit,
|
|
77
82
|
session: options?.session || this.session
|
|
78
83
|
};
|
|
79
84
|
const out = [];
|
|
85
|
+
let cursor;
|
|
80
86
|
try {
|
|
81
|
-
|
|
82
|
-
let
|
|
83
|
-
while (
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
+
cursor = collection.find(filter, options);
|
|
88
|
+
let obj;
|
|
89
|
+
while (out.length < this.defaultLimit && (obj = await cursor.next())) {
|
|
90
|
+
const v = this.transformData ? this.transformData(obj) : obj;
|
|
91
|
+
if (v)
|
|
92
|
+
out.push(obj);
|
|
87
93
|
}
|
|
88
94
|
}
|
|
89
95
|
catch (e) {
|
|
90
96
|
await this._onError(e);
|
|
91
97
|
throw e;
|
|
92
98
|
}
|
|
99
|
+
finally {
|
|
100
|
+
if (cursor)
|
|
101
|
+
await cursor.close();
|
|
102
|
+
}
|
|
93
103
|
return out;
|
|
94
104
|
}
|
|
95
105
|
async insertOne(doc, options) {
|
|
96
|
-
const
|
|
106
|
+
const db = await this.getDatabase();
|
|
107
|
+
const collection = await this.getCollection(db);
|
|
97
108
|
let out;
|
|
98
109
|
options = {
|
|
99
110
|
...options,
|
|
@@ -108,14 +119,15 @@ class MongoEntityService {
|
|
|
108
119
|
await this._onError(e);
|
|
109
120
|
throw e;
|
|
110
121
|
}
|
|
111
|
-
if (this.
|
|
112
|
-
out = this.
|
|
122
|
+
if (this.transformData)
|
|
123
|
+
out = this.transformData(out);
|
|
113
124
|
if (!out)
|
|
114
125
|
throw new Error('"insertOne" operation returned no result!');
|
|
115
126
|
return out;
|
|
116
127
|
}
|
|
117
128
|
async updateOne(filter, doc, options) {
|
|
118
|
-
const
|
|
129
|
+
const db = await this.getDatabase();
|
|
130
|
+
const collection = await this.getCollection(db);
|
|
119
131
|
let out;
|
|
120
132
|
options = {
|
|
121
133
|
...options,
|
|
@@ -130,12 +142,13 @@ class MongoEntityService {
|
|
|
130
142
|
await this._onError(e);
|
|
131
143
|
throw e;
|
|
132
144
|
}
|
|
133
|
-
if (this.
|
|
134
|
-
out = this.
|
|
145
|
+
if (this.transformData)
|
|
146
|
+
out = this.transformData(out);
|
|
135
147
|
return out;
|
|
136
148
|
}
|
|
137
149
|
async updateMany(filter, doc, options) {
|
|
138
|
-
const
|
|
150
|
+
const db = await this.getDatabase();
|
|
151
|
+
const collection = await this.getCollection(db);
|
|
139
152
|
options = {
|
|
140
153
|
...options,
|
|
141
154
|
session: options?.session || this.session,
|
|
@@ -175,5 +188,15 @@ class MongoEntityService {
|
|
|
175
188
|
throw new Error(`Database not set!`);
|
|
176
189
|
return this.db;
|
|
177
190
|
}
|
|
191
|
+
async getCollection(db) {
|
|
192
|
+
if (!this._collectionName)
|
|
193
|
+
throw new Error('collectionName is not assigned');
|
|
194
|
+
return db.collection(this.getCollectionName());
|
|
195
|
+
}
|
|
196
|
+
getCollectionName() {
|
|
197
|
+
if (!this._collectionName)
|
|
198
|
+
throw new Error('collectionName is not defined');
|
|
199
|
+
return this._collectionName;
|
|
200
|
+
}
|
|
178
201
|
}
|
|
179
202
|
exports.MongoEntityService = MongoEntityService;
|
|
@@ -22,18 +22,12 @@ class MongoSingletonResource {
|
|
|
22
22
|
async get(ctx) {
|
|
23
23
|
const prepared = mongo_adapter_js_1.MongoAdapter.transformRequest(ctx.request);
|
|
24
24
|
const service = await this.getService(ctx);
|
|
25
|
-
|
|
26
|
-
if (!out)
|
|
27
|
-
throw new common_1.ResourceNotFoundError(service.collectionName);
|
|
28
|
-
return out;
|
|
25
|
+
return service.findOne(prepared.filter, prepared.options);
|
|
29
26
|
}
|
|
30
27
|
async update(ctx) {
|
|
31
28
|
const prepared = mongo_adapter_js_1.MongoAdapter.transformRequest(ctx.request);
|
|
32
29
|
const service = await this.getService(ctx);
|
|
33
|
-
|
|
34
|
-
if (!out)
|
|
35
|
-
throw new common_1.ResourceNotFoundError(service.collectionName, prepared.filter._id);
|
|
36
|
-
return out;
|
|
30
|
+
return service.updateOne(prepared.filter, prepared.update, prepared.options);
|
|
37
31
|
}
|
|
38
32
|
}
|
|
39
33
|
tslib_1.__decorate([
|
package/cjs/transform-filter.js
CHANGED
|
@@ -1,96 +1,111 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
require("@opra/core");
|
|
3
4
|
const common_1 = require("@opra/common");
|
|
4
|
-
|
|
5
|
-
|
|
5
|
+
const opMap = {
|
|
6
|
+
'=': '$eq',
|
|
7
|
+
'!=': '$ne',
|
|
8
|
+
'>': '$gt',
|
|
9
|
+
'>=': '$gte',
|
|
10
|
+
'<': '$lt',
|
|
11
|
+
'<=': '$lte',
|
|
12
|
+
'in': '$in',
|
|
13
|
+
'!in': '$nin',
|
|
14
|
+
};
|
|
15
|
+
function transformFilter(ast, negative) {
|
|
6
16
|
if (!ast)
|
|
7
17
|
return;
|
|
8
|
-
if (ast instanceof common_1.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
18
|
+
if (ast instanceof common_1.OpraFilter.QualifiedIdentifier) {
|
|
19
|
+
return ast.value;
|
|
20
|
+
}
|
|
21
|
+
if (ast instanceof common_1.OpraFilter.NumberLiteral ||
|
|
22
|
+
ast instanceof common_1.OpraFilter.StringLiteral ||
|
|
23
|
+
ast instanceof common_1.OpraFilter.BooleanLiteral ||
|
|
24
|
+
ast instanceof common_1.OpraFilter.NullLiteral ||
|
|
25
|
+
ast instanceof common_1.OpraFilter.DateLiteral ||
|
|
26
|
+
ast instanceof common_1.OpraFilter.TimeLiteral) {
|
|
27
|
+
return ast.value;
|
|
28
|
+
}
|
|
29
|
+
if (ast instanceof common_1.OpraFilter.ArrayExpression) {
|
|
30
|
+
return ast.items.map(x => transformFilter(x, negative));
|
|
31
|
+
}
|
|
32
|
+
if (ast instanceof common_1.OpraFilter.NegativeExpression) {
|
|
33
|
+
return transformFilter(ast.expression, !negative);
|
|
34
|
+
}
|
|
35
|
+
if (ast instanceof common_1.OpraFilter.LogicalExpression) {
|
|
36
|
+
if (ast.op === 'or')
|
|
37
|
+
return { $or: ast.items.map(x => transformFilter(x, negative)) };
|
|
38
|
+
return { $and: ast.items.map(x => transformFilter(x, negative)) };
|
|
39
|
+
}
|
|
40
|
+
if (ast instanceof common_1.OpraFilter.ParenthesizedExpression) {
|
|
41
|
+
return transformFilter(ast.expression, negative);
|
|
42
|
+
}
|
|
43
|
+
if (ast instanceof common_1.OpraFilter.ComparisonExpression) {
|
|
44
|
+
const left = transformFilter(ast.left, negative);
|
|
45
|
+
if (ast.right instanceof common_1.OpraFilter.QualifiedIdentifier) {
|
|
46
|
+
const op = opMap[ast.op];
|
|
47
|
+
if (op)
|
|
48
|
+
return { $expr: { [op]: ["$" + left, "$" + ast.right.value] } };
|
|
49
|
+
/* istanbul ignore next */
|
|
50
|
+
throw new Error(`Invalid filter query.`);
|
|
51
|
+
}
|
|
52
|
+
let right = transformFilter(ast.right);
|
|
53
|
+
if (right == null) {
|
|
54
|
+
const op = ast.op === '='
|
|
55
|
+
? (negative ? '!=' : '=')
|
|
56
|
+
: (negative ? '=' : '!=');
|
|
57
|
+
if (op === '=')
|
|
58
|
+
return { $or: [{ [left]: null }, { [left]: { $exists: false } }] };
|
|
59
|
+
if (op === '!=')
|
|
60
|
+
return { $and: [{ $ne: { [left]: null } }, { [left]: { $exists: true } }] };
|
|
61
|
+
}
|
|
62
|
+
const mngOp = opMap[ast.op];
|
|
63
|
+
if (mngOp) {
|
|
64
|
+
if (ast.op === 'in' || ast.op === '!in')
|
|
65
|
+
right = Array.isArray(right) ? right : [right];
|
|
66
|
+
if (ast.op === '=' && !negative)
|
|
15
67
|
return { [left]: right };
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
return { [left]: { $gt: right } };
|
|
20
|
-
case '>=':
|
|
21
|
-
return { [left]: { $gte: right } };
|
|
22
|
-
case '<':
|
|
23
|
-
return { [left]: { $lt: right } };
|
|
24
|
-
case '<=':
|
|
25
|
-
return { [left]: { $lte: right } };
|
|
26
|
-
case 'in':
|
|
27
|
-
return { [left]: { $in: Array.isArray(right) ? right : [right] } };
|
|
28
|
-
case '!in':
|
|
29
|
-
return { [left]: { $nin: Array.isArray(right) ? right : [right] } };
|
|
68
|
+
return { [left]: wrapNot({ [mngOp]: right }, negative) };
|
|
69
|
+
}
|
|
70
|
+
switch (ast.op) {
|
|
30
71
|
case 'like':
|
|
31
72
|
return {
|
|
32
|
-
[left]: {
|
|
73
|
+
[left]: wrapNot({
|
|
33
74
|
$text: {
|
|
34
75
|
$search: '\\"' + right.replace(/\\"/, '"') + '\\"',
|
|
35
76
|
$caseSensitive: true
|
|
36
77
|
}
|
|
37
|
-
}
|
|
78
|
+
}, negative)
|
|
38
79
|
};
|
|
39
80
|
case 'ilike':
|
|
40
81
|
return {
|
|
41
|
-
[left]: {
|
|
82
|
+
[left]: wrapNot({
|
|
42
83
|
$text: {
|
|
43
84
|
$search: '\\"' + right.replace(/\\"/, '"') + '\\"'
|
|
44
85
|
}
|
|
45
|
-
}
|
|
86
|
+
}, negative)
|
|
46
87
|
};
|
|
47
88
|
case '!like':
|
|
48
89
|
return {
|
|
49
|
-
[left]: {
|
|
50
|
-
$
|
|
51
|
-
$
|
|
52
|
-
|
|
53
|
-
$caseSensitive: true
|
|
54
|
-
}
|
|
90
|
+
[left]: wrapNot({
|
|
91
|
+
$text: {
|
|
92
|
+
$search: '\\"' + right.replace(/\\"/, '"') + '\\"',
|
|
93
|
+
$caseSensitive: true
|
|
55
94
|
}
|
|
56
|
-
}
|
|
95
|
+
}, !negative)
|
|
57
96
|
};
|
|
58
97
|
case '!ilike':
|
|
59
98
|
return {
|
|
60
|
-
[left]: {
|
|
61
|
-
$
|
|
62
|
-
$
|
|
63
|
-
$search: '\\"' + right.replace(/\\"/, '"') + '\\"'
|
|
64
|
-
}
|
|
99
|
+
[left]: wrapNot({
|
|
100
|
+
$text: {
|
|
101
|
+
$search: '\\"' + right.replace(/\\"/, '"') + '\\"'
|
|
65
102
|
}
|
|
66
|
-
}
|
|
103
|
+
}, !negative)
|
|
67
104
|
};
|
|
68
|
-
default:
|
|
69
|
-
throw new Error(`ComparisonExpression operator (${ast.op}) not implemented yet`);
|
|
70
105
|
}
|
|
106
|
+
throw new Error(`Unimplemented ComparisonExpression operation (right side is ${ast.right.kind})`);
|
|
71
107
|
}
|
|
72
|
-
|
|
73
|
-
return ast.value;
|
|
74
|
-
}
|
|
75
|
-
if (ast instanceof common_1.NumberLiteral ||
|
|
76
|
-
ast instanceof common_1.StringLiteral ||
|
|
77
|
-
ast instanceof common_1.BooleanLiteral ||
|
|
78
|
-
ast instanceof common_1.NullLiteral ||
|
|
79
|
-
ast instanceof common_1.DateLiteral ||
|
|
80
|
-
ast instanceof common_1.TimeLiteral) {
|
|
81
|
-
return ast.value;
|
|
82
|
-
}
|
|
83
|
-
if (ast instanceof common_1.ArrayExpression) {
|
|
84
|
-
return ast.items.map(transformFilter);
|
|
85
|
-
}
|
|
86
|
-
if (ast instanceof common_1.LogicalExpression) {
|
|
87
|
-
if (ast.op === 'or')
|
|
88
|
-
return { $or: ast.items.map(transformFilter) };
|
|
89
|
-
return { $and: ast.items.map(transformFilter) };
|
|
90
|
-
}
|
|
91
|
-
if (ast instanceof common_1.ParenthesesExpression) {
|
|
92
|
-
return transformFilter(ast.expression);
|
|
93
|
-
}
|
|
94
|
-
throw new Error(`${ast.type} is not implemented yet`);
|
|
108
|
+
throw new Error(`${ast.kind} is not implemented yet`);
|
|
95
109
|
}
|
|
96
110
|
exports.default = transformFilter;
|
|
111
|
+
const wrapNot = (o, negative) => negative ? { $not: o } : o;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { __decorate, __metadata } from "tslib";
|
|
2
|
-
import { Collection
|
|
2
|
+
import { Collection } from '@opra/common';
|
|
3
|
+
import { CollectionResourceBase } from '@opra/core';
|
|
3
4
|
import { MongoAdapter } from './mongo-adapter.js';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
// noinspection TypeScriptAbstractClassConstructorCanBeMadeProtected
|
|
6
|
+
export class MongoCollectionResource extends CollectionResourceBase {
|
|
7
|
+
constructor(options) {
|
|
8
|
+
super(options);
|
|
7
9
|
}
|
|
8
10
|
async create(ctx) {
|
|
9
11
|
const prepared = MongoAdapter.transformRequest(ctx.request);
|
|
@@ -23,18 +25,12 @@ export class MongoCollectionResource {
|
|
|
23
25
|
async get(ctx) {
|
|
24
26
|
const prepared = MongoAdapter.transformRequest(ctx.request);
|
|
25
27
|
const service = await this.getService(ctx);
|
|
26
|
-
|
|
27
|
-
if (!out)
|
|
28
|
-
throw new ResourceNotFoundError(service.collectionName, prepared.filter._id);
|
|
29
|
-
return out;
|
|
28
|
+
return service.findOne(prepared.filter, prepared.options);
|
|
30
29
|
}
|
|
31
30
|
async update(ctx) {
|
|
32
31
|
const prepared = MongoAdapter.transformRequest(ctx.request);
|
|
33
32
|
const service = await this.getService(ctx);
|
|
34
|
-
|
|
35
|
-
if (!out)
|
|
36
|
-
throw new ResourceNotFoundError(service.collectionName, prepared.filter._id);
|
|
37
|
-
return out;
|
|
33
|
+
return service.updateOne(prepared.filter, prepared.update, prepared.options);
|
|
38
34
|
}
|
|
39
35
|
async updateMany(ctx) {
|
|
40
36
|
const prepared = MongoAdapter.transformRequest(ctx.request);
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
export class MongoEntityService {
|
|
2
|
-
constructor(
|
|
3
|
-
|
|
2
|
+
constructor(arg0, arg1) {
|
|
3
|
+
const options = typeof arg0 === 'object' ? arg0 : arg1;
|
|
4
|
+
if (typeof arg0 === 'string')
|
|
5
|
+
this._collectionName = arg0;
|
|
4
6
|
this.db = options?.db;
|
|
5
7
|
this.defaultLimit = options?.defaultLimit || 10;
|
|
6
8
|
}
|
|
7
9
|
async count(filter, options) {
|
|
8
|
-
const
|
|
10
|
+
const db = await this.getDatabase();
|
|
11
|
+
const collection = await this.getCollection(db);
|
|
9
12
|
options = {
|
|
10
13
|
...options,
|
|
11
14
|
session: options?.session || this.session
|
|
@@ -19,7 +22,8 @@ export class MongoEntityService {
|
|
|
19
22
|
}
|
|
20
23
|
}
|
|
21
24
|
async deleteOne(filter, options) {
|
|
22
|
-
const
|
|
25
|
+
const db = await this.getDatabase();
|
|
26
|
+
const collection = await this.getCollection(db);
|
|
23
27
|
options = {
|
|
24
28
|
...options,
|
|
25
29
|
session: options?.session || this.session
|
|
@@ -34,7 +38,8 @@ export class MongoEntityService {
|
|
|
34
38
|
}
|
|
35
39
|
}
|
|
36
40
|
async deleteMany(filter, options) {
|
|
37
|
-
const
|
|
41
|
+
const db = await this.getDatabase();
|
|
42
|
+
const collection = await this.getCollection(db);
|
|
38
43
|
options = {
|
|
39
44
|
...options,
|
|
40
45
|
session: options?.session || this.session
|
|
@@ -49,7 +54,8 @@ export class MongoEntityService {
|
|
|
49
54
|
}
|
|
50
55
|
}
|
|
51
56
|
async findOne(filter, options) {
|
|
52
|
-
const
|
|
57
|
+
const db = await this.getDatabase();
|
|
58
|
+
const collection = await this.getCollection(db);
|
|
53
59
|
options = {
|
|
54
60
|
...options,
|
|
55
61
|
session: options?.session || this.session
|
|
@@ -62,35 +68,40 @@ export class MongoEntityService {
|
|
|
62
68
|
await this._onError(e);
|
|
63
69
|
throw e;
|
|
64
70
|
}
|
|
65
|
-
if (this.onTransformRow)
|
|
66
|
-
out = this.onTransformRow(out);
|
|
67
71
|
return out;
|
|
68
72
|
}
|
|
69
73
|
async find(filter, options) {
|
|
70
|
-
const
|
|
74
|
+
const db = await this.getDatabase();
|
|
75
|
+
const collection = await this.getCollection(db);
|
|
71
76
|
options = {
|
|
72
77
|
...options,
|
|
73
78
|
limit: options?.limit || this.defaultLimit,
|
|
74
79
|
session: options?.session || this.session
|
|
75
80
|
};
|
|
76
81
|
const out = [];
|
|
82
|
+
let cursor;
|
|
77
83
|
try {
|
|
78
|
-
|
|
79
|
-
let
|
|
80
|
-
while (
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
+
cursor = collection.find(filter, options);
|
|
85
|
+
let obj;
|
|
86
|
+
while (out.length < this.defaultLimit && (obj = await cursor.next())) {
|
|
87
|
+
const v = this.transformData ? this.transformData(obj) : obj;
|
|
88
|
+
if (v)
|
|
89
|
+
out.push(obj);
|
|
84
90
|
}
|
|
85
91
|
}
|
|
86
92
|
catch (e) {
|
|
87
93
|
await this._onError(e);
|
|
88
94
|
throw e;
|
|
89
95
|
}
|
|
96
|
+
finally {
|
|
97
|
+
if (cursor)
|
|
98
|
+
await cursor.close();
|
|
99
|
+
}
|
|
90
100
|
return out;
|
|
91
101
|
}
|
|
92
102
|
async insertOne(doc, options) {
|
|
93
|
-
const
|
|
103
|
+
const db = await this.getDatabase();
|
|
104
|
+
const collection = await this.getCollection(db);
|
|
94
105
|
let out;
|
|
95
106
|
options = {
|
|
96
107
|
...options,
|
|
@@ -105,14 +116,15 @@ export class MongoEntityService {
|
|
|
105
116
|
await this._onError(e);
|
|
106
117
|
throw e;
|
|
107
118
|
}
|
|
108
|
-
if (this.
|
|
109
|
-
out = this.
|
|
119
|
+
if (this.transformData)
|
|
120
|
+
out = this.transformData(out);
|
|
110
121
|
if (!out)
|
|
111
122
|
throw new Error('"insertOne" operation returned no result!');
|
|
112
123
|
return out;
|
|
113
124
|
}
|
|
114
125
|
async updateOne(filter, doc, options) {
|
|
115
|
-
const
|
|
126
|
+
const db = await this.getDatabase();
|
|
127
|
+
const collection = await this.getCollection(db);
|
|
116
128
|
let out;
|
|
117
129
|
options = {
|
|
118
130
|
...options,
|
|
@@ -127,12 +139,13 @@ export class MongoEntityService {
|
|
|
127
139
|
await this._onError(e);
|
|
128
140
|
throw e;
|
|
129
141
|
}
|
|
130
|
-
if (this.
|
|
131
|
-
out = this.
|
|
142
|
+
if (this.transformData)
|
|
143
|
+
out = this.transformData(out);
|
|
132
144
|
return out;
|
|
133
145
|
}
|
|
134
146
|
async updateMany(filter, doc, options) {
|
|
135
|
-
const
|
|
147
|
+
const db = await this.getDatabase();
|
|
148
|
+
const collection = await this.getCollection(db);
|
|
136
149
|
options = {
|
|
137
150
|
...options,
|
|
138
151
|
session: options?.session || this.session,
|
|
@@ -172,4 +185,14 @@ export class MongoEntityService {
|
|
|
172
185
|
throw new Error(`Database not set!`);
|
|
173
186
|
return this.db;
|
|
174
187
|
}
|
|
188
|
+
async getCollection(db) {
|
|
189
|
+
if (!this._collectionName)
|
|
190
|
+
throw new Error('collectionName is not assigned');
|
|
191
|
+
return db.collection(this.getCollectionName());
|
|
192
|
+
}
|
|
193
|
+
getCollectionName() {
|
|
194
|
+
if (!this._collectionName)
|
|
195
|
+
throw new Error('collectionName is not defined');
|
|
196
|
+
return this._collectionName;
|
|
197
|
+
}
|
|
175
198
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { __decorate, __metadata } from "tslib";
|
|
2
|
-
import {
|
|
2
|
+
import { Singleton } from '@opra/common';
|
|
3
3
|
import { MongoAdapter } from './mongo-adapter.js';
|
|
4
4
|
export class MongoSingletonResource {
|
|
5
5
|
constructor() {
|
|
@@ -19,18 +19,12 @@ export class MongoSingletonResource {
|
|
|
19
19
|
async get(ctx) {
|
|
20
20
|
const prepared = MongoAdapter.transformRequest(ctx.request);
|
|
21
21
|
const service = await this.getService(ctx);
|
|
22
|
-
|
|
23
|
-
if (!out)
|
|
24
|
-
throw new ResourceNotFoundError(service.collectionName);
|
|
25
|
-
return out;
|
|
22
|
+
return service.findOne(prepared.filter, prepared.options);
|
|
26
23
|
}
|
|
27
24
|
async update(ctx) {
|
|
28
25
|
const prepared = MongoAdapter.transformRequest(ctx.request);
|
|
29
26
|
const service = await this.getService(ctx);
|
|
30
|
-
|
|
31
|
-
if (!out)
|
|
32
|
-
throw new ResourceNotFoundError(service.collectionName, prepared.filter._id);
|
|
33
|
-
return out;
|
|
27
|
+
return service.updateOne(prepared.filter, prepared.update, prepared.options);
|
|
34
28
|
}
|
|
35
29
|
}
|
|
36
30
|
__decorate([
|
package/esm/transform-filter.js
CHANGED
|
@@ -1,93 +1,108 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import '@opra/core';
|
|
2
|
+
import { OpraFilter } from '@opra/common';
|
|
3
|
+
const opMap = {
|
|
4
|
+
'=': '$eq',
|
|
5
|
+
'!=': '$ne',
|
|
6
|
+
'>': '$gt',
|
|
7
|
+
'>=': '$gte',
|
|
8
|
+
'<': '$lt',
|
|
9
|
+
'<=': '$lte',
|
|
10
|
+
'in': '$in',
|
|
11
|
+
'!in': '$nin',
|
|
12
|
+
};
|
|
13
|
+
export default function transformFilter(ast, negative) {
|
|
4
14
|
if (!ast)
|
|
5
15
|
return;
|
|
6
|
-
if (ast instanceof
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
16
|
+
if (ast instanceof OpraFilter.QualifiedIdentifier) {
|
|
17
|
+
return ast.value;
|
|
18
|
+
}
|
|
19
|
+
if (ast instanceof OpraFilter.NumberLiteral ||
|
|
20
|
+
ast instanceof OpraFilter.StringLiteral ||
|
|
21
|
+
ast instanceof OpraFilter.BooleanLiteral ||
|
|
22
|
+
ast instanceof OpraFilter.NullLiteral ||
|
|
23
|
+
ast instanceof OpraFilter.DateLiteral ||
|
|
24
|
+
ast instanceof OpraFilter.TimeLiteral) {
|
|
25
|
+
return ast.value;
|
|
26
|
+
}
|
|
27
|
+
if (ast instanceof OpraFilter.ArrayExpression) {
|
|
28
|
+
return ast.items.map(x => transformFilter(x, negative));
|
|
29
|
+
}
|
|
30
|
+
if (ast instanceof OpraFilter.NegativeExpression) {
|
|
31
|
+
return transformFilter(ast.expression, !negative);
|
|
32
|
+
}
|
|
33
|
+
if (ast instanceof OpraFilter.LogicalExpression) {
|
|
34
|
+
if (ast.op === 'or')
|
|
35
|
+
return { $or: ast.items.map(x => transformFilter(x, negative)) };
|
|
36
|
+
return { $and: ast.items.map(x => transformFilter(x, negative)) };
|
|
37
|
+
}
|
|
38
|
+
if (ast instanceof OpraFilter.ParenthesizedExpression) {
|
|
39
|
+
return transformFilter(ast.expression, negative);
|
|
40
|
+
}
|
|
41
|
+
if (ast instanceof OpraFilter.ComparisonExpression) {
|
|
42
|
+
const left = transformFilter(ast.left, negative);
|
|
43
|
+
if (ast.right instanceof OpraFilter.QualifiedIdentifier) {
|
|
44
|
+
const op = opMap[ast.op];
|
|
45
|
+
if (op)
|
|
46
|
+
return { $expr: { [op]: ["$" + left, "$" + ast.right.value] } };
|
|
47
|
+
/* istanbul ignore next */
|
|
48
|
+
throw new Error(`Invalid filter query.`);
|
|
49
|
+
}
|
|
50
|
+
let right = transformFilter(ast.right);
|
|
51
|
+
if (right == null) {
|
|
52
|
+
const op = ast.op === '='
|
|
53
|
+
? (negative ? '!=' : '=')
|
|
54
|
+
: (negative ? '=' : '!=');
|
|
55
|
+
if (op === '=')
|
|
56
|
+
return { $or: [{ [left]: null }, { [left]: { $exists: false } }] };
|
|
57
|
+
if (op === '!=')
|
|
58
|
+
return { $and: [{ $ne: { [left]: null } }, { [left]: { $exists: true } }] };
|
|
59
|
+
}
|
|
60
|
+
const mngOp = opMap[ast.op];
|
|
61
|
+
if (mngOp) {
|
|
62
|
+
if (ast.op === 'in' || ast.op === '!in')
|
|
63
|
+
right = Array.isArray(right) ? right : [right];
|
|
64
|
+
if (ast.op === '=' && !negative)
|
|
13
65
|
return { [left]: right };
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
return { [left]: { $gt: right } };
|
|
18
|
-
case '>=':
|
|
19
|
-
return { [left]: { $gte: right } };
|
|
20
|
-
case '<':
|
|
21
|
-
return { [left]: { $lt: right } };
|
|
22
|
-
case '<=':
|
|
23
|
-
return { [left]: { $lte: right } };
|
|
24
|
-
case 'in':
|
|
25
|
-
return { [left]: { $in: Array.isArray(right) ? right : [right] } };
|
|
26
|
-
case '!in':
|
|
27
|
-
return { [left]: { $nin: Array.isArray(right) ? right : [right] } };
|
|
66
|
+
return { [left]: wrapNot({ [mngOp]: right }, negative) };
|
|
67
|
+
}
|
|
68
|
+
switch (ast.op) {
|
|
28
69
|
case 'like':
|
|
29
70
|
return {
|
|
30
|
-
[left]: {
|
|
71
|
+
[left]: wrapNot({
|
|
31
72
|
$text: {
|
|
32
73
|
$search: '\\"' + right.replace(/\\"/, '"') + '\\"',
|
|
33
74
|
$caseSensitive: true
|
|
34
75
|
}
|
|
35
|
-
}
|
|
76
|
+
}, negative)
|
|
36
77
|
};
|
|
37
78
|
case 'ilike':
|
|
38
79
|
return {
|
|
39
|
-
[left]: {
|
|
80
|
+
[left]: wrapNot({
|
|
40
81
|
$text: {
|
|
41
82
|
$search: '\\"' + right.replace(/\\"/, '"') + '\\"'
|
|
42
83
|
}
|
|
43
|
-
}
|
|
84
|
+
}, negative)
|
|
44
85
|
};
|
|
45
86
|
case '!like':
|
|
46
87
|
return {
|
|
47
|
-
[left]: {
|
|
48
|
-
$
|
|
49
|
-
$
|
|
50
|
-
|
|
51
|
-
$caseSensitive: true
|
|
52
|
-
}
|
|
88
|
+
[left]: wrapNot({
|
|
89
|
+
$text: {
|
|
90
|
+
$search: '\\"' + right.replace(/\\"/, '"') + '\\"',
|
|
91
|
+
$caseSensitive: true
|
|
53
92
|
}
|
|
54
|
-
}
|
|
93
|
+
}, !negative)
|
|
55
94
|
};
|
|
56
95
|
case '!ilike':
|
|
57
96
|
return {
|
|
58
|
-
[left]: {
|
|
59
|
-
$
|
|
60
|
-
$
|
|
61
|
-
$search: '\\"' + right.replace(/\\"/, '"') + '\\"'
|
|
62
|
-
}
|
|
97
|
+
[left]: wrapNot({
|
|
98
|
+
$text: {
|
|
99
|
+
$search: '\\"' + right.replace(/\\"/, '"') + '\\"'
|
|
63
100
|
}
|
|
64
|
-
}
|
|
101
|
+
}, !negative)
|
|
65
102
|
};
|
|
66
|
-
default:
|
|
67
|
-
throw new Error(`ComparisonExpression operator (${ast.op}) not implemented yet`);
|
|
68
103
|
}
|
|
104
|
+
throw new Error(`Unimplemented ComparisonExpression operation (right side is ${ast.right.kind})`);
|
|
69
105
|
}
|
|
70
|
-
|
|
71
|
-
return ast.value;
|
|
72
|
-
}
|
|
73
|
-
if (ast instanceof NumberLiteral ||
|
|
74
|
-
ast instanceof StringLiteral ||
|
|
75
|
-
ast instanceof BooleanLiteral ||
|
|
76
|
-
ast instanceof NullLiteral ||
|
|
77
|
-
ast instanceof DateLiteral ||
|
|
78
|
-
ast instanceof TimeLiteral) {
|
|
79
|
-
return ast.value;
|
|
80
|
-
}
|
|
81
|
-
if (ast instanceof ArrayExpression) {
|
|
82
|
-
return ast.items.map(transformFilter);
|
|
83
|
-
}
|
|
84
|
-
if (ast instanceof LogicalExpression) {
|
|
85
|
-
if (ast.op === 'or')
|
|
86
|
-
return { $or: ast.items.map(transformFilter) };
|
|
87
|
-
return { $and: ast.items.map(transformFilter) };
|
|
88
|
-
}
|
|
89
|
-
if (ast instanceof ParenthesesExpression) {
|
|
90
|
-
return transformFilter(ast.expression);
|
|
91
|
-
}
|
|
92
|
-
throw new Error(`${ast.type} is not implemented yet`);
|
|
106
|
+
throw new Error(`${ast.kind} is not implemented yet`);
|
|
93
107
|
}
|
|
108
|
+
const wrapNot = (o, negative) => negative ? { $not: o } : o;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opra/mongodb",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.2",
|
|
4
4
|
"description": "Opra MongoDB adapter package",
|
|
5
5
|
"author": "Panates",
|
|
6
6
|
"license": "MIT",
|
|
@@ -29,12 +29,12 @@
|
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@faker-js/faker": "^7.6.0",
|
|
32
|
-
"mongodb": "^5.
|
|
32
|
+
"mongodb": "^5.4.0",
|
|
33
33
|
"ts-gems": "^2.3.0"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
|
-
"@opra/common": "^0.17.
|
|
37
|
-
"@opra/core": "^0.17.
|
|
36
|
+
"@opra/common": "^0.17.2",
|
|
37
|
+
"@opra/core": "^0.17.2",
|
|
38
38
|
"mongodb": ">=4.x.x"
|
|
39
39
|
},
|
|
40
40
|
"type": "module",
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import mongodb from 'mongodb';
|
|
2
|
+
import { Maybe } from 'ts-gems';
|
|
2
3
|
import { PartialOutput } from '@opra/common';
|
|
3
|
-
import { RequestContext } from '@opra/core';
|
|
4
|
+
import { CollectionResourceBase, RequestContext } from '@opra/core';
|
|
4
5
|
import { MongoEntityService } from './mongo-entity-service.js';
|
|
5
|
-
export declare
|
|
6
|
-
|
|
6
|
+
export declare namespace MongoCollectionResource {
|
|
7
|
+
interface Options extends CollectionResourceBase.Options {
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export declare abstract class MongoCollectionResource<T extends mongodb.Document, TOutput = PartialOutput<T>> extends CollectionResourceBase {
|
|
11
|
+
constructor(options?: MongoCollectionResource.Options);
|
|
7
12
|
create(ctx: RequestContext): Promise<TOutput>;
|
|
8
13
|
delete(ctx: RequestContext): Promise<number>;
|
|
9
14
|
deleteMany(ctx: RequestContext): Promise<number>;
|
|
10
|
-
get(ctx: RequestContext): Promise<TOutput
|
|
11
|
-
update(ctx: RequestContext): Promise<TOutput
|
|
15
|
+
get(ctx: RequestContext): Promise<Maybe<TOutput>>;
|
|
16
|
+
update(ctx: RequestContext): Promise<Maybe<TOutput>>;
|
|
12
17
|
updateMany(ctx: RequestContext): Promise<number>;
|
|
13
18
|
findMany(ctx: RequestContext): Promise<TOutput[]>;
|
|
14
19
|
abstract getService(ctx: RequestContext): MongoEntityService<T, TOutput> | Promise<MongoEntityService<T, TOutput>>;
|
|
@@ -8,11 +8,12 @@ export declare namespace MongoEntityService {
|
|
|
8
8
|
}
|
|
9
9
|
}
|
|
10
10
|
export declare class MongoEntityService<T extends mongodb.Document, TOutput = PartialOutput<T>> {
|
|
11
|
-
|
|
11
|
+
protected _collectionName: string;
|
|
12
12
|
context: RequestContext;
|
|
13
13
|
defaultLimit: number;
|
|
14
14
|
db?: mongodb.Db;
|
|
15
15
|
session?: mongodb.ClientSession;
|
|
16
|
+
constructor(options?: MongoEntityService.Options);
|
|
16
17
|
constructor(collectionName: string, options?: MongoEntityService.Options);
|
|
17
18
|
count(filter?: mongodb.Filter<T>, options?: mongodb.CountOptions): Promise<number>;
|
|
18
19
|
deleteOne(filter?: mongodb.Filter<T>, options?: mongodb.DeleteOptions): Promise<number>;
|
|
@@ -25,6 +26,8 @@ export declare class MongoEntityService<T extends mongodb.Document, TOutput = Pa
|
|
|
25
26
|
with(context: RequestContext, db?: mongodb.Db, session?: mongodb.ClientSession): MongoEntityService<T, TOutput>;
|
|
26
27
|
protected _onError(error: unknown): Promise<void>;
|
|
27
28
|
protected getDatabase(): mongodb.Db | Promise<mongodb.Db>;
|
|
29
|
+
protected getCollection(db: mongodb.Db): Promise<mongodb.Collection<T>>;
|
|
30
|
+
protected getCollectionName(): string;
|
|
28
31
|
protected onError?(error: unknown): void | Promise<void>;
|
|
29
|
-
protected
|
|
32
|
+
protected transformData?(row: TOutput): TOutput;
|
|
30
33
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import mongodb from 'mongodb';
|
|
2
|
+
import { Maybe } from 'ts-gems';
|
|
2
3
|
import { PartialOutput } from '@opra/common';
|
|
3
4
|
import { RequestContext } from '@opra/core';
|
|
4
5
|
import { MongoEntityService } from './mongo-entity-service.js';
|
|
@@ -6,7 +7,7 @@ export declare abstract class MongoSingletonResource<T extends mongodb.Document,
|
|
|
6
7
|
defaultLimit: number;
|
|
7
8
|
create(ctx: RequestContext): Promise<TOutput>;
|
|
8
9
|
delete(ctx: RequestContext): Promise<number>;
|
|
9
|
-
get(ctx: RequestContext): Promise<TOutput
|
|
10
|
-
update(ctx: RequestContext): Promise<TOutput
|
|
10
|
+
get(ctx: RequestContext): Promise<Maybe<TOutput>>;
|
|
11
|
+
update(ctx: RequestContext): Promise<Maybe<TOutput>>;
|
|
11
12
|
abstract getService(ctx: RequestContext): MongoEntityService<T, TOutput> | Promise<MongoEntityService<T, TOutput>>;
|
|
12
13
|
}
|
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import '@opra/core';
|
|
2
|
+
import { OpraFilter } from '@opra/common';
|
|
3
|
+
export default function transformFilter(ast: OpraFilter.Expression | undefined, negative?: boolean): any;
|