@opra/core 0.1.1 → 0.3.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 +1 -3
- package/cjs/services/json-collection-service.js +505 -0
- 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.d.ts +2 -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 +5 -5
- package/esm/implementation/adapter.js +12 -17
- package/esm/implementation/express-adapter.d.ts +2 -2
- 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 +10 -16
- package/esm/implementation/query-context.js +11 -17
- package/esm/index.d.ts +1 -3
- package/esm/index.js +1 -3
- package/esm/interfaces/entity-service.interface.d.ts +8 -6
- package/esm/services/json-collection-service.d.ts +85 -0
- package/esm/services/json-collection-service.js +500 -0
- 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 +10 -7
- 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/cjs/services/json-data-service.js +0 -252
- 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
- package/esm/services/json-data-service.d.ts +0 -68
- package/esm/services/json-data-service.js +0 -247
|
@@ -0,0 +1,505 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.JsonCollectionService = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const lodash_1 = tslib_1.__importDefault(require("lodash"));
|
|
6
|
+
const putil_merge_1 = tslib_1.__importDefault(require("putil-merge"));
|
|
7
|
+
const core_1 = require("@nano-sql/core");
|
|
8
|
+
const exception_1 = require("@opra/exception");
|
|
9
|
+
const schema_1 = require("@opra/schema");
|
|
10
|
+
const url_1 = require("@opra/url");
|
|
11
|
+
const path_to_tree_js_1 = require("../utils/path-to-tree.js");
|
|
12
|
+
let dbId = 1;
|
|
13
|
+
const indexingTypes = ['int', 'float', 'number', 'date', 'string'];
|
|
14
|
+
class JsonCollectionService {
|
|
15
|
+
resource;
|
|
16
|
+
_status = '';
|
|
17
|
+
_initError;
|
|
18
|
+
_dbName;
|
|
19
|
+
_initData;
|
|
20
|
+
defaultLimit;
|
|
21
|
+
constructor(resource, options) {
|
|
22
|
+
this.resource = resource;
|
|
23
|
+
if (this.resource.keyFields.length > 1)
|
|
24
|
+
throw new TypeError('JsonDataService currently doesn\'t support multiple primary keys');
|
|
25
|
+
this.defaultLimit = options?.defaultLimit ?? 10;
|
|
26
|
+
this._initData = options?.data;
|
|
27
|
+
}
|
|
28
|
+
get dataType() {
|
|
29
|
+
return this.resource.dataType;
|
|
30
|
+
}
|
|
31
|
+
get primaryKey() {
|
|
32
|
+
return this.resource.keyFields[0];
|
|
33
|
+
}
|
|
34
|
+
get resourceName() {
|
|
35
|
+
return this.resource.name;
|
|
36
|
+
}
|
|
37
|
+
async close() {
|
|
38
|
+
await this._waitInitializing();
|
|
39
|
+
if (this._status === 'initialized') {
|
|
40
|
+
this._status = 'initializing';
|
|
41
|
+
try {
|
|
42
|
+
await (0, core_1.nSQL)().disconnect(this._dbName);
|
|
43
|
+
}
|
|
44
|
+
finally {
|
|
45
|
+
this._status = '';
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async processRequest(ctx) {
|
|
50
|
+
const prepared = this._prepare(ctx.query);
|
|
51
|
+
const fn = this[prepared.method];
|
|
52
|
+
if (!fn)
|
|
53
|
+
throw new TypeError(`Unimplemented method (${prepared.method})`);
|
|
54
|
+
// @ts-ignore
|
|
55
|
+
return fn.apply(this, prepared.args);
|
|
56
|
+
}
|
|
57
|
+
async get(keyValue, options) {
|
|
58
|
+
await this._init();
|
|
59
|
+
const select = this._convertSelect({
|
|
60
|
+
pick: options?.pick,
|
|
61
|
+
omit: options?.omit,
|
|
62
|
+
include: options?.include,
|
|
63
|
+
});
|
|
64
|
+
(0, core_1.nSQL)().useDatabase(this._dbName);
|
|
65
|
+
try {
|
|
66
|
+
const rows = await (0, core_1.nSQL)(this.resourceName)
|
|
67
|
+
.query('select', select)
|
|
68
|
+
.where([this.primaryKey, '=', keyValue])
|
|
69
|
+
.exec();
|
|
70
|
+
return unFlatten(rows[0]);
|
|
71
|
+
}
|
|
72
|
+
catch (e) {
|
|
73
|
+
throw e;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
async count(options) {
|
|
77
|
+
await this._init();
|
|
78
|
+
(0, core_1.nSQL)().useDatabase(this._dbName);
|
|
79
|
+
const rows = await (0, core_1.nSQL)(this.resourceName)
|
|
80
|
+
.query('select', ['COUNT(*) as count'])
|
|
81
|
+
.where(options?.filter || [])
|
|
82
|
+
.exec();
|
|
83
|
+
return (rows[0]?.count) || 0;
|
|
84
|
+
}
|
|
85
|
+
async search(options) {
|
|
86
|
+
await this._init();
|
|
87
|
+
const select = this._convertSelect({
|
|
88
|
+
pick: options?.pick,
|
|
89
|
+
omit: options?.omit,
|
|
90
|
+
include: options?.include,
|
|
91
|
+
});
|
|
92
|
+
const filter = this._convertFilter(options?.filter);
|
|
93
|
+
(0, core_1.nSQL)().useDatabase(this._dbName);
|
|
94
|
+
const query = (0, core_1.nSQL)(this.resourceName)
|
|
95
|
+
.query('select', select)
|
|
96
|
+
.limit(options?.limit || 10)
|
|
97
|
+
.offset(options?.skip || 0)
|
|
98
|
+
.orderBy(options?.sort || [])
|
|
99
|
+
.where(filter || []);
|
|
100
|
+
return (await query.exec()).map(x => unFlatten(x));
|
|
101
|
+
}
|
|
102
|
+
async create(data, options) {
|
|
103
|
+
if (!data[this.primaryKey])
|
|
104
|
+
throw new exception_1.BadRequestError({
|
|
105
|
+
message: 'You must provide primary key value'
|
|
106
|
+
});
|
|
107
|
+
await this._init();
|
|
108
|
+
const keyValue = data[this.primaryKey];
|
|
109
|
+
(0, core_1.nSQL)().useDatabase(this._dbName);
|
|
110
|
+
const rows = await (0, core_1.nSQL)(this.resourceName).query('select', [this.primaryKey])
|
|
111
|
+
.where([this.primaryKey, '=', keyValue])
|
|
112
|
+
.exec();
|
|
113
|
+
if (rows.length)
|
|
114
|
+
throw new exception_1.ResourceConflictError(this.resourceName, this.primaryKey);
|
|
115
|
+
await (0, core_1.nSQL)(this.resourceName).query('upsert', data)
|
|
116
|
+
.exec();
|
|
117
|
+
return await this.get(keyValue, options);
|
|
118
|
+
}
|
|
119
|
+
async update(keyValue, data, options) {
|
|
120
|
+
await this._init();
|
|
121
|
+
(0, core_1.nSQL)().useDatabase(this._dbName);
|
|
122
|
+
await (0, core_1.nSQL)(this.resourceName)
|
|
123
|
+
.query('conform rows', (row) => {
|
|
124
|
+
const out = (0, putil_merge_1.default)({}, row, { deep: true, clone: true });
|
|
125
|
+
(0, putil_merge_1.default)(out, data, { deep: true });
|
|
126
|
+
return out;
|
|
127
|
+
})
|
|
128
|
+
.where([this.primaryKey, '=', keyValue])
|
|
129
|
+
.exec();
|
|
130
|
+
// await nSQL(this.resourceName).query("rebuild indexes").exec();
|
|
131
|
+
return this.get(keyValue, options);
|
|
132
|
+
}
|
|
133
|
+
async updateMany(data, options) {
|
|
134
|
+
await this._init();
|
|
135
|
+
const filter = this._convertFilter(options?.filter);
|
|
136
|
+
(0, core_1.nSQL)().useDatabase(this._dbName);
|
|
137
|
+
let affected = 0;
|
|
138
|
+
await (0, core_1.nSQL)(this.resourceName)
|
|
139
|
+
.query('conform rows', (row) => {
|
|
140
|
+
const out = (0, putil_merge_1.default)({}, row, { deep: true, clone: true });
|
|
141
|
+
(0, putil_merge_1.default)(out, data, { deep: true });
|
|
142
|
+
affected++;
|
|
143
|
+
return out;
|
|
144
|
+
})
|
|
145
|
+
.where(filter)
|
|
146
|
+
.exec();
|
|
147
|
+
// await nSQL(this.resourceName).query("rebuild indexes").exec();
|
|
148
|
+
return affected;
|
|
149
|
+
}
|
|
150
|
+
async delete(keyValue) {
|
|
151
|
+
await this._init();
|
|
152
|
+
(0, core_1.nSQL)().useDatabase(this._dbName);
|
|
153
|
+
const result = await (0, core_1.nSQL)(this.resourceName)
|
|
154
|
+
.query('delete')
|
|
155
|
+
.where([this.primaryKey, '=', keyValue])
|
|
156
|
+
.exec();
|
|
157
|
+
return !!result.length;
|
|
158
|
+
}
|
|
159
|
+
async deleteMany(options) {
|
|
160
|
+
await this._init();
|
|
161
|
+
const filter = this._convertFilter(options?.filter);
|
|
162
|
+
(0, core_1.nSQL)().useDatabase(this._dbName);
|
|
163
|
+
const result = await (0, core_1.nSQL)(this.resourceName)
|
|
164
|
+
.query('delete')
|
|
165
|
+
.where(filter)
|
|
166
|
+
.exec();
|
|
167
|
+
return result.length;
|
|
168
|
+
}
|
|
169
|
+
async _waitInitializing() {
|
|
170
|
+
if (this._status === 'initializing') {
|
|
171
|
+
return new Promise((resolve, reject) => {
|
|
172
|
+
const reTry = () => setTimeout(() => {
|
|
173
|
+
if (this._status === '')
|
|
174
|
+
return resolve(this._init());
|
|
175
|
+
if (this._status === 'error')
|
|
176
|
+
return reject(this._initError);
|
|
177
|
+
if (this._status === 'initialized')
|
|
178
|
+
return resolve();
|
|
179
|
+
reTry();
|
|
180
|
+
}, 50).unref();
|
|
181
|
+
reTry();
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
async _init() {
|
|
186
|
+
await this._waitInitializing();
|
|
187
|
+
if (this._status === 'initialized')
|
|
188
|
+
return;
|
|
189
|
+
this._status = 'initializing';
|
|
190
|
+
this._dbName = 'JsonDataService_DB_' + (dbId++);
|
|
191
|
+
try {
|
|
192
|
+
const table = {
|
|
193
|
+
name: this.resourceName,
|
|
194
|
+
model: {},
|
|
195
|
+
indexes: {}
|
|
196
|
+
};
|
|
197
|
+
for (const [k, f] of this.resource.dataType.fields.entries()) {
|
|
198
|
+
const fieldType = this.resource.owner.getDataType(f.type || 'string');
|
|
199
|
+
const o = table.model[k + ':' + dataTypeToSQLType(fieldType, !!f.isArray)] = {};
|
|
200
|
+
if (k === this.primaryKey)
|
|
201
|
+
o.pk = true;
|
|
202
|
+
}
|
|
203
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
204
|
+
const indexes = table.indexes;
|
|
205
|
+
// Add indexes for sort fields
|
|
206
|
+
const searchMethod = this.resource.metadata.methods.search;
|
|
207
|
+
if (searchMethod) {
|
|
208
|
+
if (searchMethod.sortFields) {
|
|
209
|
+
searchMethod.sortFields.forEach(fieldName => {
|
|
210
|
+
const f = this.dataType.getField(fieldName);
|
|
211
|
+
const fieldType = this.resource.owner.getDataType(f.type || 'string');
|
|
212
|
+
const t = dataTypeToSQLType(fieldType, !!f.isArray);
|
|
213
|
+
if (indexingTypes.includes(t))
|
|
214
|
+
indexes[fieldName + ':' + t] = {};
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
if (searchMethod.filters) {
|
|
218
|
+
searchMethod.filters.forEach(filter => {
|
|
219
|
+
const f = this.dataType.getField(filter.field);
|
|
220
|
+
const fieldType = this.resource.owner.getDataType(f.type || 'string');
|
|
221
|
+
const t = dataTypeToSQLType(fieldType, !!f.isArray);
|
|
222
|
+
if (indexingTypes.includes(t))
|
|
223
|
+
indexes[filter.field + ':' + t] = {};
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
await (0, core_1.nSQL)().createDatabase({
|
|
228
|
+
id: this._dbName,
|
|
229
|
+
version: 3,
|
|
230
|
+
tables: [table]
|
|
231
|
+
});
|
|
232
|
+
this._status = 'initialized';
|
|
233
|
+
if (this._initData) {
|
|
234
|
+
(0, core_1.nSQL)().useDatabase(this._dbName);
|
|
235
|
+
await (0, core_1.nSQL)(this.resourceName)
|
|
236
|
+
.query('upsert', this._initData)
|
|
237
|
+
.exec();
|
|
238
|
+
delete this._initData;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
catch (e) {
|
|
242
|
+
this._initError = e;
|
|
243
|
+
this._status = 'error';
|
|
244
|
+
throw e;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
_prepare(query) {
|
|
248
|
+
if (query.resource instanceof schema_1.EntityResource) {
|
|
249
|
+
if (query.dataType !== this.dataType)
|
|
250
|
+
throw new TypeError(`Query data type (${query.dataType.name}) ` +
|
|
251
|
+
`differs from JsonDataService data type (${this.dataType.name})`);
|
|
252
|
+
}
|
|
253
|
+
switch (query.method) {
|
|
254
|
+
case 'count': {
|
|
255
|
+
const options = lodash_1.default.omitBy({
|
|
256
|
+
filter: this._convertFilter(query.filter)
|
|
257
|
+
}, lodash_1.default.isNil);
|
|
258
|
+
return {
|
|
259
|
+
method: query.method,
|
|
260
|
+
options,
|
|
261
|
+
args: [options]
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
case 'create': {
|
|
265
|
+
const options = lodash_1.default.omitBy({
|
|
266
|
+
pick: query.pick,
|
|
267
|
+
omit: query.omit,
|
|
268
|
+
include: query.include
|
|
269
|
+
}, lodash_1.default.isNil);
|
|
270
|
+
const { data } = query;
|
|
271
|
+
return {
|
|
272
|
+
method: query.method,
|
|
273
|
+
values: data,
|
|
274
|
+
options,
|
|
275
|
+
args: [data, options]
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
case 'get': {
|
|
279
|
+
if (query.kind === 'GetInstanceQuery') {
|
|
280
|
+
const options = lodash_1.default.omitBy({
|
|
281
|
+
pick: query.pick,
|
|
282
|
+
omit: query.omit,
|
|
283
|
+
include: query.include
|
|
284
|
+
}, lodash_1.default.isNil);
|
|
285
|
+
const keyValue = query.keyValue;
|
|
286
|
+
return {
|
|
287
|
+
method: query.method,
|
|
288
|
+
keyValue,
|
|
289
|
+
options,
|
|
290
|
+
args: [keyValue, options]
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
if (query.kind === 'GetFieldQuery') {
|
|
294
|
+
// todo
|
|
295
|
+
}
|
|
296
|
+
break;
|
|
297
|
+
}
|
|
298
|
+
case 'search': {
|
|
299
|
+
if (query.distinct)
|
|
300
|
+
throw new exception_1.MethodNotAllowedError({
|
|
301
|
+
message: '$distinct parameter is not supported by JsonDataService'
|
|
302
|
+
});
|
|
303
|
+
const options = lodash_1.default.omitBy({
|
|
304
|
+
pick: query.pick,
|
|
305
|
+
omit: query.omit,
|
|
306
|
+
include: query.include,
|
|
307
|
+
filter: this._convertFilter(query.filter),
|
|
308
|
+
sort: query.sort?.length ? query.sort : undefined,
|
|
309
|
+
skip: query.skip,
|
|
310
|
+
limit: query.limit,
|
|
311
|
+
offset: query.skip,
|
|
312
|
+
count: query.count,
|
|
313
|
+
}, lodash_1.default.isNil);
|
|
314
|
+
return {
|
|
315
|
+
method: query.method,
|
|
316
|
+
options,
|
|
317
|
+
args: [options]
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
case 'update': {
|
|
321
|
+
const options = lodash_1.default.omitBy({
|
|
322
|
+
pick: query.pick,
|
|
323
|
+
omit: query.omit,
|
|
324
|
+
include: query.include
|
|
325
|
+
}, lodash_1.default.isNil);
|
|
326
|
+
const { data } = query;
|
|
327
|
+
const keyValue = query.keyValue;
|
|
328
|
+
return {
|
|
329
|
+
method: query.method,
|
|
330
|
+
keyValue: query.keyValue,
|
|
331
|
+
values: data,
|
|
332
|
+
options,
|
|
333
|
+
args: [keyValue, data, options]
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
case 'updateMany': {
|
|
337
|
+
const options = lodash_1.default.omitBy({
|
|
338
|
+
filter: this._convertFilter(query.filter)
|
|
339
|
+
}, lodash_1.default.isNil);
|
|
340
|
+
const { data } = query;
|
|
341
|
+
return {
|
|
342
|
+
method: query.method,
|
|
343
|
+
options,
|
|
344
|
+
args: [data, options]
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
case 'delete': {
|
|
348
|
+
const options = {};
|
|
349
|
+
const keyValue = query.keyValue;
|
|
350
|
+
return {
|
|
351
|
+
method: query.method,
|
|
352
|
+
keyValue,
|
|
353
|
+
options,
|
|
354
|
+
args: [keyValue, options]
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
case 'deleteMany': {
|
|
358
|
+
const options = lodash_1.default.omitBy({
|
|
359
|
+
filter: this._convertFilter(query.filter)
|
|
360
|
+
}, lodash_1.default.isNil);
|
|
361
|
+
return {
|
|
362
|
+
method: query.method,
|
|
363
|
+
options,
|
|
364
|
+
args: [options]
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
throw new Error(`Unimplemented query type "${query.method}"`);
|
|
369
|
+
}
|
|
370
|
+
_convertSelect(args) {
|
|
371
|
+
const result = [];
|
|
372
|
+
const document = this.dataType.owner;
|
|
373
|
+
const processDataType = (dt, path, pick, omit, include) => {
|
|
374
|
+
let kl;
|
|
375
|
+
for (const [k, f] of dt.fields) {
|
|
376
|
+
kl = k.toLowerCase();
|
|
377
|
+
if (omit?.[kl] === true)
|
|
378
|
+
continue;
|
|
379
|
+
if ((((!pick && !f.exclusive) || pick?.[kl])) || include?.[kl]) {
|
|
380
|
+
const fieldType = document.getDataType(f.type);
|
|
381
|
+
const subPath = (path ? path + '.' : '') + f.name;
|
|
382
|
+
if (fieldType instanceof schema_1.ComplexType) {
|
|
383
|
+
processDataType(fieldType, subPath, typeof pick?.[kl] === 'object' ? pick?.[kl] : undefined, typeof omit?.[kl] === 'object' ? omit?.[kl] : undefined, typeof include?.[kl] === 'object' ? include?.[kl] : undefined);
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
result.push(subPath);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
processDataType(this.dataType, '', (args.pick ? (0, path_to_tree_js_1.pathToTree)(args.pick, true) : undefined), (args.omit ? (0, path_to_tree_js_1.pathToTree)(args.omit, true) : undefined), (args.include ? (0, path_to_tree_js_1.pathToTree)(args.include, true) : undefined));
|
|
391
|
+
return result;
|
|
392
|
+
}
|
|
393
|
+
_convertFilter(str) {
|
|
394
|
+
const ast = typeof str === 'string'
|
|
395
|
+
? (0, url_1.$parse)(str)
|
|
396
|
+
: str;
|
|
397
|
+
if (!ast || !(ast instanceof url_1.Expression))
|
|
398
|
+
return ast;
|
|
399
|
+
if (ast instanceof url_1.ComparisonExpression) {
|
|
400
|
+
const left = this._convertFilter(ast.left);
|
|
401
|
+
const right = this._convertFilter(ast.right);
|
|
402
|
+
switch (ast.op) {
|
|
403
|
+
case '=':
|
|
404
|
+
return [left, '=', right];
|
|
405
|
+
case '!=':
|
|
406
|
+
return [left, '!=', right];
|
|
407
|
+
case '>':
|
|
408
|
+
return [left, '>', right];
|
|
409
|
+
case '>=':
|
|
410
|
+
return [left, '>=', right];
|
|
411
|
+
case '<':
|
|
412
|
+
return [left, '<', right];
|
|
413
|
+
case '<=':
|
|
414
|
+
return [left, '<=', right];
|
|
415
|
+
case 'like':
|
|
416
|
+
return [left, 'LIKE', right];
|
|
417
|
+
case '!like':
|
|
418
|
+
return [left, 'NOT LIKE', right];
|
|
419
|
+
case 'in':
|
|
420
|
+
return [left, 'IN', Array.isArray(right) ? right : [right]];
|
|
421
|
+
case '!in':
|
|
422
|
+
return [left, 'NOT IN', Array.isArray(right) ? right : [right]];
|
|
423
|
+
default:
|
|
424
|
+
throw new Error(`ComparisonExpression operator (${ast.op}) not implemented yet`);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
if (ast instanceof url_1.QualifiedIdentifier) {
|
|
428
|
+
return ast.value;
|
|
429
|
+
}
|
|
430
|
+
if (ast instanceof url_1.NumberLiteral ||
|
|
431
|
+
ast instanceof url_1.StringLiteral ||
|
|
432
|
+
ast instanceof url_1.BooleanLiteral ||
|
|
433
|
+
ast instanceof url_1.NullLiteral ||
|
|
434
|
+
ast instanceof url_1.DateLiteral ||
|
|
435
|
+
ast instanceof url_1.TimeLiteral) {
|
|
436
|
+
return ast.value;
|
|
437
|
+
}
|
|
438
|
+
if (ast instanceof url_1.ArrayExpression) {
|
|
439
|
+
return ast.items.map(item => this._convertFilter(item));
|
|
440
|
+
}
|
|
441
|
+
if (ast instanceof url_1.LogicalExpression) {
|
|
442
|
+
return ast.items.map(item => this._convertFilter(item))
|
|
443
|
+
.reduce((a, v) => {
|
|
444
|
+
if (a.length)
|
|
445
|
+
a.push(ast.op.toUpperCase());
|
|
446
|
+
a.push(v);
|
|
447
|
+
return a;
|
|
448
|
+
}, []);
|
|
449
|
+
}
|
|
450
|
+
if (ast instanceof url_1.ArrayExpression) {
|
|
451
|
+
return ast.items.map(item => this._convertFilter(item));
|
|
452
|
+
}
|
|
453
|
+
if (ast instanceof url_1.ParenthesesExpression) {
|
|
454
|
+
return this._convertFilter(ast.expression);
|
|
455
|
+
}
|
|
456
|
+
throw new Error(`${ast.kind} is not implemented yet`);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
exports.JsonCollectionService = JsonCollectionService;
|
|
460
|
+
function unFlatten(input) {
|
|
461
|
+
if (!input)
|
|
462
|
+
return;
|
|
463
|
+
const target = {};
|
|
464
|
+
for (const k of Object.keys(input)) {
|
|
465
|
+
if (k.includes('.')) {
|
|
466
|
+
const keys = k.split('.');
|
|
467
|
+
let o = target;
|
|
468
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
469
|
+
o = o[keys[i]] = o[keys[i]] || {};
|
|
470
|
+
}
|
|
471
|
+
o[keys[keys.length - 1]] = input[k];
|
|
472
|
+
}
|
|
473
|
+
else
|
|
474
|
+
target[k] = input[k];
|
|
475
|
+
}
|
|
476
|
+
return target;
|
|
477
|
+
}
|
|
478
|
+
function dataTypeToSQLType(dataType, isArray) {
|
|
479
|
+
let out = 'any';
|
|
480
|
+
if (dataType.kind !== 'SimpleType')
|
|
481
|
+
out = 'object';
|
|
482
|
+
else {
|
|
483
|
+
switch (dataType.name) {
|
|
484
|
+
case 'boolean':
|
|
485
|
+
case 'number':
|
|
486
|
+
case 'string':
|
|
487
|
+
out = dataType.name;
|
|
488
|
+
break;
|
|
489
|
+
case 'integer':
|
|
490
|
+
out = 'int';
|
|
491
|
+
break;
|
|
492
|
+
// case 'date': //there is bug in nano-sql.
|
|
493
|
+
// case 'date-time':
|
|
494
|
+
// out = 'date';
|
|
495
|
+
// break;
|
|
496
|
+
case 'time':
|
|
497
|
+
out = 'string';
|
|
498
|
+
break;
|
|
499
|
+
case 'uuid':
|
|
500
|
+
out = 'uuid';
|
|
501
|
+
break;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
return out + (isArray ? '[]' : '');
|
|
505
|
+
}
|
|
@@ -2,21 +2,23 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.pathToTree = void 0;
|
|
4
4
|
const dotPattern = /^([^.]+)\.(.*)$/;
|
|
5
|
-
function pathToTree(arr) {
|
|
5
|
+
function pathToTree(arr, lowerCaseKeys) {
|
|
6
6
|
if (!arr.length)
|
|
7
7
|
return;
|
|
8
|
-
return
|
|
8
|
+
return _pathToTree(arr, {}, lowerCaseKeys);
|
|
9
9
|
}
|
|
10
10
|
exports.pathToTree = pathToTree;
|
|
11
|
-
function
|
|
12
|
-
for (
|
|
11
|
+
function _pathToTree(arr, target, lowerCaseKeys) {
|
|
12
|
+
for (let k of arr) {
|
|
13
|
+
if (lowerCaseKeys)
|
|
14
|
+
k = k.toLowerCase();
|
|
13
15
|
const m = dotPattern.exec(k);
|
|
14
16
|
if (m) {
|
|
15
17
|
const key = m[1];
|
|
16
18
|
if (target[key] === true)
|
|
17
19
|
continue;
|
|
18
20
|
const sub = target[key] = typeof target[key] === 'object' ? target[key] : {};
|
|
19
|
-
|
|
21
|
+
_pathToTree([m[2]], sub);
|
|
20
22
|
}
|
|
21
23
|
else {
|
|
22
24
|
target[k] = true;
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
* https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers#controls
|
|
3
3
|
*/
|
|
4
4
|
export declare enum HttpHeaders {
|
|
5
|
-
X_Opra_Version = "X-
|
|
6
|
-
X_Opra_Schema = "X-
|
|
5
|
+
X_Opra_Version = "X-Opra-Version",
|
|
6
|
+
X_Opra_Schema = "X-Opra-Schema",
|
|
7
|
+
X_Opra_Count = "X-Opra-Count",
|
|
7
8
|
/**
|
|
8
9
|
* Defines the authentication method that should be used to access a resource.
|
|
9
10
|
*/
|
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
export var HttpHeaders;
|
|
6
6
|
(function (HttpHeaders) {
|
|
7
7
|
/* *** Custom Headers *** */
|
|
8
|
-
HttpHeaders["X_Opra_Version"] = "X-
|
|
9
|
-
HttpHeaders["X_Opra_Schema"] = "X-
|
|
8
|
+
HttpHeaders["X_Opra_Version"] = "X-Opra-Version";
|
|
9
|
+
HttpHeaders["X_Opra_Schema"] = "X-Opra-Schema";
|
|
10
|
+
HttpHeaders["X_Opra_Count"] = "X-Opra-Count";
|
|
10
11
|
/* *** Authentication *** */
|
|
11
12
|
/**
|
|
12
13
|
* Defines the authentication method that should be used to access a resource.
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { EntityResource,
|
|
1
|
+
import { EntityResource, OpraApi } from '@opra/schema';
|
|
2
2
|
import { QueryContext } from '../query-context.js';
|
|
3
|
-
export declare function entityResourceExecute(service:
|
|
3
|
+
export declare function entityResourceExecute(service: OpraApi, resource: EntityResource, context: QueryContext): Promise<void>;
|
|
@@ -1,44 +1,43 @@
|
|
|
1
|
+
import { ForbiddenError, ResourceNotFoundError } from '@opra/exception';
|
|
1
2
|
import { translate } from '@opra/i18n';
|
|
2
|
-
import { ComplexType } from '@opra/schema';
|
|
3
|
+
import { ComplexType, OpraCountCollectionQuery } from '@opra/schema';
|
|
3
4
|
import { HttpHeaders } from '../../enums/index.js';
|
|
4
|
-
import { ForbiddenError, ResourceNotFoundError } from '../../exception/index.js';
|
|
5
|
-
import { OpraQuery } from '../../interfaces/query.interface.js';
|
|
6
5
|
export async function entityResourceExecute(service, resource, context) {
|
|
7
6
|
const { query } = context;
|
|
8
|
-
if (
|
|
7
|
+
if (query.kind === 'SearchCollectionQuery') {
|
|
9
8
|
const promises = [];
|
|
10
9
|
let search;
|
|
11
|
-
|
|
12
|
-
promises.push(executeFn(service, resource, context, query.queryType)
|
|
10
|
+
promises.push(executeFn(service, resource, context)
|
|
13
11
|
.then(v => search = v));
|
|
14
|
-
if (query.count) {
|
|
15
|
-
|
|
16
|
-
.
|
|
12
|
+
if (query.count && resource.metadata.methods.count) {
|
|
13
|
+
const ctx = {
|
|
14
|
+
query: new OpraCountCollectionQuery(query.resource, { filter: query.filter }),
|
|
15
|
+
resultPath: ''
|
|
16
|
+
};
|
|
17
|
+
Object.setPrototypeOf(ctx, context);
|
|
18
|
+
promises.push(executeFn(service, resource, ctx));
|
|
17
19
|
}
|
|
18
20
|
await Promise.all(promises);
|
|
19
|
-
context.response
|
|
20
|
-
...search,
|
|
21
|
-
...count
|
|
22
|
-
};
|
|
21
|
+
context.response = search;
|
|
23
22
|
return;
|
|
24
23
|
}
|
|
25
|
-
context.response
|
|
24
|
+
context.response = await executeFn(service, resource, context);
|
|
26
25
|
}
|
|
27
|
-
async function executeFn(service, resource, context
|
|
28
|
-
const
|
|
29
|
-
|
|
26
|
+
async function executeFn(service, resource, context) {
|
|
27
|
+
const method = context.query.method;
|
|
28
|
+
const resolverInfo = resource.metadata.methods?.[method];
|
|
29
|
+
if (!(resolverInfo && resolverInfo.handler))
|
|
30
30
|
throw new ForbiddenError({
|
|
31
|
-
message: translate('RESOLVER_FORBIDDEN', {
|
|
31
|
+
message: translate('RESOLVER_FORBIDDEN', { method }, `The resource endpoint does not accept '{{method}}' operations`),
|
|
32
32
|
severity: 'error',
|
|
33
33
|
code: 'RESOLVER_FORBIDDEN'
|
|
34
34
|
});
|
|
35
35
|
let result = await resolverInfo.handler(context);
|
|
36
|
-
switch (
|
|
36
|
+
switch (method) {
|
|
37
37
|
case 'search':
|
|
38
|
-
context.response
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
};
|
|
38
|
+
const items = Array.isArray(result) ? result : (context.response ? [result] : []);
|
|
39
|
+
context.responseHeaders.set(HttpHeaders.X_Opra_Schema, resource.dataType.name);
|
|
40
|
+
return items;
|
|
42
41
|
case 'get':
|
|
43
42
|
case 'update':
|
|
44
43
|
if (!result) {
|
|
@@ -47,18 +46,22 @@ async function executeFn(service, resource, context, queryType) {
|
|
|
47
46
|
}
|
|
48
47
|
break;
|
|
49
48
|
case 'count':
|
|
50
|
-
|
|
49
|
+
context.responseHeaders.set(HttpHeaders.X_Opra_Count, result);
|
|
50
|
+
return;
|
|
51
51
|
case 'delete':
|
|
52
52
|
case 'deleteMany':
|
|
53
53
|
case 'updateMany':
|
|
54
|
-
let
|
|
54
|
+
let affected;
|
|
55
55
|
if (typeof result === 'number')
|
|
56
|
-
|
|
56
|
+
affected = result;
|
|
57
57
|
if (typeof result === 'boolean')
|
|
58
|
-
|
|
58
|
+
affected = result ? 1 : 0;
|
|
59
59
|
if (typeof result === 'object')
|
|
60
|
-
|
|
61
|
-
return {
|
|
60
|
+
affected = result.affectedRows || result.affected;
|
|
61
|
+
return {
|
|
62
|
+
operation: context.query.method,
|
|
63
|
+
affected
|
|
64
|
+
};
|
|
62
65
|
}
|
|
63
66
|
if (!result)
|
|
64
67
|
return;
|
|
@@ -72,9 +75,8 @@ async function executeFn(service, resource, context, queryType) {
|
|
|
72
75
|
result = result && typeof result === 'object' && result[field];
|
|
73
76
|
}
|
|
74
77
|
}
|
|
75
|
-
if (
|
|
76
|
-
context.
|
|
77
|
-
|
|
78
|
-
context.response.headers.set(HttpHeaders.X_Opra_Schema, '/$schema/types/' + dataType.name);
|
|
78
|
+
if (method === 'create')
|
|
79
|
+
context.status = 201;
|
|
80
|
+
context.responseHeaders.set(HttpHeaders.X_Opra_Schema, resource.dataType.name);
|
|
79
81
|
return result;
|
|
80
82
|
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { OpraApi, OpraResource } from '@opra/schema';
|
|
2
2
|
import { QueryContext } from '../query-context.js';
|
|
3
|
-
export declare function resourceExecute(service:
|
|
3
|
+
export declare function resourceExecute(service: OpraApi, resource: OpraResource, context: QueryContext): Promise<void>;
|