@opra/core 0.0.7 → 0.0.9
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/exception/api-exception.js +30 -33
- package/cjs/exception/errors/bad-request.error.js +6 -3
- package/cjs/exception/errors/failed-dependency.error.js +6 -3
- package/cjs/exception/errors/forbidden.error.js +6 -3
- package/cjs/exception/errors/internal-server.error.js +9 -4
- package/cjs/exception/errors/method-not-allowed.error.js +6 -3
- package/cjs/exception/errors/not-found.error.js +6 -3
- package/cjs/exception/errors/unauthorized.error.js +6 -3
- package/cjs/exception/errors/unprocessable-entity.error.js +6 -3
- package/cjs/exception/wrap-error.js +1 -1
- package/cjs/implementation/adapter/adapter.js +29 -26
- package/cjs/implementation/adapter/express-adapter.js +40 -9
- package/cjs/implementation/adapter/http-adapter.js +27 -34
- package/cjs/implementation/{execution-context.js → query-context.js} +18 -22
- package/cjs/implementation/resource/entity-resource-handler.js +16 -12
- package/cjs/implementation/resource/resource-handler.js +1 -1
- package/cjs/implementation/schema-generator.js +38 -51
- package/cjs/index.js +3 -4
- package/cjs/interfaces/{http-context.interface.js → execution-context.interface.js} +0 -0
- package/cjs/interfaces/{execution-query.interface.js → query.interface.js} +16 -16
- package/cjs/utils/internal-data-types.js +54 -17
- package/esm/exception/api-exception.d.ts +3 -2
- package/esm/exception/api-exception.js +30 -33
- package/esm/exception/errors/bad-request.error.d.ts +2 -1
- package/esm/exception/errors/bad-request.error.js +6 -3
- package/esm/exception/errors/failed-dependency.error.d.ts +2 -1
- package/esm/exception/errors/failed-dependency.error.js +6 -3
- package/esm/exception/errors/forbidden.error.d.ts +2 -1
- package/esm/exception/errors/forbidden.error.js +6 -3
- package/esm/exception/errors/internal-server.error.d.ts +2 -1
- package/esm/exception/errors/internal-server.error.js +8 -4
- package/esm/exception/errors/method-not-allowed.error.d.ts +2 -1
- package/esm/exception/errors/method-not-allowed.error.js +6 -3
- package/esm/exception/errors/not-found.error.d.ts +2 -1
- package/esm/exception/errors/not-found.error.js +6 -3
- package/esm/exception/errors/unauthorized.error.d.ts +2 -1
- package/esm/exception/errors/unauthorized.error.js +6 -3
- package/esm/exception/errors/unprocessable-entity.error.d.ts +2 -1
- package/esm/exception/errors/unprocessable-entity.error.js +6 -3
- package/esm/exception/wrap-error.js +1 -1
- package/esm/implementation/adapter/adapter.d.ts +22 -14
- package/esm/implementation/adapter/adapter.js +29 -26
- package/esm/implementation/adapter/express-adapter.d.ts +2 -2
- package/esm/implementation/adapter/express-adapter.js +40 -9
- package/esm/implementation/adapter/http-adapter.d.ts +11 -12
- package/esm/implementation/adapter/http-adapter.js +27 -34
- package/esm/implementation/query-context.d.ts +32 -0
- package/esm/implementation/{execution-context.js → query-context.js} +15 -18
- package/esm/implementation/resource/container-resource-handler.d.ts +2 -2
- package/esm/implementation/resource/entity-resource-handler.d.ts +3 -3
- package/esm/implementation/resource/entity-resource-handler.js +16 -12
- package/esm/implementation/resource/resource-handler.d.ts +3 -3
- package/esm/implementation/resource/resource-handler.js +1 -1
- package/esm/implementation/schema-generator.js +38 -51
- package/esm/index.d.ts +3 -4
- package/esm/index.js +3 -4
- package/esm/interfaces/execution-context.interface.d.ts +39 -0
- package/esm/interfaces/{http-context.interface.js → execution-context.interface.js} +0 -0
- package/esm/interfaces/{execution-query.interface.d.ts → query.interface.d.ts} +31 -25
- package/esm/interfaces/{execution-query.interface.js → query.interface.js} +15 -15
- package/esm/services/entity-resource-controller.d.ts +11 -11
- package/esm/utils/internal-data-types.d.ts +2 -1
- package/esm/utils/internal-data-types.js +53 -16
- package/package.json +6 -5
- package/cjs/interfaces/user-context.interface.js +0 -2
- package/esm/implementation/execution-context.d.ts +0 -42
- package/esm/interfaces/http-context.interface.d.ts +0 -23
- package/esm/interfaces/user-context.interface.d.ts +0 -3
- package/esm/interfaces/user-context.interface.js +0 -1
|
@@ -4,5 +4,6 @@ import { ApiException, ErrorResponse } from '../api-exception.js';
|
|
|
4
4
|
* The server has encountered a situation it does not know how to handle.
|
|
5
5
|
*/
|
|
6
6
|
export declare class InternalServerError extends ApiException {
|
|
7
|
-
constructor(response?: ErrorResponse, cause?: Error);
|
|
7
|
+
constructor(response?: string | ErrorResponse | Error, cause?: Error);
|
|
8
|
+
protected _initResponse(response: Partial<ErrorResponse>): void;
|
|
8
9
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import _ from 'lodash';
|
|
1
2
|
import { translate } from '@opra/i18n';
|
|
2
3
|
import { HttpStatus } from '../../enums/index.js';
|
|
3
4
|
import { ApiException } from '../api-exception.js';
|
|
@@ -7,12 +8,15 @@ import { ApiException } from '../api-exception.js';
|
|
|
7
8
|
*/
|
|
8
9
|
export class InternalServerError extends ApiException {
|
|
9
10
|
constructor(response, cause) {
|
|
10
|
-
super(
|
|
11
|
+
super(response, cause);
|
|
12
|
+
this.status = HttpStatus.INTERNAL_SERVER_ERROR;
|
|
13
|
+
}
|
|
14
|
+
_initResponse(response) {
|
|
15
|
+
super._initResponse({
|
|
11
16
|
message: translate('error:INTERNAL_SERVER_ERROR', 'Internal server error'),
|
|
12
17
|
severity: 'error',
|
|
13
18
|
code: 'INTERNAL_SERVER_ERROR',
|
|
14
|
-
...response
|
|
15
|
-
}
|
|
16
|
-
this.status = HttpStatus.INTERNAL_SERVER_ERROR;
|
|
19
|
+
..._.omitBy(response, _.isNil)
|
|
20
|
+
});
|
|
17
21
|
}
|
|
18
22
|
}
|
|
@@ -5,5 +5,6 @@ import { ApiException, ErrorResponse } from '../api-exception.js';
|
|
|
5
5
|
* For example, an API may not allow calling DELETE to remove a resource.
|
|
6
6
|
*/
|
|
7
7
|
export declare class MethodNotAllowedError extends ApiException {
|
|
8
|
-
constructor(response?: ErrorResponse, cause?: Error);
|
|
8
|
+
constructor(response?: string | ErrorResponse | Error, cause?: Error);
|
|
9
|
+
protected _initResponse(response: Partial<ErrorResponse>): void;
|
|
9
10
|
}
|
|
@@ -8,12 +8,15 @@ import { ApiException } from '../api-exception.js';
|
|
|
8
8
|
*/
|
|
9
9
|
export class MethodNotAllowedError extends ApiException {
|
|
10
10
|
constructor(response, cause) {
|
|
11
|
-
super(
|
|
11
|
+
super(response, cause);
|
|
12
|
+
this.status = HttpStatus.METHOD_NOT_ALLOWED;
|
|
13
|
+
}
|
|
14
|
+
_initResponse(response) {
|
|
15
|
+
super._initResponse({
|
|
12
16
|
message: translate('error:METHOD_NOT_ALLOWED', 'Method Not Allowed'),
|
|
13
17
|
severity: 'error',
|
|
14
18
|
code: 'METHOD_NOT_ALLOWED',
|
|
15
19
|
...response
|
|
16
|
-
}
|
|
17
|
-
this.status = HttpStatus.METHOD_NOT_ALLOWED;
|
|
20
|
+
});
|
|
18
21
|
}
|
|
19
22
|
}
|
|
@@ -8,5 +8,6 @@ import { ApiException, ErrorResponse } from '../api-exception.js';
|
|
|
8
8
|
* frequent occurrence on the web.
|
|
9
9
|
*/
|
|
10
10
|
export declare class NotFoundError extends ApiException {
|
|
11
|
-
constructor(response?: ErrorResponse, cause?: Error);
|
|
11
|
+
constructor(response?: string | ErrorResponse | Error, cause?: Error);
|
|
12
|
+
protected _initResponse(response: Partial<ErrorResponse>): void;
|
|
12
13
|
}
|
|
@@ -11,12 +11,15 @@ import { ApiException } from '../api-exception.js';
|
|
|
11
11
|
*/
|
|
12
12
|
export class NotFoundError extends ApiException {
|
|
13
13
|
constructor(response, cause) {
|
|
14
|
-
super(
|
|
14
|
+
super(response, cause);
|
|
15
|
+
this.status = HttpStatus.NOT_FOUND;
|
|
16
|
+
}
|
|
17
|
+
_initResponse(response) {
|
|
18
|
+
super._initResponse({
|
|
15
19
|
message: translate('error:NOT_FOUND', 'Not found'),
|
|
16
20
|
severity: 'error',
|
|
17
21
|
code: 'NOT_FOUND',
|
|
18
22
|
...response
|
|
19
|
-
}
|
|
20
|
-
this.status = HttpStatus.NOT_FOUND;
|
|
23
|
+
});
|
|
21
24
|
}
|
|
22
25
|
}
|
|
@@ -5,5 +5,6 @@ import { ApiException, ErrorResponse } from '../api-exception.js';
|
|
|
5
5
|
* That is, the client must authenticate itself to get the requested response.
|
|
6
6
|
*/
|
|
7
7
|
export declare class UnauthorizedError extends ApiException {
|
|
8
|
-
constructor(response?: ErrorResponse, cause?: Error);
|
|
8
|
+
constructor(response?: string | ErrorResponse | Error, cause?: Error);
|
|
9
|
+
protected _initResponse(response: Partial<ErrorResponse>): void;
|
|
9
10
|
}
|
|
@@ -8,12 +8,15 @@ import { ApiException } from '../api-exception.js';
|
|
|
8
8
|
*/
|
|
9
9
|
export class UnauthorizedError extends ApiException {
|
|
10
10
|
constructor(response, cause) {
|
|
11
|
-
super(
|
|
11
|
+
super(response, cause);
|
|
12
|
+
this.status = HttpStatus.UNAUTHORIZED;
|
|
13
|
+
}
|
|
14
|
+
_initResponse(response) {
|
|
15
|
+
super._initResponse({
|
|
12
16
|
message: translate('error:UNAUTHORIZED', 'Unauthorized'),
|
|
13
17
|
severity: 'error',
|
|
14
18
|
code: 'UNAUTHORIZED',
|
|
15
19
|
...response
|
|
16
|
-
}
|
|
17
|
-
this.status = HttpStatus.UNAUTHORIZED;
|
|
20
|
+
});
|
|
18
21
|
}
|
|
19
22
|
}
|
|
@@ -4,5 +4,6 @@ import { ApiException, ErrorResponse } from '../api-exception.js';
|
|
|
4
4
|
* The request was well-formed but was unable to be followed due to semantic errors.
|
|
5
5
|
*/
|
|
6
6
|
export declare class UnprocessableEntityError extends ApiException {
|
|
7
|
-
constructor(response?: ErrorResponse, cause?: Error);
|
|
7
|
+
constructor(response?: string | ErrorResponse | Error, cause?: Error);
|
|
8
|
+
protected _initResponse(response: Partial<ErrorResponse>): void;
|
|
8
9
|
}
|
|
@@ -7,12 +7,15 @@ import { ApiException } from '../api-exception.js';
|
|
|
7
7
|
*/
|
|
8
8
|
export class UnprocessableEntityError extends ApiException {
|
|
9
9
|
constructor(response, cause) {
|
|
10
|
-
super(
|
|
10
|
+
super(response, cause);
|
|
11
|
+
this.status = HttpStatus.UNPROCESSABLE_ENTITY;
|
|
12
|
+
}
|
|
13
|
+
_initResponse(response) {
|
|
14
|
+
super._initResponse({
|
|
11
15
|
message: translate('error:UNPROCESSABLE_ENTITY', 'Unprocessable entity'),
|
|
12
16
|
severity: 'error',
|
|
13
17
|
code: 'UNPROCESSABLE_ENTITY',
|
|
14
18
|
...response
|
|
15
|
-
}
|
|
16
|
-
this.status = HttpStatus.UNPROCESSABLE_ENTITY;
|
|
19
|
+
});
|
|
17
20
|
}
|
|
18
21
|
}
|
|
@@ -7,7 +7,7 @@ export function wrapError(response) {
|
|
|
7
7
|
const x = response;
|
|
8
8
|
if (typeof x.status === 'number' || typeof x.getStatus === 'function')
|
|
9
9
|
return new ApiException(response);
|
|
10
|
-
return new InternalServerError(
|
|
10
|
+
return new InternalServerError(response);
|
|
11
11
|
}
|
|
12
12
|
return new InternalServerError();
|
|
13
13
|
}
|
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
import { FallbackLng, I18n, LanguageResource } from '@opra/i18n';
|
|
2
2
|
import { ApiException } from '../../exception/index.js';
|
|
3
|
-
import {
|
|
3
|
+
import { IExecutionContext } from '../../interfaces/execution-context.interface.js';
|
|
4
4
|
import { OpraService } from '../opra-service.js';
|
|
5
|
+
import { QueryContext } from '../query-context.js';
|
|
5
6
|
export declare namespace OpraAdapter {
|
|
7
|
+
type UserContextResolver = (args: {
|
|
8
|
+
executionContext: IExecutionContext;
|
|
9
|
+
isBatch: boolean;
|
|
10
|
+
}) => object | Promise<object>;
|
|
11
|
+
type RequestFinishEvent = (args: {
|
|
12
|
+
executionContext: IExecutionContext;
|
|
13
|
+
userContext?: any;
|
|
14
|
+
failed: boolean;
|
|
15
|
+
}) => Promise<void>;
|
|
6
16
|
interface Options {
|
|
7
17
|
i18n?: I18n | I18nOptions | (() => Promise<I18n>);
|
|
8
|
-
userContext?:
|
|
9
|
-
platform: string;
|
|
10
|
-
isBatch: boolean;
|
|
11
|
-
}) => object | Promise<object>;
|
|
18
|
+
userContext?: UserContextResolver;
|
|
12
19
|
}
|
|
13
20
|
interface I18nOptions {
|
|
14
21
|
/**
|
|
@@ -37,17 +44,18 @@ export declare namespace OpraAdapter {
|
|
|
37
44
|
*/
|
|
38
45
|
resourceDirs?: string[];
|
|
39
46
|
}
|
|
40
|
-
type UserContextResolver = (isBatch: boolean) => any | Promise<any>;
|
|
41
47
|
}
|
|
42
|
-
export declare abstract class OpraAdapter<
|
|
48
|
+
export declare abstract class OpraAdapter<TExecutionContext extends IExecutionContext> {
|
|
43
49
|
readonly service: OpraService;
|
|
44
50
|
readonly i18n: I18n;
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
protected abstract
|
|
50
|
-
protected abstract
|
|
51
|
-
protected
|
|
51
|
+
readonly userContextResolver?: OpraAdapter.UserContextResolver;
|
|
52
|
+
constructor(service: OpraService, options?: Omit<OpraAdapter.Options, 'i18n'> & {
|
|
53
|
+
i18n: I18n;
|
|
54
|
+
});
|
|
55
|
+
protected abstract prepareRequests(executionContext: TExecutionContext): QueryContext[];
|
|
56
|
+
protected abstract sendResponse(executionContext: TExecutionContext, queryContexts: QueryContext[]): Promise<void>;
|
|
57
|
+
protected abstract sendError(executionContext: TExecutionContext, error: ApiException): Promise<void>;
|
|
58
|
+
protected abstract isBatch(executionContext: TExecutionContext): boolean;
|
|
59
|
+
protected handler(executionContext: TExecutionContext): Promise<void>;
|
|
52
60
|
protected static initI18n(options?: OpraAdapter.Options): Promise<I18n>;
|
|
53
61
|
}
|
|
@@ -1,35 +1,35 @@
|
|
|
1
|
+
import { AsyncEventEmitter } from 'strict-typed-events';
|
|
1
2
|
import { I18n } from '@opra/i18n';
|
|
2
3
|
import { FailedDependencyError } from '../../exception/index.js';
|
|
3
4
|
import { wrapError } from '../../exception/wrap-error.js';
|
|
4
5
|
export class OpraAdapter {
|
|
5
6
|
service;
|
|
6
7
|
i18n;
|
|
7
|
-
|
|
8
|
+
userContextResolver;
|
|
9
|
+
constructor(service, options) {
|
|
8
10
|
this.service = service;
|
|
9
|
-
this.i18n = i18n || I18n.defaultInstance;
|
|
11
|
+
this.i18n = options?.i18n || I18n.defaultInstance;
|
|
12
|
+
this.userContextResolver = options?.userContext;
|
|
10
13
|
}
|
|
11
|
-
async handler(
|
|
14
|
+
async handler(executionContext) {
|
|
12
15
|
if (!this.i18n.isInitialized)
|
|
13
16
|
await this.i18n.init();
|
|
14
|
-
|
|
15
|
-
let requests;
|
|
17
|
+
let queryContexts;
|
|
16
18
|
let userContext;
|
|
19
|
+
let failed = false;
|
|
17
20
|
try {
|
|
18
|
-
|
|
21
|
+
queryContexts = this.prepareRequests(executionContext);
|
|
19
22
|
let stop = false;
|
|
20
23
|
// Read requests can be executed simultaneously, write request should be executed one by one
|
|
21
24
|
let promises;
|
|
22
25
|
let exclusive = false;
|
|
23
|
-
for (const
|
|
24
|
-
exclusive = exclusive ||
|
|
26
|
+
for (const context of queryContexts) {
|
|
27
|
+
exclusive = exclusive || context.query.operationType !== 'read';
|
|
25
28
|
// Wait previous read requests before executing update request
|
|
26
29
|
if (exclusive && promises) {
|
|
27
30
|
await Promise.allSettled(promises);
|
|
28
31
|
promises = undefined;
|
|
29
32
|
}
|
|
30
|
-
const resource = request.query.resource;
|
|
31
|
-
const context = this.createExecutionContext(adapterContext, request);
|
|
32
|
-
executionContexts.push(context);
|
|
33
33
|
// If previous request in bucket had an error and executed an update
|
|
34
34
|
// we do not execute next requests
|
|
35
35
|
if (stop) {
|
|
@@ -37,10 +37,14 @@ export class OpraAdapter {
|
|
|
37
37
|
continue;
|
|
38
38
|
}
|
|
39
39
|
try {
|
|
40
|
+
const resource = context.query.resource;
|
|
40
41
|
const promise = (async () => {
|
|
41
42
|
await resource.prepare(context);
|
|
42
|
-
if (userContextResolver && !userContext)
|
|
43
|
-
userContext =
|
|
43
|
+
if (this.userContextResolver && !userContext)
|
|
44
|
+
userContext = this.userContextResolver({
|
|
45
|
+
executionContext,
|
|
46
|
+
isBatch: this.isBatch(executionContext)
|
|
47
|
+
});
|
|
44
48
|
context.userContext = userContext;
|
|
45
49
|
await resource.execute(context);
|
|
46
50
|
})().catch(e => {
|
|
@@ -66,22 +70,21 @@ export class OpraAdapter {
|
|
|
66
70
|
}
|
|
67
71
|
if (promises)
|
|
68
72
|
await Promise.allSettled(promises);
|
|
69
|
-
|
|
70
|
-
try {
|
|
71
|
-
const hasError = !!executionContexts.find(ctx => ctx.response.errors?.length);
|
|
72
|
-
await userContext.onRequestFinish(hasError);
|
|
73
|
-
}
|
|
74
|
-
catch (e) {
|
|
75
|
-
await this.sendError(adapterContext, wrapError(e));
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
await this.sendResponse(adapterContext, executionContexts);
|
|
73
|
+
await this.sendResponse(executionContext, queryContexts);
|
|
80
74
|
}
|
|
81
75
|
catch (e) {
|
|
76
|
+
failed = true;
|
|
82
77
|
const error = wrapError(e);
|
|
83
|
-
await this.sendError(
|
|
84
|
-
|
|
78
|
+
await this.sendError(executionContext, error);
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
if (executionContext instanceof AsyncEventEmitter) {
|
|
82
|
+
await executionContext
|
|
83
|
+
.emitAsyncSerial('finish', {
|
|
84
|
+
userContext,
|
|
85
|
+
failed
|
|
86
|
+
}).catch();
|
|
87
|
+
}
|
|
85
88
|
}
|
|
86
89
|
}
|
|
87
90
|
static async initI18n(options) {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { Application } from 'express';
|
|
2
|
-
import type {
|
|
2
|
+
import type { IHttpExecutionContext } from '../../interfaces/execution-context.interface';
|
|
3
3
|
import type { OpraService } from '../opra-service.js';
|
|
4
4
|
import { OpraHttpAdapter } from './http-adapter.js';
|
|
5
5
|
export declare namespace OpraExpressAdapter {
|
|
6
6
|
interface Options extends OpraHttpAdapter.Options {
|
|
7
7
|
}
|
|
8
8
|
}
|
|
9
|
-
export declare class OpraExpressAdapter extends OpraHttpAdapter<
|
|
9
|
+
export declare class OpraExpressAdapter extends OpraHttpAdapter<IHttpExecutionContext> {
|
|
10
10
|
static init(app: Application, service: OpraService, options?: OpraExpressAdapter.Options): Promise<OpraExpressAdapter>;
|
|
11
11
|
}
|
|
@@ -1,25 +1,56 @@
|
|
|
1
|
+
import { AsyncEventEmitter } from 'strict-typed-events';
|
|
1
2
|
import { normalizePath } from '@opra/url';
|
|
2
3
|
import { OpraHttpAdapter } from './http-adapter.js';
|
|
3
4
|
export class OpraExpressAdapter extends OpraHttpAdapter {
|
|
4
5
|
static async init(app, service, options) {
|
|
5
6
|
const i18n = await this.initI18n(options);
|
|
6
|
-
const adapter = new OpraExpressAdapter(service,
|
|
7
|
+
const adapter = new OpraExpressAdapter(service, {
|
|
8
|
+
...options,
|
|
9
|
+
i18n
|
|
10
|
+
});
|
|
7
11
|
const prefix = '/' + normalizePath(options?.prefix, true);
|
|
8
|
-
const userContextResolver = options?.userContext;
|
|
9
12
|
app.use(prefix, (request, response, next) => {
|
|
10
13
|
(async () => {
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
const adapterContext = {
|
|
14
|
-
getRequest: () => req,
|
|
15
|
-
getResponse: () => res
|
|
16
|
-
};
|
|
17
|
-
await adapter.handler(adapterContext, (isBatch) => userContextResolver && userContextResolver(request, { platform: 'express', isBatch }));
|
|
14
|
+
const executionContext = new ExpressExecutionContext(request, response);
|
|
15
|
+
await adapter.handler(executionContext);
|
|
18
16
|
})().catch(e => next(e));
|
|
19
17
|
});
|
|
20
18
|
return adapter;
|
|
21
19
|
}
|
|
22
20
|
}
|
|
21
|
+
class ExpressExecutionContext extends AsyncEventEmitter {
|
|
22
|
+
_request;
|
|
23
|
+
_response;
|
|
24
|
+
constructor(request, response) {
|
|
25
|
+
super();
|
|
26
|
+
this._request = new ExpressRequestWrapper(request);
|
|
27
|
+
this._response = new ExpressResponseWrapper(response);
|
|
28
|
+
}
|
|
29
|
+
getType() {
|
|
30
|
+
return 'http';
|
|
31
|
+
}
|
|
32
|
+
getPlatform() {
|
|
33
|
+
return 'express';
|
|
34
|
+
}
|
|
35
|
+
switchToHttp() {
|
|
36
|
+
return this;
|
|
37
|
+
}
|
|
38
|
+
getRequest() {
|
|
39
|
+
return this._request.getInstance();
|
|
40
|
+
}
|
|
41
|
+
getResponse() {
|
|
42
|
+
return this._response.getInstance();
|
|
43
|
+
}
|
|
44
|
+
getRequestWrapper() {
|
|
45
|
+
return this._request;
|
|
46
|
+
}
|
|
47
|
+
getResponseWrapper() {
|
|
48
|
+
return this._response;
|
|
49
|
+
}
|
|
50
|
+
onFinish(fn) {
|
|
51
|
+
this.on('finish', fn);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
23
54
|
class ExpressRequestWrapper {
|
|
24
55
|
instance;
|
|
25
56
|
constructor(instance) {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { OpraURL } from '@opra/url';
|
|
2
2
|
import { ApiException } from '../../exception/index.js';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { IHttpExecutionContext } from '../../interfaces/execution-context.interface.js';
|
|
4
|
+
import { OpraQuery } from '../../interfaces/query.interface.js';
|
|
5
5
|
import { HeadersObject } from '../../utils/headers.js';
|
|
6
|
-
import {
|
|
6
|
+
import { QueryContext } from '../query-context.js';
|
|
7
7
|
import { OpraAdapter } from './adapter.js';
|
|
8
8
|
export declare namespace OpraHttpAdapter {
|
|
9
9
|
type Options = OpraAdapter.Options & {
|
|
@@ -15,14 +15,13 @@ interface PreparedOutput {
|
|
|
15
15
|
headers?: Record<string, string>;
|
|
16
16
|
body?: any;
|
|
17
17
|
}
|
|
18
|
-
export declare class OpraHttpAdapter<
|
|
19
|
-
protected prepareRequests(
|
|
20
|
-
prepareRequest(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
protected
|
|
24
|
-
protected
|
|
25
|
-
protected
|
|
26
|
-
protected sendError(adapterContext: TAdapterContext, error: ApiException): Promise<void>;
|
|
18
|
+
export declare class OpraHttpAdapter<TExecutionContext extends IHttpExecutionContext> extends OpraAdapter<IHttpExecutionContext> {
|
|
19
|
+
protected prepareRequests(executionContext: TExecutionContext): QueryContext[];
|
|
20
|
+
prepareRequest(executionContext: IHttpExecutionContext, url: OpraURL, method: string, headers: HeadersObject, body?: any): QueryContext;
|
|
21
|
+
buildQuery(url: OpraURL, method: string, body?: any): OpraQuery | undefined;
|
|
22
|
+
protected sendResponse(executionContext: TExecutionContext, queryContexts: QueryContext[]): Promise<void>;
|
|
23
|
+
protected isBatch(executionContext: TExecutionContext): boolean;
|
|
24
|
+
protected createOutput(ctx: QueryContext): PreparedOutput;
|
|
25
|
+
protected sendError(executionContext: TExecutionContext, error: ApiException): Promise<void>;
|
|
27
26
|
}
|
|
28
27
|
export {};
|
|
@@ -3,26 +3,26 @@ import { OpraVersion } from '../../constants.js';
|
|
|
3
3
|
import { HttpHeaders, HttpStatus } from '../../enums/index.js';
|
|
4
4
|
import { ApiException, BadRequestError, InternalServerError, MethodNotAllowedError, NotFoundError, } from '../../exception/index.js';
|
|
5
5
|
import { wrapError } from '../../exception/wrap-error.js';
|
|
6
|
-
import {
|
|
6
|
+
import { OpraQuery } from '../../interfaces/query.interface.js';
|
|
7
7
|
import { Headers } from '../../utils/headers.js';
|
|
8
8
|
import { ComplexType } from '../data-type/complex-type.js';
|
|
9
|
-
import {
|
|
9
|
+
import { QueryContext } from '../query-context.js';
|
|
10
10
|
import { ContainerResourceHandler } from '../resource/container-resource-handler.js';
|
|
11
11
|
import { EntityResourceHandler } from '../resource/entity-resource-handler.js';
|
|
12
12
|
import { OpraAdapter } from './adapter.js';
|
|
13
13
|
export class OpraHttpAdapter extends OpraAdapter {
|
|
14
|
-
prepareRequests(
|
|
15
|
-
const req =
|
|
14
|
+
prepareRequests(executionContext) {
|
|
15
|
+
const req = executionContext.getRequestWrapper();
|
|
16
16
|
// todo implement batch requests
|
|
17
|
-
if (this.isBatch(
|
|
17
|
+
if (this.isBatch(executionContext)) {
|
|
18
18
|
throw new Error('not implemented yet');
|
|
19
19
|
}
|
|
20
20
|
const url = new OpraURL(req.getUrl());
|
|
21
21
|
return [
|
|
22
|
-
this.prepareRequest(
|
|
22
|
+
this.prepareRequest(executionContext, url, req.getMethod(), Headers.from(req.getHeaders()), req.getBody())
|
|
23
23
|
];
|
|
24
24
|
}
|
|
25
|
-
prepareRequest(
|
|
25
|
+
prepareRequest(executionContext, url, method, headers, body) {
|
|
26
26
|
if (!url.path.size)
|
|
27
27
|
throw new BadRequestError();
|
|
28
28
|
if (method !== 'GET' && url.path.size > 1)
|
|
@@ -32,20 +32,13 @@ export class OpraHttpAdapter extends OpraAdapter {
|
|
|
32
32
|
throw new MethodNotAllowedError({
|
|
33
33
|
message: `Method "${method}" is not allowed by target resource`
|
|
34
34
|
});
|
|
35
|
-
return new
|
|
35
|
+
return new QueryContext({
|
|
36
|
+
service: this.service,
|
|
37
|
+
executionContext,
|
|
36
38
|
query,
|
|
37
39
|
headers,
|
|
38
40
|
params: url.searchParams,
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
createExecutionContext(adapterContext, request) {
|
|
42
|
-
return new ExecutionContext({
|
|
43
|
-
type: 'http',
|
|
44
|
-
service: this.service,
|
|
45
|
-
request,
|
|
46
|
-
response: new ExecutionResponse(),
|
|
47
|
-
adapterContext,
|
|
48
|
-
continueOnError: request.query.operationType === 'read'
|
|
41
|
+
continueOnError: query.operationType === 'read'
|
|
49
42
|
});
|
|
50
43
|
}
|
|
51
44
|
buildQuery(url, method, body) {
|
|
@@ -70,7 +63,7 @@ export class OpraHttpAdapter extends OpraAdapter {
|
|
|
70
63
|
switch (method) {
|
|
71
64
|
case 'GET': {
|
|
72
65
|
if (scope === 'collection') {
|
|
73
|
-
query =
|
|
66
|
+
query = OpraQuery.forSearch(resource, {
|
|
74
67
|
filter: url.searchParams.get('$filter'),
|
|
75
68
|
limit: url.searchParams.get('$limit'),
|
|
76
69
|
skip: url.searchParams.get('$skip'),
|
|
@@ -83,7 +76,7 @@ export class OpraHttpAdapter extends OpraAdapter {
|
|
|
83
76
|
});
|
|
84
77
|
}
|
|
85
78
|
else {
|
|
86
|
-
query =
|
|
79
|
+
query = OpraQuery.forGet(resource, p.key, {
|
|
87
80
|
pick: url.searchParams.get('$pick'),
|
|
88
81
|
omit: url.searchParams.get('$omit'),
|
|
89
82
|
include: url.searchParams.get('$include')
|
|
@@ -102,7 +95,7 @@ export class OpraHttpAdapter extends OpraAdapter {
|
|
|
102
95
|
const prop = dataType.properties?.[p.resource];
|
|
103
96
|
if (!prop)
|
|
104
97
|
throw new NotFoundError({ message: `Invalid or unknown resource path (${path})` });
|
|
105
|
-
const q =
|
|
98
|
+
const q = OpraQuery.forGetProperty(prop);
|
|
106
99
|
if (nested) {
|
|
107
100
|
nested.nested = q;
|
|
108
101
|
}
|
|
@@ -116,18 +109,18 @@ export class OpraHttpAdapter extends OpraAdapter {
|
|
|
116
109
|
}
|
|
117
110
|
case 'DELETE': {
|
|
118
111
|
if (scope === 'collection') {
|
|
119
|
-
query =
|
|
112
|
+
query = OpraQuery.forDeleteMany(resource, {
|
|
120
113
|
filter: url.searchParams.get('$filter'),
|
|
121
114
|
});
|
|
122
115
|
}
|
|
123
116
|
else {
|
|
124
|
-
query =
|
|
117
|
+
query = OpraQuery.forDelete(resource, p.key);
|
|
125
118
|
}
|
|
126
119
|
break;
|
|
127
120
|
}
|
|
128
121
|
case 'POST': {
|
|
129
122
|
if (scope === 'collection') {
|
|
130
|
-
query =
|
|
123
|
+
query = OpraQuery.forCreate(resource, body, {
|
|
131
124
|
pick: url.searchParams.get('$pick'),
|
|
132
125
|
omit: url.searchParams.get('$omit'),
|
|
133
126
|
include: url.searchParams.get('$include')
|
|
@@ -137,12 +130,12 @@ export class OpraHttpAdapter extends OpraAdapter {
|
|
|
137
130
|
}
|
|
138
131
|
case 'PATCH': {
|
|
139
132
|
if (scope === 'collection') {
|
|
140
|
-
query =
|
|
133
|
+
query = OpraQuery.forUpdateMany(resource, body, {
|
|
141
134
|
filter: url.searchParams.get('$filter')
|
|
142
135
|
});
|
|
143
136
|
}
|
|
144
137
|
else {
|
|
145
|
-
query =
|
|
138
|
+
query = OpraQuery.forUpdate(resource, p.key, body, {
|
|
146
139
|
pick: url.searchParams.get('$pick'),
|
|
147
140
|
omit: url.searchParams.get('$omit'),
|
|
148
141
|
include: url.searchParams.get('$include')
|
|
@@ -163,13 +156,13 @@ export class OpraHttpAdapter extends OpraAdapter {
|
|
|
163
156
|
throw new BadRequestError({ message: e.message });
|
|
164
157
|
}
|
|
165
158
|
}
|
|
166
|
-
async sendResponse(
|
|
159
|
+
async sendResponse(executionContext, queryContexts) {
|
|
167
160
|
const outputPackets = [];
|
|
168
|
-
for (const ctx of
|
|
161
|
+
for (const ctx of queryContexts) {
|
|
169
162
|
const v = this.createOutput(ctx);
|
|
170
163
|
outputPackets.push(v);
|
|
171
164
|
}
|
|
172
|
-
if (this.isBatch(
|
|
165
|
+
if (this.isBatch(executionContext)) {
|
|
173
166
|
// this.writeError([], new InternalServerError({message: 'Not implemented yet'}));
|
|
174
167
|
return;
|
|
175
168
|
}
|
|
@@ -183,7 +176,7 @@ export class OpraHttpAdapter extends OpraAdapter {
|
|
|
183
176
|
});
|
|
184
177
|
}
|
|
185
178
|
const out = outputPackets[0];
|
|
186
|
-
const resp =
|
|
179
|
+
const resp = executionContext.getResponseWrapper();
|
|
187
180
|
resp.setStatus(out.status);
|
|
188
181
|
resp.setHeader(HttpHeaders.Content_Type, 'application/json');
|
|
189
182
|
resp.setHeader(HttpHeaders.Cache_Control, 'no-cache');
|
|
@@ -198,11 +191,11 @@ export class OpraHttpAdapter extends OpraAdapter {
|
|
|
198
191
|
resp.send(JSON.stringify(out.body));
|
|
199
192
|
}
|
|
200
193
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
201
|
-
isBatch(
|
|
194
|
+
isBatch(executionContext) {
|
|
202
195
|
return false;
|
|
203
196
|
}
|
|
204
197
|
createOutput(ctx) {
|
|
205
|
-
const { query } = ctx
|
|
198
|
+
const { query } = ctx;
|
|
206
199
|
let status = ctx.response.status;
|
|
207
200
|
let body = ctx.response.value || {};
|
|
208
201
|
const errors = ctx.response.errors?.map(e => wrapError(e));
|
|
@@ -228,8 +221,8 @@ export class OpraHttpAdapter extends OpraAdapter {
|
|
|
228
221
|
body
|
|
229
222
|
};
|
|
230
223
|
}
|
|
231
|
-
async sendError(
|
|
232
|
-
const resp =
|
|
224
|
+
async sendError(executionContext, error) {
|
|
225
|
+
const resp = executionContext.getResponseWrapper();
|
|
233
226
|
resp.setStatus(error.status || 500);
|
|
234
227
|
resp.setHeader(HttpHeaders.Content_Type, 'application/json');
|
|
235
228
|
resp.setHeader(HttpHeaders.Cache_Control, 'no-cache');
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { SearchParams } from '@opra/url';
|
|
2
|
+
import { HttpStatus } from '../enums/index.js';
|
|
3
|
+
import { ApiException } from '../exception/index.js';
|
|
4
|
+
import { ContextType, IExecutionContext, IHttpExecutionContext } from '../interfaces/execution-context.interface.js';
|
|
5
|
+
import { OpraQuery } from '../interfaces/query.interface.js';
|
|
6
|
+
import { HeadersObject } from '../utils/headers.js';
|
|
7
|
+
import { OpraService } from './opra-service.js';
|
|
8
|
+
export declare type QueryContextArgs = Pick<QueryContext, 'service' | 'executionContext' | 'query' | 'params' | 'headers' | 'userContext' | 'parentValue' | 'continueOnError'>;
|
|
9
|
+
export declare class QueryContext {
|
|
10
|
+
readonly service: OpraService;
|
|
11
|
+
readonly executionContext: IExecutionContext;
|
|
12
|
+
readonly query: OpraQuery;
|
|
13
|
+
readonly params: SearchParams;
|
|
14
|
+
readonly headers: HeadersObject;
|
|
15
|
+
readonly parentValue?: any;
|
|
16
|
+
readonly resultPath: string;
|
|
17
|
+
readonly response: QueryResponse;
|
|
18
|
+
userContext?: any;
|
|
19
|
+
continueOnError?: boolean;
|
|
20
|
+
constructor(args: QueryContextArgs);
|
|
21
|
+
get type(): ContextType;
|
|
22
|
+
switchToHttp(): IHttpExecutionContext;
|
|
23
|
+
}
|
|
24
|
+
export declare type QueryResponseArgs = Pick<QueryResponse, 'status' | 'value' | 'total'>;
|
|
25
|
+
export declare class QueryResponse {
|
|
26
|
+
headers: HeadersObject;
|
|
27
|
+
errors: ApiException[];
|
|
28
|
+
status?: HttpStatus;
|
|
29
|
+
value?: any;
|
|
30
|
+
total?: number;
|
|
31
|
+
constructor(args?: QueryResponseArgs);
|
|
32
|
+
}
|