@opra/core 0.3.0 → 0.4.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/adapter/adapter.js +319 -0
- package/cjs/{implementation → adapter}/express-adapter.js +3 -6
- package/cjs/adapter/http-adapter.js +242 -0
- package/cjs/adapter/metadata-resource.js +23 -0
- package/cjs/{implementation → adapter}/query-context.js +1 -1
- package/cjs/enums/http-headers.enum.js +1 -1
- package/cjs/{implementation → helpers}/headers-map.js +2 -2
- package/cjs/index.js +6 -5
- package/cjs/interfaces/resource.interface.js +2 -0
- package/cjs/services/json-collection-service.js +14 -14
- package/cjs/services/json-singleton-service.js +97 -0
- package/esm/{implementation → adapter}/adapter.d.ts +17 -9
- package/esm/adapter/adapter.js +315 -0
- package/esm/{implementation → adapter}/express-adapter.d.ts +2 -2
- package/esm/{implementation → adapter}/express-adapter.js +3 -6
- package/esm/{implementation → adapter}/http-adapter.d.ts +2 -3
- package/esm/adapter/http-adapter.js +238 -0
- package/esm/adapter/metadata-resource.d.ts +8 -0
- package/esm/adapter/metadata-resource.js +20 -0
- package/esm/{implementation → adapter}/query-context.d.ts +6 -6
- package/esm/{implementation → adapter}/query-context.js +1 -1
- package/esm/enums/http-headers.enum.d.ts +1 -1
- package/esm/enums/http-headers.enum.js +1 -1
- package/esm/{implementation → helpers}/headers-map.d.ts +1 -1
- package/esm/{implementation → helpers}/headers-map.js +1 -1
- package/esm/index.d.ts +6 -5
- package/esm/index.js +6 -5
- package/esm/interfaces/resource.interface.d.ts +22 -0
- package/esm/interfaces/resource.interface.js +1 -0
- package/esm/services/json-collection-service.d.ts +11 -12
- package/esm/services/json-collection-service.js +15 -15
- package/esm/services/json-singleton-service.d.ts +39 -0
- package/esm/services/json-singleton-service.js +92 -0
- package/esm/types.d.ts +2 -8
- package/esm/utils/create-i18n.d.ts +1 -1
- package/package.json +15 -13
- package/cjs/implementation/adapter-utils/entity-resource-execute.util.js +0 -86
- package/cjs/implementation/adapter-utils/resource-execute.util.js +0 -11
- package/cjs/implementation/adapter-utils/resource-prepare.util.js +0 -11
- package/cjs/implementation/adapter.js +0 -130
- package/cjs/implementation/http-adapter.js +0 -253
- package/cjs/interfaces/entity-service.interface.js +0 -30
- package/esm/implementation/adapter-utils/entity-resource-execute.util.d.ts +0 -3
- package/esm/implementation/adapter-utils/entity-resource-execute.util.js +0 -82
- package/esm/implementation/adapter-utils/resource-execute.util.d.ts +0 -3
- package/esm/implementation/adapter-utils/resource-execute.util.js +0 -7
- package/esm/implementation/adapter-utils/resource-prepare.util.d.ts +0 -3
- package/esm/implementation/adapter-utils/resource-prepare.util.js +0 -7
- package/esm/implementation/adapter.js +0 -126
- package/esm/implementation/http-adapter.js +0 -249
- package/esm/interfaces/entity-service.interface.d.ts +0 -19
- package/esm/interfaces/entity-service.interface.js +0 -26
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import _ from 'lodash';
|
|
2
|
+
import { SingletonResourceInfo } from '@opra/schema';
|
|
3
|
+
export class JsonSingletonService {
|
|
4
|
+
dataType;
|
|
5
|
+
_data;
|
|
6
|
+
constructor(dataType, options) {
|
|
7
|
+
this.dataType = dataType;
|
|
8
|
+
this._data = options?.data;
|
|
9
|
+
}
|
|
10
|
+
async processRequest(ctx) {
|
|
11
|
+
const prepared = this._prepare(ctx.query);
|
|
12
|
+
const fn = this[prepared.method];
|
|
13
|
+
if (!fn)
|
|
14
|
+
throw new TypeError(`Unimplemented method (${prepared.method})`);
|
|
15
|
+
// @ts-ignore
|
|
16
|
+
return fn.apply(this, prepared.args);
|
|
17
|
+
}
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
19
|
+
get(options) {
|
|
20
|
+
return this._data;
|
|
21
|
+
}
|
|
22
|
+
_prepare(query) {
|
|
23
|
+
if (query.resource instanceof SingletonResourceInfo) {
|
|
24
|
+
if (query.dataType !== this.dataType)
|
|
25
|
+
throw new TypeError(`Query data type (${query.dataType.name}) ` +
|
|
26
|
+
`differs from JsonCollectionService data type (${this.dataType.name})`);
|
|
27
|
+
}
|
|
28
|
+
switch (query.method) {
|
|
29
|
+
case 'create': {
|
|
30
|
+
const options = _.omitBy({
|
|
31
|
+
pick: query.pick,
|
|
32
|
+
omit: query.omit,
|
|
33
|
+
include: query.include
|
|
34
|
+
}, _.isNil);
|
|
35
|
+
const { data } = query;
|
|
36
|
+
return {
|
|
37
|
+
method: query.method,
|
|
38
|
+
values: data,
|
|
39
|
+
options,
|
|
40
|
+
args: [data, options]
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
case 'get': {
|
|
44
|
+
if (query.kind === 'CollectionGetQuery') {
|
|
45
|
+
const options = _.omitBy({
|
|
46
|
+
pick: query.pick,
|
|
47
|
+
omit: query.omit,
|
|
48
|
+
include: query.include
|
|
49
|
+
}, _.isNil);
|
|
50
|
+
const keyValue = query.keyValue;
|
|
51
|
+
return {
|
|
52
|
+
method: query.method,
|
|
53
|
+
keyValue,
|
|
54
|
+
options,
|
|
55
|
+
args: [keyValue, options]
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
if (query.kind === 'FieldGetQuery') {
|
|
59
|
+
// todo
|
|
60
|
+
}
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
case 'update': {
|
|
64
|
+
const options = _.omitBy({
|
|
65
|
+
pick: query.pick,
|
|
66
|
+
omit: query.omit,
|
|
67
|
+
include: query.include
|
|
68
|
+
}, _.isNil);
|
|
69
|
+
const { data } = query;
|
|
70
|
+
const keyValue = query.keyValue;
|
|
71
|
+
return {
|
|
72
|
+
method: query.method,
|
|
73
|
+
keyValue: query.keyValue,
|
|
74
|
+
values: data,
|
|
75
|
+
options,
|
|
76
|
+
args: [keyValue, data, options]
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
case 'delete': {
|
|
80
|
+
const options = {};
|
|
81
|
+
const keyValue = query.keyValue;
|
|
82
|
+
return {
|
|
83
|
+
method: query.method,
|
|
84
|
+
keyValue,
|
|
85
|
+
options,
|
|
86
|
+
args: [keyValue, options]
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
throw new Error(`Unimplemented query type "${query.method}"`);
|
|
91
|
+
}
|
|
92
|
+
}
|
package/esm/types.d.ts
CHANGED
|
@@ -1,8 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export
|
|
3
|
-
export declare type EntityOutput<T> = DeepNullableIfPartial<T>;
|
|
4
|
-
export declare type DeepNullableIfPartial<T> = _DeepNullableIfPartial<T>;
|
|
5
|
-
declare type _DeepNullableIfPartial<T> = T extends Builtin ? T : T extends Promise<infer U> ? Promise<DeepNullableIfPartial<U>> : T extends (infer U)[] ? DeepNullableIfPartial<U>[] : {
|
|
6
|
-
[P in keyof T]?: DeepNullableIfPartial<Exclude<T[P], undefined>> | null;
|
|
7
|
-
};
|
|
8
|
-
export {};
|
|
1
|
+
import { PartialInput, PartialOutput } from '@opra/common';
|
|
2
|
+
export { PartialInput, PartialOutput };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opra/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Opra schema package",
|
|
5
5
|
"author": "Panates",
|
|
6
6
|
"license": "MIT",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"postbuild": "npm run _copyi18n && npm run _copy_pkg_files",
|
|
19
19
|
"_copy_pkg_files": "cp README.md package.json ../../LICENSE ../../build/core && cp ../../package.cjs.json ../../build/core/cjs/package.json",
|
|
20
20
|
"_copyi18n": "cp -R i18n ../../build/core/i18n",
|
|
21
|
-
"lint": "eslint .",
|
|
21
|
+
"lint": "eslint . --max-warnings=0",
|
|
22
22
|
"test": "jest",
|
|
23
23
|
"cover": "jest --collect-coverage",
|
|
24
24
|
"clean": "npm run clean:src && npm run clean:dist && npm run clean:cover",
|
|
@@ -27,24 +27,26 @@
|
|
|
27
27
|
"clean:cover": "rimraf ../../coverage/core"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@opra/exception": "^0.3.0",
|
|
31
|
-
"@opra/i18n": "^0.3.0",
|
|
32
|
-
"@opra/optionals": "^0.3.0",
|
|
33
|
-
"@opra/schema": "^0.3.0",
|
|
34
|
-
"@opra/url": "^0.3.0",
|
|
35
30
|
"@nano-sql/core": "^2.3.7",
|
|
36
|
-
"
|
|
31
|
+
"@opra/common": "^0.4.0",
|
|
32
|
+
"@opra/exception": "^0.4.0",
|
|
33
|
+
"@opra/i18n": "^0.4.0",
|
|
34
|
+
"@opra/optionals": "^0.4.0",
|
|
35
|
+
"@opra/schema": "^0.4.0",
|
|
36
|
+
"@opra/url": "^0.4.0",
|
|
37
|
+
"axiosist": "^1.0.0",
|
|
37
38
|
"body-parser": "^1.20.1",
|
|
39
|
+
"express": "^4.18.2",
|
|
38
40
|
"lodash": "^4.17.21",
|
|
39
41
|
"putil-isplainobject": "^1.1.4",
|
|
40
|
-
"putil-varhelpers": "^1.6.4",
|
|
41
42
|
"putil-merge": "^3.9.0",
|
|
42
|
-
"
|
|
43
|
-
"
|
|
43
|
+
"putil-varhelpers": "^1.6.4",
|
|
44
|
+
"strict-typed-events": "^2.3.1",
|
|
45
|
+
"ts-gems": "^2.3.0"
|
|
44
46
|
},
|
|
45
47
|
"devDependencies": {
|
|
46
|
-
"@
|
|
47
|
-
"@
|
|
48
|
+
"@faker-js/faker": "^7.6.0",
|
|
49
|
+
"@types/express": "^4.17.14"
|
|
48
50
|
},
|
|
49
51
|
"type": "module",
|
|
50
52
|
"types": "esm/index.d.ts",
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.entityResourceExecute = void 0;
|
|
4
|
-
const exception_1 = require("@opra/exception");
|
|
5
|
-
const i18n_1 = require("@opra/i18n");
|
|
6
|
-
const schema_1 = require("@opra/schema");
|
|
7
|
-
const index_js_1 = require("../../enums/index.js");
|
|
8
|
-
async function entityResourceExecute(service, resource, context) {
|
|
9
|
-
const { query } = context;
|
|
10
|
-
if (query.kind === 'SearchCollectionQuery') {
|
|
11
|
-
const promises = [];
|
|
12
|
-
let search;
|
|
13
|
-
promises.push(executeFn(service, resource, context)
|
|
14
|
-
.then(v => search = v));
|
|
15
|
-
if (query.count && resource.metadata.methods.count) {
|
|
16
|
-
const ctx = {
|
|
17
|
-
query: new schema_1.OpraCountCollectionQuery(query.resource, { filter: query.filter }),
|
|
18
|
-
resultPath: ''
|
|
19
|
-
};
|
|
20
|
-
Object.setPrototypeOf(ctx, context);
|
|
21
|
-
promises.push(executeFn(service, resource, ctx));
|
|
22
|
-
}
|
|
23
|
-
await Promise.all(promises);
|
|
24
|
-
context.response = search;
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
context.response = await executeFn(service, resource, context);
|
|
28
|
-
}
|
|
29
|
-
exports.entityResourceExecute = entityResourceExecute;
|
|
30
|
-
async function executeFn(service, resource, context) {
|
|
31
|
-
const method = context.query.method;
|
|
32
|
-
const resolverInfo = resource.metadata.methods?.[method];
|
|
33
|
-
if (!(resolverInfo && resolverInfo.handler))
|
|
34
|
-
throw new exception_1.ForbiddenError({
|
|
35
|
-
message: (0, i18n_1.translate)('RESOLVER_FORBIDDEN', { method }, `The resource endpoint does not accept '{{method}}' operations`),
|
|
36
|
-
severity: 'error',
|
|
37
|
-
code: 'RESOLVER_FORBIDDEN'
|
|
38
|
-
});
|
|
39
|
-
let result = await resolverInfo.handler(context);
|
|
40
|
-
switch (method) {
|
|
41
|
-
case 'search':
|
|
42
|
-
const items = Array.isArray(result) ? result : (context.response ? [result] : []);
|
|
43
|
-
context.responseHeaders.set(index_js_1.HttpHeaders.X_Opra_Schema, resource.dataType.name);
|
|
44
|
-
return items;
|
|
45
|
-
case 'get':
|
|
46
|
-
case 'update':
|
|
47
|
-
if (!result) {
|
|
48
|
-
const query = context.query;
|
|
49
|
-
throw new exception_1.ResourceNotFoundError(resource.name, query.keyValue);
|
|
50
|
-
}
|
|
51
|
-
break;
|
|
52
|
-
case 'count':
|
|
53
|
-
context.responseHeaders.set(index_js_1.HttpHeaders.X_Opra_Count, result);
|
|
54
|
-
return;
|
|
55
|
-
case 'delete':
|
|
56
|
-
case 'deleteMany':
|
|
57
|
-
case 'updateMany':
|
|
58
|
-
let affected;
|
|
59
|
-
if (typeof result === 'number')
|
|
60
|
-
affected = result;
|
|
61
|
-
if (typeof result === 'boolean')
|
|
62
|
-
affected = result ? 1 : 0;
|
|
63
|
-
if (typeof result === 'object')
|
|
64
|
-
affected = result.affectedRows || result.affected;
|
|
65
|
-
return {
|
|
66
|
-
operation: context.query.method,
|
|
67
|
-
affected
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
if (!result)
|
|
71
|
-
return;
|
|
72
|
-
result = Array.isArray(result) ? result[0] : result;
|
|
73
|
-
let dataType = resource.dataType;
|
|
74
|
-
if (context.resultPath) {
|
|
75
|
-
const pathArray = context.resultPath.split('.');
|
|
76
|
-
for (const field of pathArray) {
|
|
77
|
-
const prop = dataType instanceof schema_1.ComplexType ? dataType.fields.get(field) : undefined;
|
|
78
|
-
dataType = prop && prop.type ? service.types.get(prop.type) : undefined;
|
|
79
|
-
result = result && typeof result === 'object' && result[field];
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
if (method === 'create')
|
|
83
|
-
context.status = 201;
|
|
84
|
-
context.responseHeaders.set(index_js_1.HttpHeaders.X_Opra_Schema, resource.dataType.name);
|
|
85
|
-
return result;
|
|
86
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.resourceExecute = void 0;
|
|
4
|
-
const schema_1 = require("@opra/schema");
|
|
5
|
-
const entity_resource_execute_util_js_1 = require("./entity-resource-execute.util.js");
|
|
6
|
-
async function resourceExecute(service, resource, context) {
|
|
7
|
-
if (resource instanceof schema_1.EntityResource)
|
|
8
|
-
return await (0, entity_resource_execute_util_js_1.entityResourceExecute)(service, resource, context);
|
|
9
|
-
throw new Error(`Executing "${resource.kind}" has not been implemented yet`);
|
|
10
|
-
}
|
|
11
|
-
exports.resourceExecute = resourceExecute;
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.resourcePrepare = void 0;
|
|
4
|
-
async function resourcePrepare(resource, context) {
|
|
5
|
-
const { query } = context;
|
|
6
|
-
const fn = resource.metadata['pre_' + query.method];
|
|
7
|
-
if (fn && typeof fn === 'function') {
|
|
8
|
-
await fn(context);
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
exports.resourcePrepare = resourcePrepare;
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.OpraAdapter = void 0;
|
|
4
|
-
const strict_typed_events_1 = require("strict-typed-events");
|
|
5
|
-
const exception_1 = require("@opra/exception");
|
|
6
|
-
const i18n_1 = require("@opra/i18n");
|
|
7
|
-
const create_i18n_js_1 = require("../utils/create-i18n.js");
|
|
8
|
-
const resource_execute_util_js_1 = require("./adapter-utils/resource-execute.util.js");
|
|
9
|
-
const resource_prepare_util_js_1 = require("./adapter-utils/resource-prepare.util.js");
|
|
10
|
-
class OpraAdapter {
|
|
11
|
-
service;
|
|
12
|
-
i18n;
|
|
13
|
-
userContextResolver;
|
|
14
|
-
constructor(service, options) {
|
|
15
|
-
this.service = service;
|
|
16
|
-
this.i18n = options?.i18n || i18n_1.I18n.defaultInstance;
|
|
17
|
-
this.userContextResolver = options?.userContext;
|
|
18
|
-
}
|
|
19
|
-
async handler(executionContext) {
|
|
20
|
-
if (!this.i18n.isInitialized)
|
|
21
|
-
await this.i18n.init();
|
|
22
|
-
let queryContexts;
|
|
23
|
-
let userContext;
|
|
24
|
-
let failed = false;
|
|
25
|
-
try {
|
|
26
|
-
queryContexts = this.prepareRequests(executionContext);
|
|
27
|
-
let stop = false;
|
|
28
|
-
// Read requests can be executed simultaneously, write request should be executed one by one
|
|
29
|
-
let promises;
|
|
30
|
-
let exclusive = false;
|
|
31
|
-
for (const context of queryContexts) {
|
|
32
|
-
exclusive = exclusive || context.query.operation !== 'read';
|
|
33
|
-
// Wait previous read requests before executing update request
|
|
34
|
-
if (exclusive && promises) {
|
|
35
|
-
await Promise.allSettled(promises);
|
|
36
|
-
promises = undefined;
|
|
37
|
-
}
|
|
38
|
-
// If previous request in bucket had an error and executed an update
|
|
39
|
-
// we do not execute next requests
|
|
40
|
-
if (stop) {
|
|
41
|
-
context.errors.push(new exception_1.FailedDependencyError());
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
try {
|
|
45
|
-
const promise = (async () => {
|
|
46
|
-
if (context.query.method === 'metadata') {
|
|
47
|
-
await this._getSchemaExecute(context);
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
const resource = context.query.resource;
|
|
51
|
-
await (0, resource_prepare_util_js_1.resourcePrepare)(resource, context);
|
|
52
|
-
if (this.userContextResolver && !userContext)
|
|
53
|
-
userContext = this.userContextResolver({
|
|
54
|
-
executionContext,
|
|
55
|
-
isBatch: this.isBatch(executionContext)
|
|
56
|
-
});
|
|
57
|
-
context.userContext = userContext;
|
|
58
|
-
await (0, resource_execute_util_js_1.resourceExecute)(this.service, resource, context);
|
|
59
|
-
})().catch(e => {
|
|
60
|
-
context.errors.push(e);
|
|
61
|
-
});
|
|
62
|
-
if (exclusive)
|
|
63
|
-
await promise;
|
|
64
|
-
else {
|
|
65
|
-
promises = promises || [];
|
|
66
|
-
promises.push(promise);
|
|
67
|
-
}
|
|
68
|
-
// todo execute sub property queries
|
|
69
|
-
}
|
|
70
|
-
catch (e) {
|
|
71
|
-
context.errors.unshift(e);
|
|
72
|
-
}
|
|
73
|
-
if (context.errors.length) {
|
|
74
|
-
// noinspection SuspiciousTypeOfGuard
|
|
75
|
-
context.errors = context.errors.map(e => (0, exception_1.wrapException)(e));
|
|
76
|
-
if (exclusive)
|
|
77
|
-
stop = stop || !!context.errors.find(e => !(e.issue.severity === 'warning' || e.issue.severity === 'info'));
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
if (promises)
|
|
81
|
-
await Promise.allSettled(promises);
|
|
82
|
-
await this.sendResponse(executionContext, queryContexts);
|
|
83
|
-
}
|
|
84
|
-
catch (e) {
|
|
85
|
-
failed = true;
|
|
86
|
-
const error = (0, exception_1.wrapException)(e);
|
|
87
|
-
await this.sendError(executionContext, error);
|
|
88
|
-
}
|
|
89
|
-
finally {
|
|
90
|
-
if (executionContext instanceof strict_typed_events_1.AsyncEventEmitter) {
|
|
91
|
-
await executionContext
|
|
92
|
-
.emitAsyncSerial('finish', {
|
|
93
|
-
userContext,
|
|
94
|
-
failed
|
|
95
|
-
}).catch();
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
async _getSchemaExecute(ctx) {
|
|
100
|
-
const query = ctx.query;
|
|
101
|
-
let out;
|
|
102
|
-
if (query.resourcePath && query.resourcePath.length > 2)
|
|
103
|
-
throw new exception_1.BadRequestError();
|
|
104
|
-
if (query.resourcePath?.length) {
|
|
105
|
-
if (query.resourcePath[0] === 'resources') {
|
|
106
|
-
const resource = this.service.getResource(query.resourcePath[1]);
|
|
107
|
-
out = resource.getSchema(true);
|
|
108
|
-
query.resourcePath[1] = resource.name;
|
|
109
|
-
}
|
|
110
|
-
else if (query.resourcePath[0] === 'types') {
|
|
111
|
-
const dataType = this.service.getDataType(query.resourcePath[1]);
|
|
112
|
-
out = dataType.getSchema(true);
|
|
113
|
-
query.resourcePath[1] = dataType.name;
|
|
114
|
-
}
|
|
115
|
-
else
|
|
116
|
-
throw new exception_1.BadRequestError();
|
|
117
|
-
ctx.response = out;
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
ctx.response = this.service.getSchema(true);
|
|
121
|
-
}
|
|
122
|
-
static async initI18n(options) {
|
|
123
|
-
if (options?.i18n instanceof i18n_1.I18n)
|
|
124
|
-
return options.i18n;
|
|
125
|
-
if (typeof options?.i18n === 'function')
|
|
126
|
-
return options.i18n();
|
|
127
|
-
return (0, create_i18n_js_1.createI18n)(options?.i18n);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
exports.OpraAdapter = OpraAdapter;
|
|
@@ -1,253 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.OpraHttpAdapter = void 0;
|
|
4
|
-
const exception_1 = require("@opra/exception");
|
|
5
|
-
const schema_1 = require("@opra/schema");
|
|
6
|
-
const url_1 = require("@opra/url");
|
|
7
|
-
const index_js_1 = require("../enums/index.js");
|
|
8
|
-
const adapter_js_1 = require("./adapter.js");
|
|
9
|
-
const headers_map_js_1 = require("./headers-map.js");
|
|
10
|
-
const query_context_js_1 = require("./query-context.js");
|
|
11
|
-
class OpraHttpAdapter extends adapter_js_1.OpraAdapter {
|
|
12
|
-
prepareRequests(executionContext) {
|
|
13
|
-
const req = executionContext.getRequestWrapper();
|
|
14
|
-
// todo implement batch requests
|
|
15
|
-
if (this.isBatch(executionContext)) {
|
|
16
|
-
throw new Error('not implemented yet');
|
|
17
|
-
}
|
|
18
|
-
const url = new url_1.OpraURL(req.getUrl());
|
|
19
|
-
return [
|
|
20
|
-
this.prepareRequest(executionContext, url, req.getMethod(), new headers_map_js_1.HeadersMap(req.getHeaders()), req.getBody())
|
|
21
|
-
];
|
|
22
|
-
}
|
|
23
|
-
prepareRequest(executionContext, url, method, headers, body) {
|
|
24
|
-
if (!url.path.size)
|
|
25
|
-
throw new exception_1.BadRequestError();
|
|
26
|
-
if (method !== 'GET' && url.path.size > 1)
|
|
27
|
-
throw new exception_1.BadRequestError();
|
|
28
|
-
const query = this.buildQuery(url, method, body);
|
|
29
|
-
if (!query)
|
|
30
|
-
throw new exception_1.MethodNotAllowedError({
|
|
31
|
-
message: `Method "${method}" is not allowed by target endpoint`
|
|
32
|
-
});
|
|
33
|
-
return new query_context_js_1.QueryContext({
|
|
34
|
-
service: this.service,
|
|
35
|
-
executionContext,
|
|
36
|
-
query,
|
|
37
|
-
headers: new headers_map_js_1.HeadersMap(),
|
|
38
|
-
params: url.searchParams,
|
|
39
|
-
continueOnError: query.operation === 'read'
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
buildGGetMetadataQuery(url) {
|
|
43
|
-
const pathLen = url.path.size;
|
|
44
|
-
const resourcePath = [];
|
|
45
|
-
let pathIndex = 0;
|
|
46
|
-
while (pathIndex < pathLen) {
|
|
47
|
-
const p = url.path.get(pathIndex++);
|
|
48
|
-
if (p.key)
|
|
49
|
-
throw new exception_1.BadRequestError();
|
|
50
|
-
if (p.resource !== '$metadata') {
|
|
51
|
-
if (pathIndex === 1)
|
|
52
|
-
resourcePath.push('resources');
|
|
53
|
-
resourcePath.push(p.resource);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
const opts = {
|
|
57
|
-
pick: url.searchParams.get('$pick'),
|
|
58
|
-
omit: url.searchParams.get('$omit'),
|
|
59
|
-
include: url.searchParams.get('$include'),
|
|
60
|
-
resourcePath
|
|
61
|
-
};
|
|
62
|
-
return new schema_1.OpraGetMetadataQuery(opts);
|
|
63
|
-
}
|
|
64
|
-
buildQuery(url, method, body) {
|
|
65
|
-
let container = this.service;
|
|
66
|
-
try {
|
|
67
|
-
const pathLen = url.path.size;
|
|
68
|
-
// Check if requesting metadata
|
|
69
|
-
for (let i = 0; i < pathLen; i++) {
|
|
70
|
-
const p = url.path.get(i);
|
|
71
|
-
if (p.resource === '$metadata') {
|
|
72
|
-
if (method !== 'GET')
|
|
73
|
-
return;
|
|
74
|
-
return this.buildGGetMetadataQuery(url);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
let pathIndex = 0;
|
|
78
|
-
while (pathIndex < pathLen) {
|
|
79
|
-
let p = url.path.get(pathIndex++);
|
|
80
|
-
const resource = container.getResource(p.resource);
|
|
81
|
-
// Move through path directories (containers)
|
|
82
|
-
if (resource instanceof schema_1.ContainerResource) {
|
|
83
|
-
container = resource;
|
|
84
|
-
continue;
|
|
85
|
-
}
|
|
86
|
-
method = method.toUpperCase();
|
|
87
|
-
if (resource instanceof schema_1.EntityResource) {
|
|
88
|
-
const scope = p.key ? 'instance' : 'collection';
|
|
89
|
-
if (pathIndex < pathLen && !(method === 'GET' && scope === 'instance'))
|
|
90
|
-
return;
|
|
91
|
-
let query;
|
|
92
|
-
switch (method) {
|
|
93
|
-
case 'GET': {
|
|
94
|
-
if (scope === 'collection') {
|
|
95
|
-
query = new schema_1.OpraSearchCollectionQuery(resource, {
|
|
96
|
-
filter: url.searchParams.get('$filter'),
|
|
97
|
-
limit: url.searchParams.get('$limit'),
|
|
98
|
-
skip: url.searchParams.get('$skip'),
|
|
99
|
-
distinct: url.searchParams.get('$distinct'),
|
|
100
|
-
count: url.searchParams.get('$count'),
|
|
101
|
-
sort: url.searchParams.get('$sort'),
|
|
102
|
-
pick: url.searchParams.get('$pick'),
|
|
103
|
-
omit: url.searchParams.get('$omit'),
|
|
104
|
-
include: url.searchParams.get('$include'),
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
else {
|
|
108
|
-
query = new schema_1.OpraGetInstanceQuery(resource, p.key, {
|
|
109
|
-
pick: url.searchParams.get('$pick'),
|
|
110
|
-
omit: url.searchParams.get('$omit'),
|
|
111
|
-
include: url.searchParams.get('$include')
|
|
112
|
-
});
|
|
113
|
-
// Move through properties
|
|
114
|
-
let dataType = resource.dataType;
|
|
115
|
-
const curPath = [];
|
|
116
|
-
let parent = query;
|
|
117
|
-
while (pathIndex < pathLen) {
|
|
118
|
-
if (!(dataType instanceof schema_1.ComplexType))
|
|
119
|
-
throw new TypeError(`"${resource.name}.${curPath.join()}" is not a ComplexType and has no fields.`);
|
|
120
|
-
p = url.path.get(pathIndex++);
|
|
121
|
-
curPath.push(p.resource);
|
|
122
|
-
const field = dataType.getField(p.resource);
|
|
123
|
-
parent.nested = new schema_1.OpraGetFieldQuery(parent, field.name);
|
|
124
|
-
parent = parent.nested;
|
|
125
|
-
dataType = parent.dataType;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
break;
|
|
129
|
-
}
|
|
130
|
-
case 'DELETE': {
|
|
131
|
-
query = scope === 'collection'
|
|
132
|
-
? new schema_1.OpraDeleteCollectionQuery(resource, {
|
|
133
|
-
filter: url.searchParams.get('$filter'),
|
|
134
|
-
})
|
|
135
|
-
: new schema_1.OpraDeleteInstanceQuery(resource, p.key);
|
|
136
|
-
break;
|
|
137
|
-
}
|
|
138
|
-
case 'POST': {
|
|
139
|
-
if (scope === 'collection') {
|
|
140
|
-
query = new schema_1.OpraCreateInstanceQuery(resource, body, {
|
|
141
|
-
pick: url.searchParams.get('$pick'),
|
|
142
|
-
omit: url.searchParams.get('$omit'),
|
|
143
|
-
include: url.searchParams.get('$include')
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
break;
|
|
147
|
-
}
|
|
148
|
-
case 'PATCH': {
|
|
149
|
-
query = scope === 'collection'
|
|
150
|
-
? new schema_1.OpraUpdateCollectionQuery(resource, body, {
|
|
151
|
-
filter: url.searchParams.get('$filter')
|
|
152
|
-
})
|
|
153
|
-
: new schema_1.OpraUpdateInstanceQuery(resource, p.key, body, {
|
|
154
|
-
pick: url.searchParams.get('$pick'),
|
|
155
|
-
omit: url.searchParams.get('$omit'),
|
|
156
|
-
include: url.searchParams.get('$include')
|
|
157
|
-
});
|
|
158
|
-
break;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
return query;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
throw new exception_1.InternalServerError();
|
|
165
|
-
}
|
|
166
|
-
catch (e) {
|
|
167
|
-
if (e instanceof exception_1.OpraException)
|
|
168
|
-
throw e;
|
|
169
|
-
throw new exception_1.BadRequestError(e);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
async sendResponse(executionContext, queryContexts) {
|
|
173
|
-
const outputPackets = [];
|
|
174
|
-
for (const ctx of queryContexts) {
|
|
175
|
-
const v = this.createOutput(ctx);
|
|
176
|
-
outputPackets.push(v);
|
|
177
|
-
}
|
|
178
|
-
if (this.isBatch(executionContext)) {
|
|
179
|
-
// this.writeError([], new InternalServerError({message: 'Not implemented yet'}));
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
if (!outputPackets.length)
|
|
183
|
-
return this.sendError(executionContext, new exception_1.NotFoundError());
|
|
184
|
-
const out = outputPackets[0];
|
|
185
|
-
const resp = executionContext.getResponseWrapper();
|
|
186
|
-
resp.setStatus(out.status);
|
|
187
|
-
resp.setHeader(index_js_1.HttpHeaders.Content_Type, 'application/opra+json');
|
|
188
|
-
resp.setHeader(index_js_1.HttpHeaders.Cache_Control, 'no-cache');
|
|
189
|
-
resp.setHeader(index_js_1.HttpHeaders.Pragma, 'no-cache');
|
|
190
|
-
resp.setHeader(index_js_1.HttpHeaders.Expires, '-1');
|
|
191
|
-
resp.setHeader(index_js_1.HttpHeaders.X_Opra_Version, schema_1.OpraSchema.Version);
|
|
192
|
-
if (out.headers) {
|
|
193
|
-
for (const [k, v] of Object.entries(out.headers)) {
|
|
194
|
-
resp.setHeader(k, v);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
resp.send(JSON.stringify(out.body));
|
|
198
|
-
}
|
|
199
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
200
|
-
isBatch(executionContext) {
|
|
201
|
-
return false;
|
|
202
|
-
}
|
|
203
|
-
createOutput(ctx) {
|
|
204
|
-
const { query } = ctx;
|
|
205
|
-
let body;
|
|
206
|
-
let status = ctx.status || 0;
|
|
207
|
-
const errors = ctx.errors.map(e => (0, exception_1.wrapException)(e));
|
|
208
|
-
if (errors && errors.length) {
|
|
209
|
-
// Sort errors from fatal to info
|
|
210
|
-
errors.sort((a, b) => {
|
|
211
|
-
const i = exception_1.IssueSeverity.Keys.indexOf(a.issue.severity) - exception_1.IssueSeverity.Keys.indexOf(b.issue.severity);
|
|
212
|
-
if (i === 0)
|
|
213
|
-
return b.status - a.status;
|
|
214
|
-
return i;
|
|
215
|
-
});
|
|
216
|
-
if (!status || status < index_js_1.HttpStatus.BAD_REQUEST) {
|
|
217
|
-
status = errors[0].status;
|
|
218
|
-
if (status < index_js_1.HttpStatus.BAD_REQUEST)
|
|
219
|
-
status = index_js_1.HttpStatus.INTERNAL_SERVER_ERROR;
|
|
220
|
-
}
|
|
221
|
-
body = {
|
|
222
|
-
operation: ctx.query.method,
|
|
223
|
-
errors: errors.map(e => e.issue)
|
|
224
|
-
};
|
|
225
|
-
}
|
|
226
|
-
else {
|
|
227
|
-
body = ctx.response;
|
|
228
|
-
status = status || (query.operation === 'create' ? index_js_1.HttpStatus.CREATED : index_js_1.HttpStatus.OK);
|
|
229
|
-
}
|
|
230
|
-
body = this.i18n.deep(body);
|
|
231
|
-
return {
|
|
232
|
-
status,
|
|
233
|
-
headers: ctx.responseHeaders.toObject(),
|
|
234
|
-
body
|
|
235
|
-
};
|
|
236
|
-
}
|
|
237
|
-
async sendError(executionContext, error) {
|
|
238
|
-
const resp = executionContext.getResponseWrapper();
|
|
239
|
-
resp.setStatus(error.status || 500);
|
|
240
|
-
resp.setHeader(index_js_1.HttpHeaders.Content_Type, 'application/json');
|
|
241
|
-
resp.setHeader(index_js_1.HttpHeaders.Cache_Control, 'no-cache');
|
|
242
|
-
resp.setHeader(index_js_1.HttpHeaders.Pragma, 'no-cache');
|
|
243
|
-
resp.setHeader(index_js_1.HttpHeaders.Expires, '-1');
|
|
244
|
-
resp.setHeader(index_js_1.HttpHeaders.X_Opra_Version, schema_1.OpraSchema.Version);
|
|
245
|
-
const issue = this.i18n.deep(error.issue);
|
|
246
|
-
const body = {
|
|
247
|
-
operation: 'unknown',
|
|
248
|
-
errors: [issue]
|
|
249
|
-
};
|
|
250
|
-
resp.send(JSON.stringify(body));
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
exports.OpraHttpAdapter = OpraHttpAdapter;
|