@opra/elastic 0.33.13 → 1.0.0-alpha.18
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 +105 -0
- package/cjs/adapter-utils/prepare-key-values.js +22 -0
- package/cjs/adapter-utils/prepare-projection.js +109 -0
- package/cjs/{transform-sort.js → adapter-utils/prepare-sort.js} +2 -2
- package/cjs/elastic-adapter.js +76 -39
- package/cjs/elastic-service.js +144 -0
- package/esm/adapter-utils/prepare-filter.js +102 -0
- package/esm/adapter-utils/prepare-key-values.js +18 -0
- package/esm/adapter-utils/prepare-projection.js +109 -0
- package/esm/{transform-sort.js → adapter-utils/prepare-sort.js} +1 -1
- package/esm/elastic-adapter.js +76 -39
- package/esm/elastic-service.js +140 -0
- package/package.json +14 -9
- package/types/adapter-utils/prepare-filter.d.ts +3 -0
- package/types/adapter-utils/prepare-key-values.d.ts +1 -0
- package/types/adapter-utils/prepare-projection.d.ts +0 -0
- package/types/adapter-utils/prepare-sort.d.ts +1 -0
- package/types/elastic-adapter.d.ts +14 -10
- package/types/elastic-service.d.ts +147 -0
- package/cjs/transform-filter.js +0 -108
- package/cjs/transform-key-values.js +0 -14
- package/cjs/transform-projection.js +0 -53
- package/esm/transform-filter.js +0 -105
- package/esm/transform-key-values.js +0 -11
- package/esm/transform-projection.js +0 -50
- package/types/transform-filter.d.ts +0 -3
- package/types/transform-key-values.d.ts +0 -2
- package/types/transform-projection.d.ts +0 -6
- package/types/transform-sort.d.ts +0 -1
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = prepareFilter;
|
|
4
|
+
/* eslint-disable camelcase */
|
|
5
|
+
require("@opra/core");
|
|
6
|
+
const common_1 = require("@opra/common");
|
|
7
|
+
const isNil = (v) => v == null;
|
|
8
|
+
function prepareFilter(ast, negative) {
|
|
9
|
+
if (!ast)
|
|
10
|
+
return;
|
|
11
|
+
if (ast instanceof common_1.OpraFilter.QualifiedIdentifier) {
|
|
12
|
+
return ast.value;
|
|
13
|
+
}
|
|
14
|
+
if (ast instanceof common_1.OpraFilter.NumberLiteral ||
|
|
15
|
+
ast instanceof common_1.OpraFilter.StringLiteral ||
|
|
16
|
+
ast instanceof common_1.OpraFilter.BooleanLiteral ||
|
|
17
|
+
ast instanceof common_1.OpraFilter.NullLiteral ||
|
|
18
|
+
ast instanceof common_1.OpraFilter.DateLiteral ||
|
|
19
|
+
ast instanceof common_1.OpraFilter.TimeLiteral) {
|
|
20
|
+
return ast.value;
|
|
21
|
+
}
|
|
22
|
+
if (ast instanceof common_1.OpraFilter.ArrayExpression) {
|
|
23
|
+
return ast.items.map(x => prepareFilter(x, negative)).filter(x => !isNil(x));
|
|
24
|
+
}
|
|
25
|
+
if (ast instanceof common_1.OpraFilter.NegativeExpression) {
|
|
26
|
+
return prepareFilter(ast.expression, !negative);
|
|
27
|
+
}
|
|
28
|
+
if (ast instanceof common_1.OpraFilter.LogicalExpression) {
|
|
29
|
+
const v = ast.items.map(x => prepareFilter(x)).filter(x => !isNil(x));
|
|
30
|
+
if (ast.op === 'and') {
|
|
31
|
+
return {
|
|
32
|
+
bool: {
|
|
33
|
+
[negative ? 'must_not' : 'must']: v,
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
return wrapNot({
|
|
38
|
+
bool: { should: v },
|
|
39
|
+
}, negative);
|
|
40
|
+
}
|
|
41
|
+
if (ast instanceof common_1.OpraFilter.ParenthesizedExpression) {
|
|
42
|
+
return prepareFilter(ast.expression, negative);
|
|
43
|
+
}
|
|
44
|
+
if (ast instanceof common_1.OpraFilter.ComparisonExpression)
|
|
45
|
+
return _transformComparisonExpression(ast, !!negative);
|
|
46
|
+
throw new Error(`${ast.kind} is not implemented yet`);
|
|
47
|
+
}
|
|
48
|
+
function _transformComparisonExpression(ast, negative) {
|
|
49
|
+
const left = prepareFilter(ast.left, negative);
|
|
50
|
+
if (ast.right instanceof common_1.OpraFilter.QualifiedIdentifier) {
|
|
51
|
+
throw new TypeError('not implemented yet');
|
|
52
|
+
}
|
|
53
|
+
const right = prepareFilter(ast.right);
|
|
54
|
+
if (right == null) {
|
|
55
|
+
const op = ast.op === '=' ? (negative ? '!=' : '=') : negative ? '=' : '!=';
|
|
56
|
+
if (op === '=')
|
|
57
|
+
return { bool: { must_not: { exists: { field: left } } } };
|
|
58
|
+
if (op === '!=')
|
|
59
|
+
return { bool: { exists: { field: left } } };
|
|
60
|
+
}
|
|
61
|
+
switch (ast.op) {
|
|
62
|
+
case '=':
|
|
63
|
+
return wrapNot({ term: { [left]: right } }, negative);
|
|
64
|
+
case '!=':
|
|
65
|
+
return wrapNot({ term: { [left]: right } }, !negative);
|
|
66
|
+
case '>':
|
|
67
|
+
return wrapNot({ range: { [left]: { gt: right } } }, negative);
|
|
68
|
+
case '>=':
|
|
69
|
+
return wrapNot({ range: { [left]: { gte: right } } }, negative);
|
|
70
|
+
case '<':
|
|
71
|
+
return wrapNot({ range: { [left]: { lt: right } } }, negative);
|
|
72
|
+
case '<=':
|
|
73
|
+
return wrapNot({ range: { [left]: { lte: right } } }, negative);
|
|
74
|
+
case 'in':
|
|
75
|
+
return wrapNot({ terms: { [left]: Array.isArray(right) ? right : [right] } }, negative);
|
|
76
|
+
case '!in':
|
|
77
|
+
return wrapNot({ terms: { [left]: Array.isArray(right) ? right : [right] } }, !negative);
|
|
78
|
+
case 'like':
|
|
79
|
+
return wrapNot({ wildcard: { [left]: String(right) } }, negative);
|
|
80
|
+
case '!like':
|
|
81
|
+
return wrapNot({ wildcard: { [left]: String(right) } }, !negative);
|
|
82
|
+
case 'ilike':
|
|
83
|
+
return wrapNot({
|
|
84
|
+
wildcard: {
|
|
85
|
+
[left]: {
|
|
86
|
+
value: String(right),
|
|
87
|
+
case_insensitive: true,
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
}, negative);
|
|
91
|
+
case '!ilike':
|
|
92
|
+
return wrapNot({
|
|
93
|
+
wildcard: {
|
|
94
|
+
[left]: {
|
|
95
|
+
value: String(right),
|
|
96
|
+
case_insensitive: true,
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
}, !negative);
|
|
100
|
+
default:
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
throw new Error(`ComparisonExpression operator (${ast.op}) not implemented yet`);
|
|
104
|
+
}
|
|
105
|
+
const wrapNot = (o, negative) => (negative ? { bool: { must_not: o } } : o);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = prepareKeyValues;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const putil_isplainobject_1 = tslib_1.__importDefault(require("putil-isplainobject"));
|
|
6
|
+
const defaultPrimaryKey = ['_id'];
|
|
7
|
+
function prepareKeyValues(keyValue, primaryKey) {
|
|
8
|
+
primaryKey = primaryKey || defaultPrimaryKey;
|
|
9
|
+
const b = (0, putil_isplainobject_1.default)(keyValue);
|
|
10
|
+
if (primaryKey.length > 1 && !b) {
|
|
11
|
+
throw new TypeError(`Argument "keyValue" must be an object that contains all key values`);
|
|
12
|
+
}
|
|
13
|
+
if (primaryKey.length > 1 || b) {
|
|
14
|
+
return primaryKey.reduce((o, k) => {
|
|
15
|
+
o[k] = keyValue[k];
|
|
16
|
+
if (o[k] == null)
|
|
17
|
+
throw new Error(`Value of key "${k}" is required`);
|
|
18
|
+
return o;
|
|
19
|
+
}, {});
|
|
20
|
+
}
|
|
21
|
+
return { [primaryKey[0]]: keyValue };
|
|
22
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// import { ApiField, ComplexType, FieldsProjection, omitNullish, parseFieldsProjection } from '@opra/common';
|
|
3
|
+
// import mongodb, { Document } from 'mongodb';
|
|
4
|
+
//
|
|
5
|
+
// export default function prepareProjection(
|
|
6
|
+
// dataType: ComplexType,
|
|
7
|
+
// projection?: string | string[] | Document,
|
|
8
|
+
// ): mongodb.Document | undefined {
|
|
9
|
+
// if (projection && typeof projection === 'object' && !Array.isArray(projection)) return projection;
|
|
10
|
+
// const out: Record<string, boolean> = {};
|
|
11
|
+
// const projection_ =
|
|
12
|
+
// typeof projection === 'string' || Array.isArray(projection) ? parseFieldsProjection(projection) : projection;
|
|
13
|
+
// // const exclusionProjection = !pick && !!omit;
|
|
14
|
+
// prepare(dataType, out, projection_);
|
|
15
|
+
// return Object.keys(out).length ? out : undefined;
|
|
16
|
+
// }
|
|
17
|
+
//
|
|
18
|
+
// export function prepare(dataType: ComplexType, target: mongodb.Document, projection?: FieldsProjection) {
|
|
19
|
+
// const defaultFields = !projection || !Object.values(projection).find(p => !p.sign);
|
|
20
|
+
// const projectionKeys = projection && Object.keys(projection).map(x => x.toLowerCase());
|
|
21
|
+
// const projectionKeysSet = new Set(projectionKeys);
|
|
22
|
+
// let fieldName: string;
|
|
23
|
+
// let field: ApiField;
|
|
24
|
+
// let k: string;
|
|
25
|
+
// /** Add fields from data type */
|
|
26
|
+
// for (field of dataType.fields.values()) {
|
|
27
|
+
// fieldName = field.name;
|
|
28
|
+
// k = fieldName.toLowerCase();
|
|
29
|
+
// projectionKeysSet.delete(k);
|
|
30
|
+
// const p = projection?.[k];
|
|
31
|
+
// if (
|
|
32
|
+
// /** Ignore if field is omitted */
|
|
33
|
+
// p?.sign === '-' ||
|
|
34
|
+
// /** Ignore if default fields and field is not in projection */
|
|
35
|
+
// (!defaultFields && !p) ||
|
|
36
|
+
// /** Ignore if default fields enabled and fields is exclusive */
|
|
37
|
+
// (defaultFields && field.exclusive && !p)
|
|
38
|
+
// ) {
|
|
39
|
+
// continue;
|
|
40
|
+
// }
|
|
41
|
+
//
|
|
42
|
+
// if (field.type instanceof ComplexType && typeof p?.projection === 'object') {
|
|
43
|
+
// target[fieldName] = {};
|
|
44
|
+
// prepare(field.type, target[fieldName], p.projection);
|
|
45
|
+
// continue;
|
|
46
|
+
// }
|
|
47
|
+
// target[fieldName] = 1;
|
|
48
|
+
// }
|
|
49
|
+
// /** Add additional fields */
|
|
50
|
+
// if (dataType.additionalFields) {
|
|
51
|
+
// for (k of projectionKeysSet.values()) {
|
|
52
|
+
// const n = projectionKeysSet[k];
|
|
53
|
+
// if (n?.sign !== '-') target[k] = 1;
|
|
54
|
+
// }
|
|
55
|
+
// }
|
|
56
|
+
// }
|
|
57
|
+
//
|
|
58
|
+
// // function __prepareProjection(
|
|
59
|
+
// // dataType: ComplexType,
|
|
60
|
+
// // args: {
|
|
61
|
+
// // pick?: string[];
|
|
62
|
+
// // omit?: string[];
|
|
63
|
+
// // include?: string[];
|
|
64
|
+
// // },
|
|
65
|
+
// // ): any {
|
|
66
|
+
// // let includes: string[] | undefined;
|
|
67
|
+
// // let excludes: string[] | undefined;
|
|
68
|
+
// //
|
|
69
|
+
// // if (args.include && !args.pick) {
|
|
70
|
+
// // includes = includes || [];
|
|
71
|
+
// // for (const [k, f] of dataType.fields) {
|
|
72
|
+
// // if (f.exclusive) continue;
|
|
73
|
+
// // if (f.type instanceof ComplexType) includes.push(k + '.*');
|
|
74
|
+
// // else includes.push(k);
|
|
75
|
+
// // }
|
|
76
|
+
// // }
|
|
77
|
+
// //
|
|
78
|
+
// // if (args.pick) {
|
|
79
|
+
// // includes = includes || [];
|
|
80
|
+
// // for (const k of args.pick) {
|
|
81
|
+
// // const f = dataType.getField(k);
|
|
82
|
+
// // if (f.type instanceof ComplexType) includes.push(k + '.*');
|
|
83
|
+
// // else includes.push(k);
|
|
84
|
+
// // }
|
|
85
|
+
// // }
|
|
86
|
+
// //
|
|
87
|
+
// // if (args.include) {
|
|
88
|
+
// // includes = includes || [];
|
|
89
|
+
// // for (const k of args.include) {
|
|
90
|
+
// // const f = dataType.getField(k);
|
|
91
|
+
// // if (f.type instanceof ComplexType) includes.push(k + '.*');
|
|
92
|
+
// // else includes.push(k);
|
|
93
|
+
// // }
|
|
94
|
+
// // }
|
|
95
|
+
// //
|
|
96
|
+
// // if (args.omit) {
|
|
97
|
+
// // excludes = excludes || [];
|
|
98
|
+
// // for (const k of args.omit) {
|
|
99
|
+
// // const f = dataType.getField(k);
|
|
100
|
+
// // if (f.type instanceof ComplexType) excludes.push(k + '.*');
|
|
101
|
+
// // else excludes.push(k);
|
|
102
|
+
// // }
|
|
103
|
+
// // }
|
|
104
|
+
// //
|
|
105
|
+
// // return omitNullish({
|
|
106
|
+
// // includes,
|
|
107
|
+
// // excludes,
|
|
108
|
+
// // });
|
|
109
|
+
// // }
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
|
|
3
|
+
exports.default = prepareSort;
|
|
4
|
+
function prepareSort(sort) {
|
|
4
5
|
if (!(sort && sort.length))
|
|
5
6
|
return;
|
|
6
7
|
const out = [];
|
|
@@ -14,4 +15,3 @@ function transformSort(sort) {
|
|
|
14
15
|
});
|
|
15
16
|
return out;
|
|
16
17
|
}
|
|
17
|
-
exports.default = transformSort;
|
package/cjs/elastic-adapter.js
CHANGED
|
@@ -2,50 +2,87 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ElasticAdapter = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
const
|
|
5
|
+
// import { SearchRequest } from '@elastic/elasticsearch/lib/api/types';
|
|
6
|
+
// import { TransportRequestOptions } from '@elastic/transport';
|
|
7
|
+
// import { omitNullish } from '@opra/common';
|
|
8
|
+
const prepare_filter_js_1 = tslib_1.__importDefault(require("./adapter-utils/prepare-filter.js"));
|
|
9
|
+
const prepare_key_values_js_1 = tslib_1.__importDefault(require("./adapter-utils/prepare-key-values.js"));
|
|
10
|
+
// import _prepareProjection from './adapter-utils/prepare-projection.js';
|
|
11
|
+
const prepare_sort_js_1 = tslib_1.__importDefault(require("./adapter-utils/prepare-sort.js"));
|
|
10
12
|
var ElasticAdapter;
|
|
11
13
|
(function (ElasticAdapter) {
|
|
12
|
-
ElasticAdapter.
|
|
13
|
-
ElasticAdapter.
|
|
14
|
-
|
|
15
|
-
ElasticAdapter.
|
|
16
|
-
function
|
|
17
|
-
const {
|
|
18
|
-
if (
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
if (filter)
|
|
26
|
-
searchRequest.query = filter;
|
|
27
|
-
if (params?.limit != null)
|
|
28
|
-
searchRequest.size = params.limit;
|
|
29
|
-
if (params?.skip != null)
|
|
30
|
-
searchRequest.from = params.skip;
|
|
31
|
-
if (params?.count)
|
|
32
|
-
searchRequest.track_total_hits = true;
|
|
33
|
-
if (params?.pick || params?.include || params?.omit)
|
|
34
|
-
searchRequest._source = (0, transform_projection_js_1.default)(resource.type, params);
|
|
35
|
-
if (params?.sort)
|
|
36
|
-
searchRequest.sort = (0, transform_sort_js_1.default)(params.sort);
|
|
37
|
-
searchRequest = (0, common_1.omitNullish)(searchRequest);
|
|
38
|
-
options = (0, common_1.omitNullish)(options);
|
|
39
|
-
return {
|
|
40
|
-
method: 'search',
|
|
41
|
-
params: searchRequest,
|
|
42
|
-
options,
|
|
43
|
-
args: [searchRequest, options]
|
|
14
|
+
ElasticAdapter.prepareFilter = prepare_filter_js_1.default;
|
|
15
|
+
ElasticAdapter.prepareKeyValues = prepare_key_values_js_1.default;
|
|
16
|
+
// export const prepareProjection = _prepareProjection;
|
|
17
|
+
ElasticAdapter.prepareSort = prepare_sort_js_1.default;
|
|
18
|
+
async function parseRequest(context) {
|
|
19
|
+
const { operation } = context;
|
|
20
|
+
if (operation.composition?.startsWith('Entity.') && operation.compositionOptions?.type) {
|
|
21
|
+
const controller = operation.owner;
|
|
22
|
+
switch (operation.composition) {
|
|
23
|
+
case 'Entity.Create': {
|
|
24
|
+
const data = await context.getBody();
|
|
25
|
+
const options = {
|
|
26
|
+
projection: context.queryParams.projection,
|
|
44
27
|
};
|
|
28
|
+
return { method: 'create', data, options };
|
|
45
29
|
}
|
|
30
|
+
case 'Entity.Delete': {
|
|
31
|
+
const keyParam = operation.parameters.find(p => p.keyParam) || controller.parameters.find(p => p.keyParam);
|
|
32
|
+
const key = keyParam && context.pathParams[String(keyParam.name)];
|
|
33
|
+
const options = {
|
|
34
|
+
filter: context.queryParams.filter,
|
|
35
|
+
};
|
|
36
|
+
return { method: 'delete', key, options };
|
|
37
|
+
}
|
|
38
|
+
case 'Entity.DeleteMany': {
|
|
39
|
+
const options = {
|
|
40
|
+
filter: context.queryParams.filter,
|
|
41
|
+
};
|
|
42
|
+
return { method: 'deleteMany', options };
|
|
43
|
+
}
|
|
44
|
+
case 'Entity.FindMany': {
|
|
45
|
+
const options = {
|
|
46
|
+
filter: context.queryParams.filter,
|
|
47
|
+
projection: context.queryParams.projection,
|
|
48
|
+
count: context.queryParams.count,
|
|
49
|
+
limit: context.queryParams.limit,
|
|
50
|
+
skip: context.queryParams.skip,
|
|
51
|
+
sort: context.queryParams.sort,
|
|
52
|
+
};
|
|
53
|
+
return { method: 'findMany', options };
|
|
54
|
+
}
|
|
55
|
+
case 'Entity.Get': {
|
|
56
|
+
const keyParam = operation.parameters.find(p => p.keyParam) || controller.parameters.find(p => p.keyParam);
|
|
57
|
+
const key = keyParam && context.pathParams[String(keyParam.name)];
|
|
58
|
+
const options = {
|
|
59
|
+
projection: context.queryParams.projection,
|
|
60
|
+
filter: context.queryParams.filter,
|
|
61
|
+
};
|
|
62
|
+
return { method: 'get', key, options };
|
|
63
|
+
}
|
|
64
|
+
case 'Entity.Update': {
|
|
65
|
+
const data = await context.getBody();
|
|
66
|
+
const keyParam = operation.parameters.find(p => p.keyParam) || controller.parameters.find(p => p.keyParam);
|
|
67
|
+
const key = keyParam && context.pathParams[String(keyParam.name)];
|
|
68
|
+
const options = {
|
|
69
|
+
projection: context.queryParams.projection,
|
|
70
|
+
filter: context.queryParams.filter,
|
|
71
|
+
};
|
|
72
|
+
return { method: 'update', key, data, options };
|
|
73
|
+
}
|
|
74
|
+
case 'Entity.UpdateMany': {
|
|
75
|
+
const data = await context.getBody();
|
|
76
|
+
const options = {
|
|
77
|
+
filter: context.queryParams.filter,
|
|
78
|
+
};
|
|
79
|
+
return { method: 'updateMany', data, options };
|
|
80
|
+
}
|
|
81
|
+
default:
|
|
82
|
+
break;
|
|
46
83
|
}
|
|
47
84
|
}
|
|
48
|
-
throw new
|
|
85
|
+
throw new Error(`This operation is not compatible to Elastic adapter`);
|
|
49
86
|
}
|
|
50
|
-
ElasticAdapter.
|
|
87
|
+
ElasticAdapter.parseRequest = parseRequest;
|
|
51
88
|
})(ElasticAdapter || (exports.ElasticAdapter = ElasticAdapter = {}));
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ElasticService = void 0;
|
|
4
|
+
const core_1 = require("@opra/core");
|
|
5
|
+
/**
|
|
6
|
+
* Class representing a ElasticSearch service for interacting with a collection.
|
|
7
|
+
* @extends ServiceBase
|
|
8
|
+
* @template T - The type of the documents in the collection.
|
|
9
|
+
*/
|
|
10
|
+
class ElasticService extends core_1.ServiceBase {
|
|
11
|
+
/**
|
|
12
|
+
* Constructs a new instance
|
|
13
|
+
*
|
|
14
|
+
* @param dataType - The data type of the returning results
|
|
15
|
+
* @param indexName - The name of the index, or a function that returns the index name
|
|
16
|
+
* @param [options] - The options for the service
|
|
17
|
+
* @constructor
|
|
18
|
+
*/
|
|
19
|
+
constructor(dataType, indexName, options) {
|
|
20
|
+
super();
|
|
21
|
+
this._inputCodecs = {};
|
|
22
|
+
this._outputCodecs = {};
|
|
23
|
+
this._dataType_ = dataType;
|
|
24
|
+
this.client = options?.client;
|
|
25
|
+
this.$commonFilter = this.$commonFilter || options?.commonFilter;
|
|
26
|
+
this.$interceptor = this.$interceptor || options?.interceptor;
|
|
27
|
+
this.$indexName = indexName;
|
|
28
|
+
this.$resourceName = options?.resourceName;
|
|
29
|
+
this.$idGenerator = options?.idGenerator;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Retrieves the index name.
|
|
33
|
+
*
|
|
34
|
+
* @protected
|
|
35
|
+
* @returns The index name.
|
|
36
|
+
* @throws {Error} If the index name is not defined.
|
|
37
|
+
*/
|
|
38
|
+
getIndexName() {
|
|
39
|
+
const out = typeof this.$indexName === 'function' ? this.$indexName(this) : this.$indexName;
|
|
40
|
+
if (out)
|
|
41
|
+
return out;
|
|
42
|
+
throw new Error('indexName is not defined');
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Retrieves the resource name.
|
|
46
|
+
*
|
|
47
|
+
* @protected
|
|
48
|
+
* @returns {string} The resource name.
|
|
49
|
+
* @throws {Error} If the resource name is not defined.
|
|
50
|
+
*/
|
|
51
|
+
getResourceName() {
|
|
52
|
+
const out = typeof this.$resourceName === 'function' ? this.$resourceName(this) : this.$resourceName || this.getIndexName();
|
|
53
|
+
if (out)
|
|
54
|
+
return out;
|
|
55
|
+
throw new Error('resourceName is not defined');
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Retrieves the OPRA data type
|
|
59
|
+
*
|
|
60
|
+
* @throws {NotAcceptableError} If the data type is not a ComplexType.
|
|
61
|
+
*/
|
|
62
|
+
get dataType() {
|
|
63
|
+
if (!this._dataType)
|
|
64
|
+
this._dataType = this.context.document.node.getComplexType(this._dataType_);
|
|
65
|
+
return this._dataType;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Retrieves the codec for the specified operation.
|
|
69
|
+
*
|
|
70
|
+
* @param operation - The operation to retrieve the encoder for. Valid values are 'create' and 'update'.
|
|
71
|
+
*/
|
|
72
|
+
getInputCodec(operation) {
|
|
73
|
+
let validator = this._inputCodecs[operation];
|
|
74
|
+
if (validator)
|
|
75
|
+
return validator;
|
|
76
|
+
const options = { projection: '*' };
|
|
77
|
+
if (operation === 'update')
|
|
78
|
+
options.partial = 'deep';
|
|
79
|
+
const dataType = this.dataType;
|
|
80
|
+
validator = dataType.generateCodec('decode', options);
|
|
81
|
+
this._inputCodecs[operation] = validator;
|
|
82
|
+
return validator;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Retrieves the codec.
|
|
86
|
+
*/
|
|
87
|
+
getOutputCodec(operation) {
|
|
88
|
+
let validator = this._outputCodecs[operation];
|
|
89
|
+
if (validator)
|
|
90
|
+
return validator;
|
|
91
|
+
const options = { projection: '*', partial: 'deep' };
|
|
92
|
+
const dataType = this.dataType;
|
|
93
|
+
validator = dataType.generateCodec('decode', options);
|
|
94
|
+
this._outputCodecs[operation] = validator;
|
|
95
|
+
return validator;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Retrieves the ElasticSearch client.
|
|
99
|
+
*
|
|
100
|
+
* @protected
|
|
101
|
+
*
|
|
102
|
+
* @throws {Error} If the context or client is not set.
|
|
103
|
+
*/
|
|
104
|
+
getClient() {
|
|
105
|
+
// @ts-ignore
|
|
106
|
+
const db = typeof this.client === 'function' ? this.client(this) : this.client;
|
|
107
|
+
if (!db)
|
|
108
|
+
throw new Error(`Client not set!`);
|
|
109
|
+
return db;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Generates an ID.
|
|
113
|
+
*
|
|
114
|
+
* @protected
|
|
115
|
+
* @returns The generated ID.
|
|
116
|
+
*/
|
|
117
|
+
_generateId() {
|
|
118
|
+
return typeof this.$idGenerator === 'function' ? this.$idGenerator(this) : undefined;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Retrieves the common filter used for querying documents.
|
|
122
|
+
* This method is mostly used for security issues like securing multi-tenant applications.
|
|
123
|
+
*
|
|
124
|
+
* @protected
|
|
125
|
+
* @returns {QueryDslQueryContainer | Promise<QueryDslQueryContainer> | undefined} The common filter or a Promise
|
|
126
|
+
* that resolves to the common filter, or undefined if not available.
|
|
127
|
+
*/
|
|
128
|
+
_getCommonFilter(info) {
|
|
129
|
+
return typeof this.$commonFilter === 'function' ? this.$commonFilter(info, this) : this.$commonFilter;
|
|
130
|
+
}
|
|
131
|
+
async _intercept(callback, info) {
|
|
132
|
+
try {
|
|
133
|
+
if (this.$interceptor)
|
|
134
|
+
return this.$interceptor(callback, info, this);
|
|
135
|
+
return callback();
|
|
136
|
+
}
|
|
137
|
+
catch (e) {
|
|
138
|
+
Error.captureStackTrace(e, this._intercept);
|
|
139
|
+
await this.$onError?.(e, this);
|
|
140
|
+
throw e;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
exports.ElasticService = ElasticService;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/* eslint-disable camelcase */
|
|
2
|
+
import '@opra/core';
|
|
3
|
+
import { OpraFilter } from '@opra/common';
|
|
4
|
+
const isNil = (v) => v == null;
|
|
5
|
+
export default function prepareFilter(ast, negative) {
|
|
6
|
+
if (!ast)
|
|
7
|
+
return;
|
|
8
|
+
if (ast instanceof OpraFilter.QualifiedIdentifier) {
|
|
9
|
+
return ast.value;
|
|
10
|
+
}
|
|
11
|
+
if (ast instanceof OpraFilter.NumberLiteral ||
|
|
12
|
+
ast instanceof OpraFilter.StringLiteral ||
|
|
13
|
+
ast instanceof OpraFilter.BooleanLiteral ||
|
|
14
|
+
ast instanceof OpraFilter.NullLiteral ||
|
|
15
|
+
ast instanceof OpraFilter.DateLiteral ||
|
|
16
|
+
ast instanceof OpraFilter.TimeLiteral) {
|
|
17
|
+
return ast.value;
|
|
18
|
+
}
|
|
19
|
+
if (ast instanceof OpraFilter.ArrayExpression) {
|
|
20
|
+
return ast.items.map(x => prepareFilter(x, negative)).filter(x => !isNil(x));
|
|
21
|
+
}
|
|
22
|
+
if (ast instanceof OpraFilter.NegativeExpression) {
|
|
23
|
+
return prepareFilter(ast.expression, !negative);
|
|
24
|
+
}
|
|
25
|
+
if (ast instanceof OpraFilter.LogicalExpression) {
|
|
26
|
+
const v = ast.items.map(x => prepareFilter(x)).filter(x => !isNil(x));
|
|
27
|
+
if (ast.op === 'and') {
|
|
28
|
+
return {
|
|
29
|
+
bool: {
|
|
30
|
+
[negative ? 'must_not' : 'must']: v,
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
return wrapNot({
|
|
35
|
+
bool: { should: v },
|
|
36
|
+
}, negative);
|
|
37
|
+
}
|
|
38
|
+
if (ast instanceof OpraFilter.ParenthesizedExpression) {
|
|
39
|
+
return prepareFilter(ast.expression, negative);
|
|
40
|
+
}
|
|
41
|
+
if (ast instanceof OpraFilter.ComparisonExpression)
|
|
42
|
+
return _transformComparisonExpression(ast, !!negative);
|
|
43
|
+
throw new Error(`${ast.kind} is not implemented yet`);
|
|
44
|
+
}
|
|
45
|
+
function _transformComparisonExpression(ast, negative) {
|
|
46
|
+
const left = prepareFilter(ast.left, negative);
|
|
47
|
+
if (ast.right instanceof OpraFilter.QualifiedIdentifier) {
|
|
48
|
+
throw new TypeError('not implemented yet');
|
|
49
|
+
}
|
|
50
|
+
const right = prepareFilter(ast.right);
|
|
51
|
+
if (right == null) {
|
|
52
|
+
const op = ast.op === '=' ? (negative ? '!=' : '=') : negative ? '=' : '!=';
|
|
53
|
+
if (op === '=')
|
|
54
|
+
return { bool: { must_not: { exists: { field: left } } } };
|
|
55
|
+
if (op === '!=')
|
|
56
|
+
return { bool: { exists: { field: left } } };
|
|
57
|
+
}
|
|
58
|
+
switch (ast.op) {
|
|
59
|
+
case '=':
|
|
60
|
+
return wrapNot({ term: { [left]: right } }, negative);
|
|
61
|
+
case '!=':
|
|
62
|
+
return wrapNot({ term: { [left]: right } }, !negative);
|
|
63
|
+
case '>':
|
|
64
|
+
return wrapNot({ range: { [left]: { gt: right } } }, negative);
|
|
65
|
+
case '>=':
|
|
66
|
+
return wrapNot({ range: { [left]: { gte: right } } }, negative);
|
|
67
|
+
case '<':
|
|
68
|
+
return wrapNot({ range: { [left]: { lt: right } } }, negative);
|
|
69
|
+
case '<=':
|
|
70
|
+
return wrapNot({ range: { [left]: { lte: right } } }, negative);
|
|
71
|
+
case 'in':
|
|
72
|
+
return wrapNot({ terms: { [left]: Array.isArray(right) ? right : [right] } }, negative);
|
|
73
|
+
case '!in':
|
|
74
|
+
return wrapNot({ terms: { [left]: Array.isArray(right) ? right : [right] } }, !negative);
|
|
75
|
+
case 'like':
|
|
76
|
+
return wrapNot({ wildcard: { [left]: String(right) } }, negative);
|
|
77
|
+
case '!like':
|
|
78
|
+
return wrapNot({ wildcard: { [left]: String(right) } }, !negative);
|
|
79
|
+
case 'ilike':
|
|
80
|
+
return wrapNot({
|
|
81
|
+
wildcard: {
|
|
82
|
+
[left]: {
|
|
83
|
+
value: String(right),
|
|
84
|
+
case_insensitive: true,
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
}, negative);
|
|
88
|
+
case '!ilike':
|
|
89
|
+
return wrapNot({
|
|
90
|
+
wildcard: {
|
|
91
|
+
[left]: {
|
|
92
|
+
value: String(right),
|
|
93
|
+
case_insensitive: true,
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
}, !negative);
|
|
97
|
+
default:
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
throw new Error(`ComparisonExpression operator (${ast.op}) not implemented yet`);
|
|
101
|
+
}
|
|
102
|
+
const wrapNot = (o, negative) => (negative ? { bool: { must_not: o } } : o);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import isPlainObject from 'putil-isplainobject';
|
|
2
|
+
const defaultPrimaryKey = ['_id'];
|
|
3
|
+
export default function prepareKeyValues(keyValue, primaryKey) {
|
|
4
|
+
primaryKey = primaryKey || defaultPrimaryKey;
|
|
5
|
+
const b = isPlainObject(keyValue);
|
|
6
|
+
if (primaryKey.length > 1 && !b) {
|
|
7
|
+
throw new TypeError(`Argument "keyValue" must be an object that contains all key values`);
|
|
8
|
+
}
|
|
9
|
+
if (primaryKey.length > 1 || b) {
|
|
10
|
+
return primaryKey.reduce((o, k) => {
|
|
11
|
+
o[k] = keyValue[k];
|
|
12
|
+
if (o[k] == null)
|
|
13
|
+
throw new Error(`Value of key "${k}" is required`);
|
|
14
|
+
return o;
|
|
15
|
+
}, {});
|
|
16
|
+
}
|
|
17
|
+
return { [primaryKey[0]]: keyValue };
|
|
18
|
+
}
|