@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,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 { OpraResource, OpraService } from '@opra/schema';
|
|
2
2
|
import { QueryContext } from '../query-context.js';
|
|
3
|
-
export declare function resourceExecute(service: OpraService, resource:
|
|
3
|
+
export declare function resourceExecute(service: OpraService, resource: OpraResource, context: QueryContext): Promise<void>;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { OpraResource } from '@opra/schema';
|
|
2
2
|
import { QueryContext } from '../query-context.js';
|
|
3
|
-
export declare function resourcePrepare(resource:
|
|
3
|
+
export declare function resourcePrepare(resource: OpraResource, context: QueryContext): Promise<void>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { OpraException } from '@opra/exception';
|
|
1
2
|
import { FallbackLng, I18n, LanguageResource } from '@opra/i18n';
|
|
2
3
|
import { OpraService } from '@opra/schema';
|
|
3
|
-
import { ApiException } from '../exception/index.js';
|
|
4
4
|
import { IExecutionContext } from '../interfaces/execution-context.interface.js';
|
|
5
5
|
import { QueryContext } from './query-context.js';
|
|
6
6
|
export declare namespace OpraAdapter {
|
|
@@ -49,7 +49,7 @@ export declare abstract class OpraAdapter<TExecutionContext extends IExecutionCo
|
|
|
49
49
|
});
|
|
50
50
|
protected abstract prepareRequests(executionContext: TExecutionContext): QueryContext[];
|
|
51
51
|
protected abstract sendResponse(executionContext: TExecutionContext, queryContexts: QueryContext[]): Promise<void>;
|
|
52
|
-
protected abstract sendError(executionContext: TExecutionContext, error:
|
|
52
|
+
protected abstract sendError(executionContext: TExecutionContext, error: OpraException): Promise<void>;
|
|
53
53
|
protected abstract isBatch(executionContext: TExecutionContext): boolean;
|
|
54
54
|
protected handler(executionContext: TExecutionContext): Promise<void>;
|
|
55
55
|
protected _getSchemaExecute(ctx: QueryContext): Promise<void>;
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { AsyncEventEmitter } from 'strict-typed-events';
|
|
2
|
+
import { BadRequestError, FailedDependencyError, wrapException } from '@opra/exception';
|
|
2
3
|
import { I18n } from '@opra/i18n';
|
|
3
|
-
import { HttpHeaders } from '../enums/index.js';
|
|
4
|
-
import { BadRequestError, FailedDependencyError } from '../exception/index.js';
|
|
5
|
-
import { wrapError } from '../exception/wrap-error.js';
|
|
6
4
|
import { createI18n } from '../utils/create-i18n.js';
|
|
7
5
|
import { resourceExecute } from './adapter-utils/resource-execute.util.js';
|
|
8
6
|
import { resourcePrepare } from './adapter-utils/resource-prepare.util.js';
|
|
@@ -37,12 +35,12 @@ export class OpraAdapter {
|
|
|
37
35
|
// If previous request in bucket had an error and executed an update
|
|
38
36
|
// we do not execute next requests
|
|
39
37
|
if (stop) {
|
|
40
|
-
context.
|
|
38
|
+
context.errors.push(new FailedDependencyError());
|
|
41
39
|
continue;
|
|
42
40
|
}
|
|
43
41
|
try {
|
|
44
42
|
const promise = (async () => {
|
|
45
|
-
if (context.query.
|
|
43
|
+
if (context.query.method === 'metadata') {
|
|
46
44
|
await this._getSchemaExecute(context);
|
|
47
45
|
return;
|
|
48
46
|
}
|
|
@@ -56,7 +54,7 @@ export class OpraAdapter {
|
|
|
56
54
|
context.userContext = userContext;
|
|
57
55
|
await resourceExecute(this.service, resource, context);
|
|
58
56
|
})().catch(e => {
|
|
59
|
-
context.
|
|
57
|
+
context.errors.push(e);
|
|
60
58
|
});
|
|
61
59
|
if (exclusive)
|
|
62
60
|
await promise;
|
|
@@ -67,13 +65,13 @@ export class OpraAdapter {
|
|
|
67
65
|
// todo execute sub property queries
|
|
68
66
|
}
|
|
69
67
|
catch (e) {
|
|
70
|
-
context.
|
|
68
|
+
context.errors.unshift(e);
|
|
71
69
|
}
|
|
72
|
-
if (context.
|
|
70
|
+
if (context.errors.length) {
|
|
73
71
|
// noinspection SuspiciousTypeOfGuard
|
|
74
|
-
context.
|
|
72
|
+
context.errors = context.errors.map(e => wrapException(e));
|
|
75
73
|
if (exclusive)
|
|
76
|
-
stop = stop || !!context.
|
|
74
|
+
stop = stop || !!context.errors.find(e => !(e.issue.severity === 'warning' || e.issue.severity === 'info'));
|
|
77
75
|
}
|
|
78
76
|
}
|
|
79
77
|
if (promises)
|
|
@@ -82,7 +80,7 @@ export class OpraAdapter {
|
|
|
82
80
|
}
|
|
83
81
|
catch (e) {
|
|
84
82
|
failed = true;
|
|
85
|
-
const error =
|
|
83
|
+
const error = wrapException(e);
|
|
86
84
|
await this.sendError(executionContext, error);
|
|
87
85
|
}
|
|
88
86
|
finally {
|
|
@@ -98,28 +96,25 @@ export class OpraAdapter {
|
|
|
98
96
|
async _getSchemaExecute(ctx) {
|
|
99
97
|
const query = ctx.query;
|
|
100
98
|
let out;
|
|
101
|
-
if (query.resourcePath.length > 2)
|
|
99
|
+
if (query.resourcePath && query.resourcePath.length > 2)
|
|
102
100
|
throw new BadRequestError();
|
|
103
101
|
if (query.resourcePath?.length) {
|
|
104
102
|
if (query.resourcePath[0] === 'resources') {
|
|
105
103
|
const resource = this.service.getResource(query.resourcePath[1]);
|
|
106
104
|
out = resource.getSchema(true);
|
|
107
105
|
query.resourcePath[1] = resource.name;
|
|
108
|
-
ctx.response.headers.set(HttpHeaders.X_Opra_Schema, 'http://www.oprajs.com/reference/v1/schema#' + resource.kind);
|
|
109
106
|
}
|
|
110
107
|
else if (query.resourcePath[0] === 'types') {
|
|
111
108
|
const dataType = this.service.getDataType(query.resourcePath[1]);
|
|
112
109
|
out = dataType.getSchema(true);
|
|
113
110
|
query.resourcePath[1] = dataType.name;
|
|
114
|
-
ctx.response.headers.set(HttpHeaders.X_Opra_Schema, 'http://www.oprajs.com/reference/v1/schema#' + dataType.kind);
|
|
115
111
|
}
|
|
116
112
|
else
|
|
117
113
|
throw new BadRequestError();
|
|
118
|
-
ctx.response
|
|
114
|
+
ctx.response = out;
|
|
119
115
|
return;
|
|
120
116
|
}
|
|
121
|
-
ctx.response.
|
|
122
|
-
ctx.response.value = this.service.getSchema(true);
|
|
117
|
+
ctx.response = this.service.getSchema(true);
|
|
123
118
|
}
|
|
124
119
|
static async initI18n(options) {
|
|
125
120
|
if (options?.i18n instanceof I18n)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import bodyParser from 'body-parser';
|
|
1
2
|
import { AsyncEventEmitter } from 'strict-typed-events';
|
|
2
3
|
import { normalizePath } from '@opra/url';
|
|
3
4
|
import { OpraHttpAdapter } from './http-adapter.js';
|
|
@@ -9,6 +10,7 @@ export class OpraExpressAdapter extends OpraHttpAdapter {
|
|
|
9
10
|
i18n
|
|
10
11
|
});
|
|
11
12
|
const prefix = '/' + normalizePath(options?.prefix, true);
|
|
13
|
+
app.use(prefix, bodyParser.json());
|
|
12
14
|
app.use(prefix, (request, response, next) => {
|
|
13
15
|
(async () => {
|
|
14
16
|
const executionContext = new ExpressExecutionContext(request, response);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ResponsiveMap } from '@opra/schema';
|
|
2
|
+
import { HttpHeaders } from '../enums/index.js';
|
|
3
|
+
export class HeadersMap extends ResponsiveMap {
|
|
4
|
+
constructor(data) {
|
|
5
|
+
super(data, Array.from(Object.values(HttpHeaders)));
|
|
6
|
+
}
|
|
7
|
+
toObject() {
|
|
8
|
+
return Array.from(this.keys()).sort()
|
|
9
|
+
.reduce((a, k) => {
|
|
10
|
+
a[k] = this.get(k);
|
|
11
|
+
return a;
|
|
12
|
+
}, {});
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { OpraException } from '@opra/exception';
|
|
2
|
+
import { OpraAnyQuery, OpraGetMetadataQuery } from '@opra/schema';
|
|
1
3
|
import { OpraURL } from '@opra/url';
|
|
2
|
-
import { ApiException } from '../exception/index.js';
|
|
3
4
|
import { IHttpExecutionContext } from '../interfaces/execution-context.interface.js';
|
|
4
|
-
import { OpraGetSchemaQuery, OpraQuery } from '../interfaces/query.interface.js';
|
|
5
5
|
import { OpraAdapter } from './adapter.js';
|
|
6
6
|
import { QueryContext } from './query-context.js';
|
|
7
7
|
export declare namespace OpraHttpAdapter {
|
|
@@ -11,17 +11,17 @@ export declare namespace OpraHttpAdapter {
|
|
|
11
11
|
}
|
|
12
12
|
interface PreparedOutput {
|
|
13
13
|
status: number;
|
|
14
|
-
headers
|
|
14
|
+
headers: Record<string, string>;
|
|
15
15
|
body?: any;
|
|
16
16
|
}
|
|
17
17
|
export declare class OpraHttpAdapter<TExecutionContext extends IHttpExecutionContext> extends OpraAdapter<IHttpExecutionContext> {
|
|
18
18
|
protected prepareRequests(executionContext: TExecutionContext): QueryContext[];
|
|
19
19
|
prepareRequest(executionContext: IHttpExecutionContext, url: OpraURL, method: string, headers: Map<string, string>, body?: any): QueryContext;
|
|
20
|
-
|
|
21
|
-
buildQuery(url: OpraURL, method: string, body?: any):
|
|
20
|
+
buildGGetMetadataQuery(url: OpraURL): OpraGetMetadataQuery;
|
|
21
|
+
buildQuery(url: OpraURL, method: string, body?: any): OpraAnyQuery | undefined;
|
|
22
22
|
protected sendResponse(executionContext: TExecutionContext, queryContexts: QueryContext[]): Promise<void>;
|
|
23
23
|
protected isBatch(executionContext: TExecutionContext): boolean;
|
|
24
24
|
protected createOutput(ctx: QueryContext): PreparedOutput;
|
|
25
|
-
protected sendError(executionContext: TExecutionContext, error:
|
|
25
|
+
protected sendError(executionContext: TExecutionContext, error: OpraException): Promise<void>;
|
|
26
26
|
}
|
|
27
27
|
export {};
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BadRequestError, InternalServerError, IssueSeverity, MethodNotAllowedError, NotFoundError, OpraException, wrapException } from '@opra/exception';
|
|
2
|
+
import { ComplexType, ContainerResource, EntityResource, OpraCreateInstanceQuery, OpraDeleteCollectionQuery, OpraDeleteInstanceQuery, OpraGetFieldQuery, OpraGetInstanceQuery, OpraGetMetadataQuery, OpraSchema, OpraSearchCollectionQuery, OpraUpdateCollectionQuery, OpraUpdateInstanceQuery, } from '@opra/schema';
|
|
2
3
|
import { OpraURL } from '@opra/url';
|
|
3
4
|
import { HttpHeaders, HttpStatus } from '../enums/index.js';
|
|
4
|
-
import { BadRequestError, InternalServerError, MethodNotAllowedError, NotFoundError, } from '../exception/index.js';
|
|
5
|
-
import { wrapError } from '../exception/wrap-error.js';
|
|
6
|
-
import { OpraQuery } from '../interfaces/query.interface.js';
|
|
7
5
|
import { OpraAdapter } from './adapter.js';
|
|
6
|
+
import { HeadersMap } from './headers-map.js';
|
|
8
7
|
import { QueryContext } from './query-context.js';
|
|
9
8
|
export class OpraHttpAdapter extends OpraAdapter {
|
|
10
9
|
prepareRequests(executionContext) {
|
|
@@ -15,7 +14,7 @@ export class OpraHttpAdapter extends OpraAdapter {
|
|
|
15
14
|
}
|
|
16
15
|
const url = new OpraURL(req.getUrl());
|
|
17
16
|
return [
|
|
18
|
-
this.prepareRequest(executionContext, url, req.getMethod(), new
|
|
17
|
+
this.prepareRequest(executionContext, url, req.getMethod(), new HeadersMap(req.getHeaders()), req.getBody())
|
|
19
18
|
];
|
|
20
19
|
}
|
|
21
20
|
prepareRequest(executionContext, url, method, headers, body) {
|
|
@@ -32,12 +31,12 @@ export class OpraHttpAdapter extends OpraAdapter {
|
|
|
32
31
|
service: this.service,
|
|
33
32
|
executionContext,
|
|
34
33
|
query,
|
|
35
|
-
headers,
|
|
34
|
+
headers: new HeadersMap(),
|
|
36
35
|
params: url.searchParams,
|
|
37
36
|
continueOnError: query.operation === 'read'
|
|
38
37
|
});
|
|
39
38
|
}
|
|
40
|
-
|
|
39
|
+
buildGGetMetadataQuery(url) {
|
|
41
40
|
const pathLen = url.path.size;
|
|
42
41
|
const resourcePath = [];
|
|
43
42
|
let pathIndex = 0;
|
|
@@ -45,7 +44,7 @@ export class OpraHttpAdapter extends OpraAdapter {
|
|
|
45
44
|
const p = url.path.get(pathIndex++);
|
|
46
45
|
if (p.key)
|
|
47
46
|
throw new BadRequestError();
|
|
48
|
-
if (p.resource !== '$
|
|
47
|
+
if (p.resource !== '$metadata') {
|
|
49
48
|
if (pathIndex === 1)
|
|
50
49
|
resourcePath.push('resources');
|
|
51
50
|
resourcePath.push(p.resource);
|
|
@@ -55,8 +54,9 @@ export class OpraHttpAdapter extends OpraAdapter {
|
|
|
55
54
|
pick: url.searchParams.get('$pick'),
|
|
56
55
|
omit: url.searchParams.get('$omit'),
|
|
57
56
|
include: url.searchParams.get('$include'),
|
|
57
|
+
resourcePath
|
|
58
58
|
};
|
|
59
|
-
return
|
|
59
|
+
return new OpraGetMetadataQuery(opts);
|
|
60
60
|
}
|
|
61
61
|
buildQuery(url, method, body) {
|
|
62
62
|
let container = this.service;
|
|
@@ -65,10 +65,10 @@ export class OpraHttpAdapter extends OpraAdapter {
|
|
|
65
65
|
// Check if requesting metadata
|
|
66
66
|
for (let i = 0; i < pathLen; i++) {
|
|
67
67
|
const p = url.path.get(i);
|
|
68
|
-
if (p.resource === '$
|
|
68
|
+
if (p.resource === '$metadata') {
|
|
69
69
|
if (method !== 'GET')
|
|
70
70
|
return;
|
|
71
|
-
return this.
|
|
71
|
+
return this.buildGGetMetadataQuery(url);
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
74
|
let pathIndex = 0;
|
|
@@ -89,7 +89,7 @@ export class OpraHttpAdapter extends OpraAdapter {
|
|
|
89
89
|
switch (method) {
|
|
90
90
|
case 'GET': {
|
|
91
91
|
if (scope === 'collection') {
|
|
92
|
-
query =
|
|
92
|
+
query = new OpraSearchCollectionQuery(resource, {
|
|
93
93
|
filter: url.searchParams.get('$filter'),
|
|
94
94
|
limit: url.searchParams.get('$limit'),
|
|
95
95
|
skip: url.searchParams.get('$skip'),
|
|
@@ -102,51 +102,39 @@ export class OpraHttpAdapter extends OpraAdapter {
|
|
|
102
102
|
});
|
|
103
103
|
}
|
|
104
104
|
else {
|
|
105
|
-
query =
|
|
105
|
+
query = new OpraGetInstanceQuery(resource, p.key, {
|
|
106
106
|
pick: url.searchParams.get('$pick'),
|
|
107
107
|
omit: url.searchParams.get('$omit'),
|
|
108
108
|
include: url.searchParams.get('$include')
|
|
109
109
|
});
|
|
110
110
|
// Move through properties
|
|
111
|
-
let
|
|
112
|
-
|
|
111
|
+
let dataType = resource.dataType;
|
|
112
|
+
const curPath = [];
|
|
113
|
+
let parent = query;
|
|
113
114
|
while (pathIndex < pathLen) {
|
|
114
|
-
const dataType = nested
|
|
115
|
-
? this.service.getDataType(nested.property.type || 'string')
|
|
116
|
-
: query.resource.dataType;
|
|
117
115
|
if (!(dataType instanceof ComplexType))
|
|
118
|
-
throw new
|
|
116
|
+
throw new TypeError(`"${resource.name}.${curPath.join()}" is not a ComplexType and has no fields.`);
|
|
119
117
|
p = url.path.get(pathIndex++);
|
|
120
|
-
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
if (nested) {
|
|
126
|
-
nested.nested = q;
|
|
127
|
-
}
|
|
128
|
-
else {
|
|
129
|
-
query.nested = q;
|
|
130
|
-
}
|
|
131
|
-
nested = q;
|
|
118
|
+
curPath.push(p.resource);
|
|
119
|
+
const field = dataType.getField(p.resource);
|
|
120
|
+
parent.nested = new OpraGetFieldQuery(parent, field.name);
|
|
121
|
+
parent = parent.nested;
|
|
122
|
+
dataType = parent.dataType;
|
|
132
123
|
}
|
|
133
124
|
}
|
|
134
125
|
break;
|
|
135
126
|
}
|
|
136
127
|
case 'DELETE': {
|
|
137
|
-
|
|
138
|
-
|
|
128
|
+
query = scope === 'collection'
|
|
129
|
+
? new OpraDeleteCollectionQuery(resource, {
|
|
139
130
|
filter: url.searchParams.get('$filter'),
|
|
140
|
-
})
|
|
141
|
-
|
|
142
|
-
else {
|
|
143
|
-
query = OpraQuery.forDelete(resource, p.key);
|
|
144
|
-
}
|
|
131
|
+
})
|
|
132
|
+
: new OpraDeleteInstanceQuery(resource, p.key);
|
|
145
133
|
break;
|
|
146
134
|
}
|
|
147
135
|
case 'POST': {
|
|
148
136
|
if (scope === 'collection') {
|
|
149
|
-
query =
|
|
137
|
+
query = new OpraCreateInstanceQuery(resource, body, {
|
|
150
138
|
pick: url.searchParams.get('$pick'),
|
|
151
139
|
omit: url.searchParams.get('$omit'),
|
|
152
140
|
include: url.searchParams.get('$include')
|
|
@@ -155,18 +143,15 @@ export class OpraHttpAdapter extends OpraAdapter {
|
|
|
155
143
|
break;
|
|
156
144
|
}
|
|
157
145
|
case 'PATCH': {
|
|
158
|
-
|
|
159
|
-
|
|
146
|
+
query = scope === 'collection'
|
|
147
|
+
? new OpraUpdateCollectionQuery(resource, body, {
|
|
160
148
|
filter: url.searchParams.get('$filter')
|
|
161
|
-
})
|
|
162
|
-
|
|
163
|
-
else {
|
|
164
|
-
query = OpraQuery.forUpdate(resource, p.key, body, {
|
|
149
|
+
})
|
|
150
|
+
: new OpraUpdateInstanceQuery(resource, p.key, body, {
|
|
165
151
|
pick: url.searchParams.get('$pick'),
|
|
166
152
|
omit: url.searchParams.get('$omit'),
|
|
167
153
|
include: url.searchParams.get('$include')
|
|
168
154
|
});
|
|
169
|
-
}
|
|
170
155
|
break;
|
|
171
156
|
}
|
|
172
157
|
}
|
|
@@ -176,7 +161,9 @@ export class OpraHttpAdapter extends OpraAdapter {
|
|
|
176
161
|
throw new InternalServerError();
|
|
177
162
|
}
|
|
178
163
|
catch (e) {
|
|
179
|
-
|
|
164
|
+
if (e instanceof OpraException)
|
|
165
|
+
throw e;
|
|
166
|
+
throw new BadRequestError(e);
|
|
180
167
|
}
|
|
181
168
|
}
|
|
182
169
|
async sendResponse(executionContext, queryContexts) {
|
|
@@ -189,19 +176,12 @@ export class OpraHttpAdapter extends OpraAdapter {
|
|
|
189
176
|
// this.writeError([], new InternalServerError({message: 'Not implemented yet'}));
|
|
190
177
|
return;
|
|
191
178
|
}
|
|
192
|
-
if (!outputPackets.length)
|
|
193
|
-
|
|
194
|
-
outputPackets.push({
|
|
195
|
-
status: err.status,
|
|
196
|
-
body: {
|
|
197
|
-
errors: [err.response]
|
|
198
|
-
}
|
|
199
|
-
});
|
|
200
|
-
}
|
|
179
|
+
if (!outputPackets.length)
|
|
180
|
+
return this.sendError(executionContext, new NotFoundError());
|
|
201
181
|
const out = outputPackets[0];
|
|
202
182
|
const resp = executionContext.getResponseWrapper();
|
|
203
183
|
resp.setStatus(out.status);
|
|
204
|
-
resp.setHeader(HttpHeaders.Content_Type, 'application/json');
|
|
184
|
+
resp.setHeader(HttpHeaders.Content_Type, 'application/opra+json');
|
|
205
185
|
resp.setHeader(HttpHeaders.Cache_Control, 'no-cache');
|
|
206
186
|
resp.setHeader(HttpHeaders.Pragma, 'no-cache');
|
|
207
187
|
resp.setHeader(HttpHeaders.Expires, '-1');
|
|
@@ -219,34 +199,35 @@ export class OpraHttpAdapter extends OpraAdapter {
|
|
|
219
199
|
}
|
|
220
200
|
createOutput(ctx) {
|
|
221
201
|
const { query } = ctx;
|
|
222
|
-
let
|
|
223
|
-
let
|
|
224
|
-
const errors = ctx.
|
|
202
|
+
let body;
|
|
203
|
+
let status = ctx.status || 0;
|
|
204
|
+
const errors = ctx.errors.map(e => wrapException(e));
|
|
225
205
|
if (errors && errors.length) {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
206
|
+
// Sort errors from fatal to info
|
|
207
|
+
errors.sort((a, b) => {
|
|
208
|
+
const i = IssueSeverity.Keys.indexOf(a.issue.severity) - IssueSeverity.Keys.indexOf(b.issue.severity);
|
|
209
|
+
if (i === 0)
|
|
210
|
+
return b.status - a.status;
|
|
211
|
+
return i;
|
|
212
|
+
});
|
|
213
|
+
if (!status || status < HttpStatus.BAD_REQUEST) {
|
|
214
|
+
status = errors[0].status;
|
|
231
215
|
if (status < HttpStatus.BAD_REQUEST)
|
|
232
216
|
status = HttpStatus.INTERNAL_SERVER_ERROR;
|
|
233
217
|
}
|
|
234
|
-
body
|
|
218
|
+
body = {
|
|
219
|
+
operation: ctx.query.method,
|
|
220
|
+
errors: errors.map(e => e.issue)
|
|
221
|
+
};
|
|
235
222
|
}
|
|
236
223
|
else {
|
|
237
|
-
|
|
224
|
+
body = ctx.response;
|
|
238
225
|
status = status || (query.operation === 'create' ? HttpStatus.CREATED : HttpStatus.OK);
|
|
239
226
|
}
|
|
240
|
-
// Convert headers map to object
|
|
241
|
-
const headers = Array.from(ctx.response.headers.keys()).map(k => k.toLowerCase()).sort()
|
|
242
|
-
.reduce((a, k) => {
|
|
243
|
-
a[k] = ctx.response.headers.get(k);
|
|
244
|
-
return a;
|
|
245
|
-
}, {});
|
|
246
227
|
body = this.i18n.deep(body);
|
|
247
228
|
return {
|
|
248
229
|
status,
|
|
249
|
-
headers,
|
|
230
|
+
headers: ctx.responseHeaders.toObject(),
|
|
250
231
|
body
|
|
251
232
|
};
|
|
252
233
|
}
|
|
@@ -258,6 +239,11 @@ export class OpraHttpAdapter extends OpraAdapter {
|
|
|
258
239
|
resp.setHeader(HttpHeaders.Pragma, 'no-cache');
|
|
259
240
|
resp.setHeader(HttpHeaders.Expires, '-1');
|
|
260
241
|
resp.setHeader(HttpHeaders.X_Opra_Version, OpraSchema.Version);
|
|
261
|
-
|
|
242
|
+
const issue = this.i18n.deep(error.issue);
|
|
243
|
+
const body = {
|
|
244
|
+
operation: 'unknown',
|
|
245
|
+
errors: [issue]
|
|
246
|
+
};
|
|
247
|
+
resp.send(JSON.stringify(body));
|
|
262
248
|
}
|
|
263
249
|
}
|
|
@@ -1,31 +1,25 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { OpraException } from '@opra/exception';
|
|
2
|
+
import { OpraAnyQuery, OpraService } from '@opra/schema';
|
|
2
3
|
import { SearchParams } from '@opra/url';
|
|
3
4
|
import { HttpStatus } from '../enums/index.js';
|
|
4
|
-
import { ApiException } from '../exception/index.js';
|
|
5
5
|
import { ContextType, IExecutionContext, IHttpExecutionContext } from '../interfaces/execution-context.interface.js';
|
|
6
|
-
import {
|
|
6
|
+
import { HeadersMap } from './headers-map.js';
|
|
7
7
|
export declare type QueryContextArgs = Pick<QueryContext, 'service' | 'executionContext' | 'query' | 'params' | 'headers' | 'userContext' | 'parentValue' | 'continueOnError'>;
|
|
8
8
|
export declare class QueryContext {
|
|
9
9
|
readonly service: OpraService;
|
|
10
10
|
readonly executionContext: IExecutionContext;
|
|
11
|
-
readonly query:
|
|
11
|
+
readonly query: OpraAnyQuery;
|
|
12
12
|
readonly params: SearchParams;
|
|
13
|
-
readonly headers:
|
|
13
|
+
readonly headers: HeadersMap;
|
|
14
14
|
readonly parentValue?: any;
|
|
15
15
|
readonly resultPath: string;
|
|
16
|
-
readonly
|
|
16
|
+
readonly responseHeaders: HeadersMap;
|
|
17
|
+
response?: any;
|
|
18
|
+
errors: OpraException[];
|
|
19
|
+
status?: HttpStatus;
|
|
17
20
|
userContext?: any;
|
|
18
21
|
continueOnError?: boolean;
|
|
19
22
|
constructor(args: QueryContextArgs);
|
|
20
23
|
get type(): ContextType;
|
|
21
24
|
switchToHttp(): IHttpExecutionContext;
|
|
22
25
|
}
|
|
23
|
-
export declare type QueryResponseArgs = Pick<QueryResponse, 'status' | 'value' | 'total'>;
|
|
24
|
-
export declare class QueryResponse {
|
|
25
|
-
headers: ResponsiveMap<string, string>;
|
|
26
|
-
errors: ApiException[];
|
|
27
|
-
status?: HttpStatus;
|
|
28
|
-
value?: any;
|
|
29
|
-
total?: number;
|
|
30
|
-
constructor(args?: QueryResponseArgs);
|
|
31
|
-
}
|