@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.
Files changed (86) hide show
  1. package/cjs/enums/http-headers.enum.js +3 -2
  2. package/cjs/implementation/adapter-utils/entity-resource-execute.util.js +36 -34
  3. package/cjs/implementation/adapter-utils/resource-prepare.util.js +1 -1
  4. package/cjs/implementation/adapter.js +14 -19
  5. package/cjs/implementation/express-adapter.js +3 -0
  6. package/cjs/implementation/headers-map.js +18 -0
  7. package/cjs/implementation/http-adapter.js +65 -79
  8. package/cjs/implementation/query-context.js +12 -19
  9. package/cjs/index.js +1 -3
  10. package/cjs/services/json-collection-service.js +505 -0
  11. package/cjs/utils/path-to-tree.js +7 -5
  12. package/esm/enums/http-headers.enum.d.ts +3 -2
  13. package/esm/enums/http-headers.enum.js +3 -2
  14. package/esm/implementation/adapter-utils/entity-resource-execute.util.d.ts +2 -2
  15. package/esm/implementation/adapter-utils/entity-resource-execute.util.js +35 -33
  16. package/esm/implementation/adapter-utils/resource-execute.util.d.ts +2 -2
  17. package/esm/implementation/adapter-utils/resource-prepare.util.d.ts +2 -2
  18. package/esm/implementation/adapter-utils/resource-prepare.util.js +1 -1
  19. package/esm/implementation/adapter.d.ts +5 -5
  20. package/esm/implementation/adapter.js +12 -17
  21. package/esm/implementation/express-adapter.d.ts +2 -2
  22. package/esm/implementation/express-adapter.js +2 -0
  23. package/esm/implementation/headers-map.d.ts +5 -0
  24. package/esm/implementation/headers-map.js +14 -0
  25. package/esm/implementation/http-adapter.d.ts +6 -6
  26. package/esm/implementation/http-adapter.js +61 -75
  27. package/esm/implementation/query-context.d.ts +10 -16
  28. package/esm/implementation/query-context.js +11 -17
  29. package/esm/index.d.ts +1 -3
  30. package/esm/index.js +1 -3
  31. package/esm/interfaces/entity-service.interface.d.ts +8 -6
  32. package/esm/services/json-collection-service.d.ts +85 -0
  33. package/esm/services/json-collection-service.js +500 -0
  34. package/esm/types.d.ts +0 -3
  35. package/esm/utils/path-to-tree.d.ts +1 -1
  36. package/esm/utils/path-to-tree.js +7 -5
  37. package/i18n/en/error.json +5 -5
  38. package/package.json +10 -7
  39. package/cjs/exception/api-exception.js +0 -68
  40. package/cjs/exception/http-errors/bad-request.error.js +0 -26
  41. package/cjs/exception/http-errors/failed-dependency.error.js +0 -25
  42. package/cjs/exception/http-errors/forbidden.error.js +0 -27
  43. package/cjs/exception/http-errors/internal-server.error.js +0 -27
  44. package/cjs/exception/http-errors/method-not-allowed.error.js +0 -26
  45. package/cjs/exception/http-errors/not-acceptable.error.js +0 -26
  46. package/cjs/exception/http-errors/not-found.error.js +0 -29
  47. package/cjs/exception/http-errors/unauthorized.error.js +0 -26
  48. package/cjs/exception/http-errors/unprocessable-entity.error.js +0 -25
  49. package/cjs/exception/index.js +0 -15
  50. package/cjs/exception/resource-errors/resource-conflict.error.js +0 -19
  51. package/cjs/exception/resource-errors/resource-not-found.error.js +0 -19
  52. package/cjs/exception/wrap-error.js +0 -17
  53. package/cjs/interfaces/query.interface.js +0 -207
  54. package/cjs/services/json-data-service.js +0 -252
  55. package/esm/exception/api-exception.d.ts +0 -40
  56. package/esm/exception/api-exception.js +0 -64
  57. package/esm/exception/http-errors/bad-request.error.d.ts +0 -10
  58. package/esm/exception/http-errors/bad-request.error.js +0 -22
  59. package/esm/exception/http-errors/failed-dependency.error.d.ts +0 -9
  60. package/esm/exception/http-errors/failed-dependency.error.js +0 -21
  61. package/esm/exception/http-errors/forbidden.error.d.ts +0 -11
  62. package/esm/exception/http-errors/forbidden.error.js +0 -23
  63. package/esm/exception/http-errors/internal-server.error.d.ts +0 -9
  64. package/esm/exception/http-errors/internal-server.error.js +0 -22
  65. package/esm/exception/http-errors/method-not-allowed.error.d.ts +0 -10
  66. package/esm/exception/http-errors/method-not-allowed.error.js +0 -22
  67. package/esm/exception/http-errors/not-acceptable.error.d.ts +0 -10
  68. package/esm/exception/http-errors/not-acceptable.error.js +0 -22
  69. package/esm/exception/http-errors/not-found.error.d.ts +0 -13
  70. package/esm/exception/http-errors/not-found.error.js +0 -25
  71. package/esm/exception/http-errors/unauthorized.error.d.ts +0 -10
  72. package/esm/exception/http-errors/unauthorized.error.js +0 -22
  73. package/esm/exception/http-errors/unprocessable-entity.error.d.ts +0 -9
  74. package/esm/exception/http-errors/unprocessable-entity.error.js +0 -21
  75. package/esm/exception/index.d.ts +0 -12
  76. package/esm/exception/index.js +0 -12
  77. package/esm/exception/resource-errors/resource-conflict.error.d.ts +0 -4
  78. package/esm/exception/resource-errors/resource-conflict.error.js +0 -15
  79. package/esm/exception/resource-errors/resource-not-found.error.d.ts +0 -4
  80. package/esm/exception/resource-errors/resource-not-found.error.js +0 -15
  81. package/esm/exception/wrap-error.d.ts +0 -2
  82. package/esm/exception/wrap-error.js +0 -13
  83. package/esm/interfaces/query.interface.d.ts +0 -115
  84. package/esm/interfaces/query.interface.js +0 -203
  85. package/esm/services/json-data-service.d.ts +0 -68
  86. 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 _stringPathToObjectTree(arr, {});
8
+ return _pathToTree(arr, {}, lowerCaseKeys);
9
9
  }
10
10
  exports.pathToTree = pathToTree;
11
- function _stringPathToObjectTree(arr, target) {
12
- for (const k of arr) {
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
- _stringPathToObjectTree([m[2]], sub);
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-OPRA-Version",
6
- X_Opra_Schema = "X-OPRA-Schema",
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-OPRA-Version";
9
- HttpHeaders["X_Opra_Schema"] = "X-OPRA-Schema";
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, OpraService } from '@opra/schema';
1
+ import { EntityResource, OpraApi } from '@opra/schema';
2
2
  import { QueryContext } from '../query-context.js';
3
- export declare function entityResourceExecute(service: OpraService, resource: EntityResource, context: QueryContext): Promise<void>;
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 (OpraQuery.isSearchQuery(query)) {
7
+ if (query.kind === 'SearchCollectionQuery') {
9
8
  const promises = [];
10
9
  let search;
11
- let count;
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
- promises.push(executeFn(service, resource, context, 'count')
16
- .then(v => count = v));
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.value = {
20
- ...search,
21
- ...count
22
- };
21
+ context.response = search;
23
22
  return;
24
23
  }
25
- context.response.value = await executeFn(service, resource, context, query.queryType);
24
+ context.response = await executeFn(service, resource, context);
26
25
  }
27
- async function executeFn(service, resource, context, queryType) {
28
- const resolverInfo = resource.metadata.methods?.[queryType];
29
- if (!resolverInfo.handler)
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', { queryType }),
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 (queryType) {
36
+ switch (method) {
37
37
  case 'search':
38
- context.response.headers.set(HttpHeaders.X_Opra_Schema, '/$schema/types/' + resource.dataType.name);
39
- return {
40
- items: Array.isArray(result) ? result : (context.response.value ? [result] : [])
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
- return { count: result || 0 };
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 affectedRecords;
54
+ let affected;
55
55
  if (typeof result === 'number')
56
- affectedRecords = result;
56
+ affected = result;
57
57
  if (typeof result === 'boolean')
58
- affectedRecords = result ? 1 : 0;
58
+ affected = result ? 1 : 0;
59
59
  if (typeof result === 'object')
60
- affectedRecords = result.affectedRows || result.affectedRecords;
61
- return { affectedRecords };
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 (queryType === 'create')
76
- context.response.status = 201;
77
- if (dataType)
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 { BaseResource, OpraService } from '@opra/schema';
1
+ import { OpraApi, OpraResource } from '@opra/schema';
2
2
  import { QueryContext } from '../query-context.js';
3
- export declare function resourceExecute(service: OpraService, resource: BaseResource, context: QueryContext): Promise<void>;
3
+ export declare function resourceExecute(service: OpraApi, resource: OpraResource, context: QueryContext): Promise<void>;