@opra/core 0.1.0 → 0.2.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/enums/http-headers.enum.js +3 -2
- package/cjs/implementation/adapter-utils/entity-resource-execute.util.js +36 -34
- package/cjs/implementation/adapter-utils/resource-prepare.util.js +1 -1
- package/cjs/implementation/adapter.js +14 -19
- package/cjs/implementation/express-adapter.js +3 -0
- package/cjs/implementation/headers-map.js +18 -0
- package/cjs/implementation/http-adapter.js +65 -79
- package/cjs/implementation/query-context.js +12 -19
- package/cjs/index.js +0 -2
- package/cjs/services/json-data-service.js +367 -131
- package/cjs/utils/path-to-tree.js +7 -5
- package/esm/enums/http-headers.enum.d.ts +3 -2
- package/esm/enums/http-headers.enum.js +3 -2
- package/esm/implementation/adapter-utils/entity-resource-execute.util.js +35 -33
- package/esm/implementation/adapter-utils/resource-execute.util.d.ts +2 -2
- package/esm/implementation/adapter-utils/resource-prepare.util.d.ts +2 -2
- package/esm/implementation/adapter-utils/resource-prepare.util.js +1 -1
- package/esm/implementation/adapter.d.ts +2 -2
- package/esm/implementation/adapter.js +12 -17
- package/esm/implementation/express-adapter.js +2 -0
- package/esm/implementation/headers-map.d.ts +5 -0
- package/esm/implementation/headers-map.js +14 -0
- package/esm/implementation/http-adapter.d.ts +6 -6
- package/esm/implementation/http-adapter.js +61 -75
- package/esm/implementation/query-context.d.ts +9 -15
- package/esm/implementation/query-context.js +11 -17
- package/esm/index.d.ts +0 -2
- package/esm/index.js +0 -2
- package/esm/interfaces/entity-service.interface.d.ts +8 -6
- package/esm/services/json-data-service.d.ts +56 -39
- package/esm/services/json-data-service.js +365 -129
- package/esm/types.d.ts +0 -3
- package/esm/utils/path-to-tree.d.ts +1 -1
- package/esm/utils/path-to-tree.js +7 -5
- package/i18n/en/error.json +5 -5
- package/package.json +11 -8
- package/cjs/exception/api-exception.js +0 -68
- package/cjs/exception/http-errors/bad-request.error.js +0 -26
- package/cjs/exception/http-errors/failed-dependency.error.js +0 -25
- package/cjs/exception/http-errors/forbidden.error.js +0 -27
- package/cjs/exception/http-errors/internal-server.error.js +0 -27
- package/cjs/exception/http-errors/method-not-allowed.error.js +0 -26
- package/cjs/exception/http-errors/not-acceptable.error.js +0 -26
- package/cjs/exception/http-errors/not-found.error.js +0 -29
- package/cjs/exception/http-errors/unauthorized.error.js +0 -26
- package/cjs/exception/http-errors/unprocessable-entity.error.js +0 -25
- package/cjs/exception/index.js +0 -15
- package/cjs/exception/resource-errors/resource-conflict.error.js +0 -19
- package/cjs/exception/resource-errors/resource-not-found.error.js +0 -19
- package/cjs/exception/wrap-error.js +0 -17
- package/cjs/interfaces/query.interface.js +0 -207
- package/esm/exception/api-exception.d.ts +0 -40
- package/esm/exception/api-exception.js +0 -64
- package/esm/exception/http-errors/bad-request.error.d.ts +0 -10
- package/esm/exception/http-errors/bad-request.error.js +0 -22
- package/esm/exception/http-errors/failed-dependency.error.d.ts +0 -9
- package/esm/exception/http-errors/failed-dependency.error.js +0 -21
- package/esm/exception/http-errors/forbidden.error.d.ts +0 -11
- package/esm/exception/http-errors/forbidden.error.js +0 -23
- package/esm/exception/http-errors/internal-server.error.d.ts +0 -9
- package/esm/exception/http-errors/internal-server.error.js +0 -22
- package/esm/exception/http-errors/method-not-allowed.error.d.ts +0 -10
- package/esm/exception/http-errors/method-not-allowed.error.js +0 -22
- package/esm/exception/http-errors/not-acceptable.error.d.ts +0 -10
- package/esm/exception/http-errors/not-acceptable.error.js +0 -22
- package/esm/exception/http-errors/not-found.error.d.ts +0 -13
- package/esm/exception/http-errors/not-found.error.js +0 -25
- package/esm/exception/http-errors/unauthorized.error.d.ts +0 -10
- package/esm/exception/http-errors/unauthorized.error.js +0 -22
- package/esm/exception/http-errors/unprocessable-entity.error.d.ts +0 -9
- package/esm/exception/http-errors/unprocessable-entity.error.js +0 -21
- package/esm/exception/index.d.ts +0 -12
- package/esm/exception/index.js +0 -12
- package/esm/exception/resource-errors/resource-conflict.error.d.ts +0 -4
- package/esm/exception/resource-errors/resource-conflict.error.js +0 -15
- package/esm/exception/resource-errors/resource-not-found.error.d.ts +0 -4
- package/esm/exception/resource-errors/resource-not-found.error.js +0 -15
- package/esm/exception/wrap-error.d.ts +0 -2
- package/esm/exception/wrap-error.js +0 -13
- package/esm/interfaces/query.interface.d.ts +0 -115
- package/esm/interfaces/query.interface.js +0 -203
|
@@ -1,151 +1,311 @@
|
|
|
1
1
|
import _ from 'lodash';
|
|
2
|
-
import
|
|
2
|
+
import merge from 'putil-merge';
|
|
3
|
+
import { nSQL } from "@nano-sql/core";
|
|
4
|
+
import { BadRequestError, MethodNotAllowedError, ResourceConflictError } from '@opra/exception';
|
|
5
|
+
import { ComplexType, EntityResource } from '@opra/schema';
|
|
3
6
|
import { $parse, ArrayExpression, BooleanLiteral, ComparisonExpression, DateLiteral, Expression, LogicalExpression, NullLiteral, NumberLiteral, ParenthesesExpression, QualifiedIdentifier, StringLiteral, TimeLiteral } from '@opra/url';
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
const createFilterFn = typeof ruleJudgment === 'function'
|
|
7
|
-
? ruleJudgment
|
|
8
|
-
: ruleJudgment.default;
|
|
7
|
+
import { pathToTree } from '../utils/path-to-tree.js';
|
|
8
|
+
let dbId = 1;
|
|
9
9
|
export class JsonDataService {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
10
|
+
resource;
|
|
11
|
+
_status = '';
|
|
12
|
+
_initError;
|
|
13
|
+
_dbName;
|
|
14
|
+
_initData;
|
|
15
|
+
defaultLimit;
|
|
16
|
+
constructor(resource, options) {
|
|
17
|
+
this.resource = resource;
|
|
18
|
+
this.defaultLimit = options?.defaultLimit ?? 10;
|
|
19
|
+
this._initData = options?.data;
|
|
20
|
+
}
|
|
21
|
+
get dataType() {
|
|
22
|
+
return this.resource.dataType;
|
|
23
|
+
}
|
|
24
|
+
get primaryKey() {
|
|
25
|
+
return this.resource.dataType.primaryKey;
|
|
26
|
+
}
|
|
27
|
+
get resourceName() {
|
|
28
|
+
return this.resource.name;
|
|
29
|
+
}
|
|
30
|
+
async close() {
|
|
31
|
+
await this._waitInitializing();
|
|
32
|
+
if (this._status === 'initialized') {
|
|
33
|
+
this._status = 'initializing';
|
|
34
|
+
try {
|
|
35
|
+
await nSQL().disconnect(this._dbName);
|
|
36
|
+
}
|
|
37
|
+
finally {
|
|
38
|
+
this._status = '';
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async processRequest(ctx) {
|
|
43
|
+
const prepared = this._prepare(ctx.query);
|
|
20
44
|
const fn = this[prepared.method];
|
|
21
45
|
if (!fn)
|
|
22
46
|
throw new TypeError(`Unimplemented method (${prepared.method})`);
|
|
47
|
+
// @ts-ignore
|
|
23
48
|
return fn.apply(this, prepared.args);
|
|
24
49
|
}
|
|
25
|
-
get(keyValue, options) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
50
|
+
async get(keyValue, options) {
|
|
51
|
+
await this._init();
|
|
52
|
+
const select = this._convertSelect({
|
|
53
|
+
pick: options?.pick,
|
|
54
|
+
omit: options?.omit,
|
|
55
|
+
include: options?.include,
|
|
56
|
+
});
|
|
57
|
+
nSQL().useDatabase(this._dbName);
|
|
58
|
+
const rows = await nSQL(this.resourceName)
|
|
59
|
+
.query('select', select)
|
|
60
|
+
.where([this.primaryKey, '=', keyValue])
|
|
61
|
+
.exec();
|
|
62
|
+
return unFlatten(rows[0]);
|
|
63
|
+
}
|
|
64
|
+
async count(options) {
|
|
65
|
+
await this._init();
|
|
66
|
+
nSQL().useDatabase(this._dbName);
|
|
67
|
+
const rows = await nSQL(this.resourceName)
|
|
68
|
+
.query('select', ['COUNT(*) as count'])
|
|
69
|
+
.where(options?.filter || [])
|
|
70
|
+
.exec();
|
|
71
|
+
return (rows[0]?.count) || 0;
|
|
72
|
+
}
|
|
73
|
+
async search(options) {
|
|
74
|
+
await this._init();
|
|
75
|
+
const select = this._convertSelect({
|
|
76
|
+
pick: options?.pick,
|
|
77
|
+
omit: options?.omit,
|
|
78
|
+
include: options?.include,
|
|
79
|
+
});
|
|
80
|
+
const filter = this._convertFilter(options?.filter);
|
|
81
|
+
nSQL().useDatabase(this._dbName);
|
|
82
|
+
const query = nSQL(this.resourceName)
|
|
83
|
+
.query('select', select)
|
|
84
|
+
.limit(options?.limit || 10)
|
|
85
|
+
.offset(options?.skip || 0)
|
|
86
|
+
.orderBy(options?.sort || [])
|
|
87
|
+
.where(filter || []);
|
|
88
|
+
return (await query.exec()).map(x => unFlatten(x));
|
|
44
89
|
}
|
|
45
|
-
create(data, options) {
|
|
46
|
-
if (
|
|
90
|
+
async create(data, options) {
|
|
91
|
+
if (!data[this.primaryKey])
|
|
92
|
+
throw new BadRequestError({
|
|
93
|
+
message: 'You must provide primary key value'
|
|
94
|
+
});
|
|
95
|
+
await this._init();
|
|
96
|
+
const keyValue = data[this.primaryKey];
|
|
97
|
+
nSQL().useDatabase(this._dbName);
|
|
98
|
+
const rows = await nSQL(this._initData).query('select', [this.primaryKey])
|
|
99
|
+
.where([this.primaryKey, '=', keyValue])
|
|
100
|
+
.exec();
|
|
101
|
+
if (rows.length)
|
|
47
102
|
throw new ResourceConflictError(this.resourceName, this.primaryKey);
|
|
48
|
-
this.
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
103
|
+
await nSQL(this._initData).query('upsert', data)
|
|
104
|
+
.exec();
|
|
105
|
+
return await this.get(keyValue, options);
|
|
106
|
+
}
|
|
107
|
+
async update(keyValue, data, options) {
|
|
108
|
+
await this._init();
|
|
109
|
+
nSQL().useDatabase(this._dbName);
|
|
110
|
+
await nSQL(this._initData)
|
|
111
|
+
.query('conform rows', (row) => {
|
|
112
|
+
const out = merge({}, row, { deep: true, clone: true });
|
|
113
|
+
merge(out, data, { deep: true });
|
|
114
|
+
return out;
|
|
115
|
+
})
|
|
116
|
+
.where([this.primaryKey, '=', keyValue])
|
|
117
|
+
.exec();
|
|
118
|
+
await nSQL(this._initData).query("rebuild indexes").exec();
|
|
119
|
+
return this.get(keyValue, options);
|
|
120
|
+
}
|
|
121
|
+
async updateMany(data, options) {
|
|
122
|
+
await this._init();
|
|
123
|
+
const filter = this._convertFilter(options?.filter);
|
|
124
|
+
await this._init();
|
|
125
|
+
nSQL().useDatabase(this._dbName);
|
|
126
|
+
let affected = 0;
|
|
127
|
+
await nSQL(this._initData)
|
|
128
|
+
.query('conform rows', (row) => {
|
|
129
|
+
const out = merge({}, row, { deep: true, clone: true });
|
|
130
|
+
merge(out, data, { deep: true });
|
|
131
|
+
affected++;
|
|
132
|
+
return out;
|
|
133
|
+
})
|
|
134
|
+
.where(filter)
|
|
135
|
+
.exec();
|
|
136
|
+
await nSQL(this._initData).query("rebuild indexes").exec();
|
|
137
|
+
return affected;
|
|
138
|
+
}
|
|
139
|
+
async delete(keyValue) {
|
|
140
|
+
await this._init();
|
|
141
|
+
nSQL().useDatabase(this._dbName);
|
|
142
|
+
const result = await nSQL(this._initData)
|
|
143
|
+
.query('delete')
|
|
144
|
+
.where([this.primaryKey, '=', keyValue])
|
|
145
|
+
.exec();
|
|
146
|
+
return !!result.length;
|
|
147
|
+
}
|
|
148
|
+
async deleteMany(options) {
|
|
149
|
+
await this._init();
|
|
150
|
+
const filter = this._convertFilter(options?.filter);
|
|
151
|
+
nSQL().useDatabase(this._dbName);
|
|
152
|
+
const result = await nSQL(this._initData)
|
|
153
|
+
.query('delete')
|
|
154
|
+
.where(filter)
|
|
155
|
+
.exec();
|
|
156
|
+
return result.length;
|
|
157
|
+
}
|
|
158
|
+
async _waitInitializing() {
|
|
159
|
+
if (this._status === 'initializing') {
|
|
160
|
+
return new Promise((resolve, reject) => {
|
|
161
|
+
const reTry = () => setTimeout(() => {
|
|
162
|
+
if (this._status === '')
|
|
163
|
+
return resolve(this._init());
|
|
164
|
+
if (this._status === 'error')
|
|
165
|
+
return reject(this._initError);
|
|
166
|
+
if (this._status === 'initialized')
|
|
167
|
+
return resolve();
|
|
168
|
+
reTry();
|
|
169
|
+
}, 50).unref();
|
|
170
|
+
reTry();
|
|
171
|
+
});
|
|
57
172
|
}
|
|
58
173
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
174
|
+
async _init() {
|
|
175
|
+
await this._waitInitializing();
|
|
176
|
+
if (this._status === 'initialized')
|
|
177
|
+
return;
|
|
178
|
+
this._status = 'initializing';
|
|
179
|
+
this._dbName = 'JsonDataService_DB_' + (dbId++);
|
|
180
|
+
try {
|
|
181
|
+
const model = {
|
|
182
|
+
name: this.resourceName,
|
|
183
|
+
model: {
|
|
184
|
+
'*:any': {}
|
|
185
|
+
},
|
|
186
|
+
indexes: {},
|
|
187
|
+
primaryKey: this.primaryKey
|
|
188
|
+
};
|
|
189
|
+
// Add indexes for sort fields
|
|
190
|
+
const searchMethod = this.resource.metadata.methods.search;
|
|
191
|
+
if (searchMethod) {
|
|
192
|
+
if (searchMethod.sortFields) {
|
|
193
|
+
searchMethod.sortFields.forEach(fieldName => {
|
|
194
|
+
const f = this.dataType.getField(fieldName);
|
|
195
|
+
const fieldType = this.resource.owner.getDataType(f.type || 'string');
|
|
196
|
+
model.indexes[fieldName + ':' + dataTypeToSQLType(fieldType, !!f.isArray)] = {};
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
if (searchMethod.filters) {
|
|
200
|
+
searchMethod.filters.forEach(filter => {
|
|
201
|
+
const f = this.dataType.getField(filter.field);
|
|
202
|
+
const fieldType = this.resource.owner.getDataType(f.type || 'string');
|
|
203
|
+
model.indexes[filter.field + ':' + dataTypeToSQLType(fieldType, !!f.isArray)] = {};
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
await nSQL().createDatabase({
|
|
208
|
+
id: this._dbName,
|
|
209
|
+
version: 3,
|
|
210
|
+
tables: [model]
|
|
211
|
+
});
|
|
212
|
+
this._status = 'initialized';
|
|
213
|
+
if (this._initData) {
|
|
214
|
+
nSQL().useDatabase(this._dbName);
|
|
215
|
+
await nSQL(this.resourceName)
|
|
216
|
+
.query('upsert', this._initData)
|
|
217
|
+
.exec();
|
|
218
|
+
delete this._initData;
|
|
219
|
+
}
|
|
63
220
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const primaryKey = this.primaryKey;
|
|
68
|
-
const i = this.data.findIndex(x => '' + x[primaryKey] === '' + keyValue);
|
|
69
|
-
if (i >= 0) {
|
|
70
|
-
this.data = this.data.slice(i, 1);
|
|
71
|
-
return true;
|
|
221
|
+
catch (e) {
|
|
222
|
+
this._initError = e;
|
|
223
|
+
this._status = 'error';
|
|
72
224
|
}
|
|
73
|
-
return false;
|
|
74
|
-
}
|
|
75
|
-
deleteMany(options) {
|
|
76
|
-
const items = this.search({ filter: options?.filter });
|
|
77
|
-
this.data = this.data.filter(x => !items.includes(x));
|
|
78
|
-
return items.length;
|
|
79
|
-
}
|
|
80
|
-
static filterFields(obj,
|
|
81
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
82
|
-
pick,
|
|
83
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
84
|
-
omit,
|
|
85
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
86
|
-
include) {
|
|
87
|
-
if (!obj)
|
|
88
|
-
return;
|
|
89
|
-
return obj;
|
|
90
225
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
226
|
+
_prepare(query) {
|
|
227
|
+
if (query.resource instanceof EntityResource) {
|
|
228
|
+
if (query.dataType !== this.dataType)
|
|
229
|
+
throw new TypeError(`Query data type (${query.dataType.name}) ` +
|
|
230
|
+
`differs from JsonDataService data type (${this.dataType.name})`);
|
|
231
|
+
}
|
|
232
|
+
switch (query.method) {
|
|
233
|
+
case 'count': {
|
|
94
234
|
const options = _.omitBy({
|
|
95
|
-
|
|
96
|
-
omit: query.omit?.length ? query.omit : undefined,
|
|
97
|
-
include: query.include?.length ? query.include : undefined,
|
|
235
|
+
filter: this._convertFilter(query.filter)
|
|
98
236
|
}, _.isNil);
|
|
99
|
-
const { data } = query;
|
|
100
237
|
return {
|
|
101
|
-
method: query.
|
|
102
|
-
values: data,
|
|
238
|
+
method: query.method,
|
|
103
239
|
options,
|
|
104
|
-
args: [
|
|
240
|
+
args: [options]
|
|
105
241
|
};
|
|
106
242
|
}
|
|
107
|
-
case '
|
|
243
|
+
case 'create': {
|
|
108
244
|
const options = _.omitBy({
|
|
109
|
-
pick: query.pick
|
|
110
|
-
omit: query.omit
|
|
111
|
-
include: query.include
|
|
245
|
+
pick: query.pick,
|
|
246
|
+
omit: query.omit,
|
|
247
|
+
include: query.include
|
|
112
248
|
}, _.isNil);
|
|
113
|
-
const
|
|
249
|
+
const { data } = query;
|
|
114
250
|
return {
|
|
115
|
-
method: query.
|
|
116
|
-
|
|
251
|
+
method: query.method,
|
|
252
|
+
values: data,
|
|
117
253
|
options,
|
|
118
|
-
args: [
|
|
254
|
+
args: [data, options]
|
|
119
255
|
};
|
|
120
256
|
}
|
|
257
|
+
case 'get': {
|
|
258
|
+
if (query.kind === 'GetInstanceQuery') {
|
|
259
|
+
const options = _.omitBy({
|
|
260
|
+
pick: query.pick,
|
|
261
|
+
omit: query.omit,
|
|
262
|
+
include: query.include
|
|
263
|
+
}, _.isNil);
|
|
264
|
+
const keyValue = query.keyValue;
|
|
265
|
+
return {
|
|
266
|
+
method: query.method,
|
|
267
|
+
keyValue,
|
|
268
|
+
options,
|
|
269
|
+
args: [keyValue, options]
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
if (query.kind === 'GetFieldQuery') {
|
|
273
|
+
// todo
|
|
274
|
+
}
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
121
277
|
case 'search': {
|
|
278
|
+
if (query.distinct)
|
|
279
|
+
throw new MethodNotAllowedError({
|
|
280
|
+
message: '$distinct parameter is not supported by JsonDataService'
|
|
281
|
+
});
|
|
122
282
|
const options = _.omitBy({
|
|
123
|
-
pick: query.pick
|
|
124
|
-
omit: query.omit
|
|
125
|
-
include: query.include
|
|
283
|
+
pick: query.pick,
|
|
284
|
+
omit: query.omit,
|
|
285
|
+
include: query.include,
|
|
286
|
+
filter: this._convertFilter(query.filter),
|
|
126
287
|
sort: query.sort?.length ? query.sort : undefined,
|
|
288
|
+
skip: query.skip,
|
|
127
289
|
limit: query.limit,
|
|
128
290
|
offset: query.skip,
|
|
129
|
-
|
|
130
|
-
total: query.count,
|
|
131
|
-
filter: JsonDataService.convertFilter(query.filter)
|
|
291
|
+
count: query.count,
|
|
132
292
|
}, _.isNil);
|
|
133
293
|
return {
|
|
134
|
-
method: query.
|
|
294
|
+
method: query.method,
|
|
135
295
|
options,
|
|
136
296
|
args: [options]
|
|
137
297
|
};
|
|
138
298
|
}
|
|
139
299
|
case 'update': {
|
|
140
300
|
const options = _.omitBy({
|
|
141
|
-
pick: query.pick
|
|
142
|
-
omit: query.omit
|
|
143
|
-
include: query.include
|
|
301
|
+
pick: query.pick,
|
|
302
|
+
omit: query.omit,
|
|
303
|
+
include: query.include
|
|
144
304
|
}, _.isNil);
|
|
145
305
|
const { data } = query;
|
|
146
306
|
const keyValue = query.keyValue;
|
|
147
307
|
return {
|
|
148
|
-
method: query.
|
|
308
|
+
method: query.method,
|
|
149
309
|
keyValue: query.keyValue,
|
|
150
310
|
values: data,
|
|
151
311
|
options,
|
|
@@ -154,11 +314,11 @@ export class JsonDataService {
|
|
|
154
314
|
}
|
|
155
315
|
case 'updateMany': {
|
|
156
316
|
const options = _.omitBy({
|
|
157
|
-
filter:
|
|
317
|
+
filter: this._convertFilter(query.filter)
|
|
158
318
|
}, _.isNil);
|
|
159
319
|
const { data } = query;
|
|
160
320
|
return {
|
|
161
|
-
method: query.
|
|
321
|
+
method: query.method,
|
|
162
322
|
options,
|
|
163
323
|
args: [data, options]
|
|
164
324
|
};
|
|
@@ -167,7 +327,7 @@ export class JsonDataService {
|
|
|
167
327
|
const options = {};
|
|
168
328
|
const keyValue = query.keyValue;
|
|
169
329
|
return {
|
|
170
|
-
method: query.
|
|
330
|
+
method: query.method,
|
|
171
331
|
keyValue,
|
|
172
332
|
options,
|
|
173
333
|
args: [keyValue, options]
|
|
@@ -175,44 +335,70 @@ export class JsonDataService {
|
|
|
175
335
|
}
|
|
176
336
|
case 'deleteMany': {
|
|
177
337
|
const options = _.omitBy({
|
|
178
|
-
filter:
|
|
338
|
+
filter: this._convertFilter(query.filter)
|
|
179
339
|
}, _.isNil);
|
|
180
340
|
return {
|
|
181
|
-
method: query.
|
|
341
|
+
method: query.method,
|
|
182
342
|
options,
|
|
183
343
|
args: [options]
|
|
184
344
|
};
|
|
185
345
|
}
|
|
186
|
-
default:
|
|
187
|
-
throw new Error(`Unimplemented query type "${query.queryType}"`);
|
|
188
346
|
}
|
|
347
|
+
throw new Error(`Unimplemented query type "${query.method}"`);
|
|
348
|
+
}
|
|
349
|
+
_convertSelect(args) {
|
|
350
|
+
const result = [];
|
|
351
|
+
const document = this.dataType.owner;
|
|
352
|
+
const processDataType = (dt, path, pick, omit, include) => {
|
|
353
|
+
let kl;
|
|
354
|
+
for (const [k, f] of dt.fields) {
|
|
355
|
+
kl = k.toLowerCase();
|
|
356
|
+
if (omit?.[kl] === true)
|
|
357
|
+
continue;
|
|
358
|
+
if ((((!pick && !f.exclusive) || pick?.[kl])) || include?.[kl]) {
|
|
359
|
+
const fieldType = document.getDataType(f.type);
|
|
360
|
+
const subPath = (path ? path + '.' : '') + f.name;
|
|
361
|
+
if (fieldType instanceof ComplexType) {
|
|
362
|
+
processDataType(fieldType, subPath, typeof pick?.[kl] === 'object' ? pick?.[kl] : undefined, typeof omit?.[kl] === 'object' ? omit?.[kl] : undefined, typeof include?.[kl] === 'object' ? include?.[kl] : undefined);
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
result.push(subPath);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
processDataType(this.dataType, '', (args.pick ? pathToTree(args.pick, true) : undefined), (args.omit ? pathToTree(args.omit, true) : undefined), (args.include ? pathToTree(args.include, true) : undefined));
|
|
370
|
+
return result;
|
|
189
371
|
}
|
|
190
|
-
|
|
372
|
+
_convertFilter(str) {
|
|
191
373
|
const ast = typeof str === 'string'
|
|
192
374
|
? $parse(str)
|
|
193
375
|
: str;
|
|
194
376
|
if (!ast || !(ast instanceof Expression))
|
|
195
377
|
return ast;
|
|
196
378
|
if (ast instanceof ComparisonExpression) {
|
|
197
|
-
const left =
|
|
198
|
-
const right =
|
|
379
|
+
const left = this._convertFilter(ast.left);
|
|
380
|
+
const right = this._convertFilter(ast.right);
|
|
199
381
|
switch (ast.op) {
|
|
200
382
|
case '=':
|
|
201
|
-
return
|
|
383
|
+
return [left, '=', right];
|
|
202
384
|
case '!=':
|
|
203
|
-
return
|
|
385
|
+
return [left, '!=', right];
|
|
204
386
|
case '>':
|
|
205
|
-
return
|
|
387
|
+
return [left, '>', right];
|
|
206
388
|
case '>=':
|
|
207
|
-
return
|
|
389
|
+
return [left, '>=', right];
|
|
208
390
|
case '<':
|
|
209
|
-
return
|
|
391
|
+
return [left, '<', right];
|
|
210
392
|
case '<=':
|
|
211
|
-
return
|
|
393
|
+
return [left, '<=', right];
|
|
394
|
+
case 'like':
|
|
395
|
+
return [left, 'LIKE', right];
|
|
396
|
+
case '!like':
|
|
397
|
+
return [left, 'NOT LIKE', right];
|
|
212
398
|
case 'in':
|
|
213
|
-
return
|
|
399
|
+
return [left, 'IN', Array.isArray(right) ? right : [right]];
|
|
214
400
|
case '!in':
|
|
215
|
-
return
|
|
401
|
+
return [left, 'NOT IN', Array.isArray(right) ? right : [right]];
|
|
216
402
|
default:
|
|
217
403
|
throw new Error(`ComparisonExpression operator (${ast.op}) not implemented yet`);
|
|
218
404
|
}
|
|
@@ -229,19 +415,69 @@ export class JsonDataService {
|
|
|
229
415
|
return ast.value;
|
|
230
416
|
}
|
|
231
417
|
if (ast instanceof ArrayExpression) {
|
|
232
|
-
return ast.items.map(
|
|
418
|
+
return ast.items.map(item => this._convertFilter(item));
|
|
233
419
|
}
|
|
234
420
|
if (ast instanceof LogicalExpression) {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
421
|
+
return ast.items.map(item => this._convertFilter(item))
|
|
422
|
+
.reduce((a, v) => {
|
|
423
|
+
if (a.length)
|
|
424
|
+
a.push(ast.op.toUpperCase());
|
|
425
|
+
a.push(v);
|
|
426
|
+
return a;
|
|
427
|
+
}, []);
|
|
238
428
|
}
|
|
239
429
|
if (ast instanceof ArrayExpression) {
|
|
240
|
-
return ast.items.map(
|
|
430
|
+
return ast.items.map(item => this._convertFilter(item));
|
|
241
431
|
}
|
|
242
432
|
if (ast instanceof ParenthesesExpression) {
|
|
243
|
-
return
|
|
433
|
+
return this._convertFilter(ast.expression);
|
|
434
|
+
}
|
|
435
|
+
throw new Error(`${ast.kind} is not implemented yet`);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
function unFlatten(input) {
|
|
439
|
+
if (!input)
|
|
440
|
+
return;
|
|
441
|
+
const target = {};
|
|
442
|
+
for (const k of Object.keys(input)) {
|
|
443
|
+
if (k.includes('.')) {
|
|
444
|
+
const keys = k.split('.');
|
|
445
|
+
let o = target;
|
|
446
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
447
|
+
o = o[keys[i]] = o[keys[i]] || {};
|
|
448
|
+
}
|
|
449
|
+
o[keys[keys.length - 1]] = input[k];
|
|
450
|
+
}
|
|
451
|
+
else
|
|
452
|
+
target[k] = input[k];
|
|
453
|
+
}
|
|
454
|
+
return target;
|
|
455
|
+
}
|
|
456
|
+
function dataTypeToSQLType(dataType, isArray) {
|
|
457
|
+
let out = 'any';
|
|
458
|
+
if (dataType.kind !== 'SimpleType')
|
|
459
|
+
out = 'object';
|
|
460
|
+
else {
|
|
461
|
+
switch (dataType.name) {
|
|
462
|
+
case 'booolean':
|
|
463
|
+
case 'number':
|
|
464
|
+
case 'string':
|
|
465
|
+
out = dataType.name;
|
|
466
|
+
break;
|
|
467
|
+
case 'integer':
|
|
468
|
+
out = 'int';
|
|
469
|
+
break;
|
|
470
|
+
case 'date':
|
|
471
|
+
case 'date-time':
|
|
472
|
+
out = 'date';
|
|
473
|
+
break;
|
|
474
|
+
case 'time':
|
|
475
|
+
out = 'string';
|
|
476
|
+
break;
|
|
477
|
+
case 'uuid':
|
|
478
|
+
out = 'uuid';
|
|
479
|
+
break;
|
|
244
480
|
}
|
|
245
|
-
throw new Error(`${ast.type} is not implemented yet`);
|
|
246
481
|
}
|
|
482
|
+
return out + (isArray ? '[]' : '');
|
|
247
483
|
}
|
package/esm/types.d.ts
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
import { Builtin, DeepPickWritable } from 'ts-gems';
|
|
2
|
-
import { OpraSchema } from '@opra/schema';
|
|
3
|
-
export declare type QueryType = OpraSchema.EntityMethodType | 'schema' | 'execute';
|
|
4
|
-
export declare type KeyValue = string | number | boolean | object;
|
|
5
2
|
export declare type EntityInput<T> = DeepNullableIfPartial<DeepPickWritable<T>>;
|
|
6
3
|
export declare type EntityOutput<T> = DeepNullableIfPartial<T>;
|
|
7
4
|
export declare type DeepNullableIfPartial<T> = _DeepNullableIfPartial<T>;
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
const dotPattern = /^([^.]+)\.(.*)$/;
|
|
2
|
-
export function pathToTree(arr) {
|
|
2
|
+
export function pathToTree(arr, lowerCaseKeys) {
|
|
3
3
|
if (!arr.length)
|
|
4
4
|
return;
|
|
5
|
-
return
|
|
5
|
+
return _pathToTree(arr, {}, lowerCaseKeys);
|
|
6
6
|
}
|
|
7
|
-
function
|
|
8
|
-
for (
|
|
7
|
+
function _pathToTree(arr, target, lowerCaseKeys) {
|
|
8
|
+
for (let k of arr) {
|
|
9
|
+
if (lowerCaseKeys)
|
|
10
|
+
k = k.toLowerCase();
|
|
9
11
|
const m = dotPattern.exec(k);
|
|
10
12
|
if (m) {
|
|
11
13
|
const key = m[1];
|
|
12
14
|
if (target[key] === true)
|
|
13
15
|
continue;
|
|
14
16
|
const sub = target[key] = typeof target[key] === 'object' ? target[key] : {};
|
|
15
|
-
|
|
17
|
+
_pathToTree([m[2]], sub);
|
|
16
18
|
}
|
|
17
19
|
else {
|
|
18
20
|
target[k] = true;
|
package/i18n/en/error.json
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
"FAILED_DEPENDENCY": "The request failed due to failure of a previous request",
|
|
4
4
|
"FORBIDDEN": "You are not authorized to perform this action",
|
|
5
5
|
"INTERNAL_SERVER_ERROR": "Internal server error",
|
|
6
|
-
"METHOD_NOT_ALLOWED": "Method
|
|
7
|
-
"NOT_ACCEPTABLE": "Not
|
|
6
|
+
"METHOD_NOT_ALLOWED": "Method not allowed",
|
|
7
|
+
"NOT_ACCEPTABLE": "Not acceptable",
|
|
8
8
|
"NOT_FOUND": "Not found",
|
|
9
9
|
"UNAUTHORIZED": "You have not been authenticated to perform this action",
|
|
10
10
|
"UNPROCESSABLE_ENTITY": "Unprocessable entity",
|
|
11
|
-
|
|
12
|
-
"RESOLVER_FORBIDDEN": "The resource endpoint does not accept '{{queryType}}' operations",
|
|
13
11
|
"RESOURCE_NOT_FOUND": "The resource '{{resource}}' could not be found",
|
|
14
|
-
"RESOURCE_CONFLICT": "There is already an other {{resource}} resource with same field values ({{fields}})"
|
|
12
|
+
"RESOURCE_CONFLICT": "There is already an other {{resource}} resource with same field values ({{fields}})",
|
|
13
|
+
"RESOLVER_FORBIDDEN": "The resource endpoint does not accept '{{method}}' operations",
|
|
14
|
+
"UNACCEPTED_SORT_FIELD": "Field '{{field}}' is not available for sort operation"
|
|
15
15
|
}
|