@opra/core 0.14.0 → 0.15.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 +88 -294
- package/cjs/adapter/http/express-adapter.js +27 -0
- package/cjs/adapter/http/http-adapter.js +466 -0
- package/cjs/adapter/http/http-request-context.host.js +28 -0
- package/cjs/adapter/http/http-request.host.js +14 -0
- package/cjs/adapter/http/http-response.host.js +14 -0
- package/cjs/adapter/internal/metadata.resource.js +27 -0
- package/cjs/adapter/request-context.host.js +30 -0
- package/cjs/adapter/request.host.js +19 -0
- package/cjs/adapter/response.host.js +20 -0
- package/cjs/augmentation/resource.augmentation.js +26 -0
- package/cjs/index.js +7 -8
- package/esm/adapter/adapter.d.ts +52 -28
- package/esm/adapter/adapter.js +88 -295
- package/esm/adapter/http/express-adapter.d.ts +11 -0
- package/esm/adapter/http/express-adapter.js +22 -0
- package/esm/adapter/http/http-adapter.d.ts +37 -0
- package/esm/adapter/http/http-adapter.js +462 -0
- package/esm/adapter/http/http-request-context.host.d.ts +16 -0
- package/esm/adapter/http/http-request-context.host.js +24 -0
- package/esm/adapter/http/http-request.host.d.ts +7 -0
- package/esm/adapter/http/http-request.host.js +10 -0
- package/esm/adapter/http/http-response.host.d.ts +7 -0
- package/esm/adapter/http/http-response.host.js +10 -0
- package/esm/{interfaces → adapter/interfaces}/logger.interface.d.ts +1 -0
- package/esm/adapter/interfaces/request-context.interface.d.ts +25 -0
- package/esm/adapter/interfaces/request.interface.d.ts +13 -0
- package/esm/adapter/interfaces/response.interface.d.ts +22 -0
- package/esm/adapter/internal/metadata.resource.d.ts +7 -0
- package/esm/adapter/internal/metadata.resource.js +24 -0
- package/esm/adapter/request-context.host.d.ts +19 -0
- package/esm/adapter/request-context.host.js +26 -0
- package/esm/adapter/request.host.d.ts +26 -0
- package/esm/adapter/request.host.js +15 -0
- package/esm/adapter/response.host.d.ts +20 -0
- package/esm/adapter/response.host.js +16 -0
- package/esm/augmentation/resource.augmentation.d.ts +33 -0
- package/esm/augmentation/resource.augmentation.js +24 -0
- package/esm/index.d.ts +7 -8
- package/esm/index.js +7 -8
- package/i18n/en/error.json +1 -1
- package/package.json +9 -7
- package/cjs/adapter/classes/execution-context.host.js +0 -16
- package/cjs/adapter/classes/express-request-wrapper.host.js +0 -36
- package/cjs/adapter/classes/express-response-wrapper.host.js +0 -55
- package/cjs/adapter/classes/http-execution-context.host.js +0 -28
- package/cjs/adapter/classes/metadata.resource.js +0 -22
- package/cjs/adapter/express-adapter.js +0 -26
- package/cjs/adapter/http-adapter.js +0 -443
- package/cjs/adapter/request-contexts/batch-request-context.js +0 -11
- package/cjs/adapter/request-contexts/request-context.js +0 -25
- package/cjs/adapter/request-contexts/single-request-context.js +0 -14
- package/cjs/interfaces/resource.interface.js +0 -2
- package/cjs/services/data-service.js +0 -9
- package/cjs/services/json-singleton-service.js +0 -96
- package/cjs/utils/create-i18n.js +0 -21
- package/cjs/utils/get-caller-file.util.js +0 -24
- package/esm/adapter/classes/execution-context.host.d.ts +0 -10
- package/esm/adapter/classes/execution-context.host.js +0 -12
- package/esm/adapter/classes/express-request-wrapper.host.d.ts +0 -19
- package/esm/adapter/classes/express-request-wrapper.host.js +0 -32
- package/esm/adapter/classes/express-response-wrapper.host.d.ts +0 -22
- package/esm/adapter/classes/express-response-wrapper.host.js +0 -51
- package/esm/adapter/classes/http-execution-context.host.d.ts +0 -13
- package/esm/adapter/classes/http-execution-context.host.js +0 -24
- package/esm/adapter/classes/metadata.resource.d.ts +0 -8
- package/esm/adapter/classes/metadata.resource.js +0 -19
- package/esm/adapter/express-adapter.d.ts +0 -11
- package/esm/adapter/express-adapter.js +0 -21
- package/esm/adapter/http-adapter.d.ts +0 -37
- package/esm/adapter/http-adapter.js +0 -439
- package/esm/adapter/request-contexts/batch-request-context.d.ts +0 -7
- package/esm/adapter/request-contexts/batch-request-context.js +0 -7
- package/esm/adapter/request-contexts/request-context.d.ts +0 -22
- package/esm/adapter/request-contexts/request-context.js +0 -21
- package/esm/adapter/request-contexts/single-request-context.d.ts +0 -10
- package/esm/adapter/request-contexts/single-request-context.js +0 -10
- package/esm/enums/issue-severity.enum.d.ts +0 -1
- package/esm/interfaces/execution-context.interface.d.ts +0 -47
- package/esm/interfaces/i18n-options.interface.d.ts +0 -28
- package/esm/interfaces/resource.interface.d.ts +0 -23
- package/esm/interfaces/resource.interface.js +0 -1
- package/esm/services/data-service.d.ts +0 -2
- package/esm/services/data-service.js +0 -5
- package/esm/services/json-singleton-service.d.ts +0 -39
- package/esm/services/json-singleton-service.js +0 -91
- package/esm/utils/create-i18n.d.ts +0 -3
- package/esm/utils/create-i18n.js +0 -16
- package/esm/utils/get-caller-file.util.d.ts +0 -1
- package/esm/utils/get-caller-file.util.js +0 -20
- /package/cjs/{interfaces → adapter/interfaces}/logger.interface.js +0 -0
- /package/cjs/{enums/issue-severity.enum.js → adapter/interfaces/request-context.interface.js} +0 -0
- /package/cjs/{interfaces/execution-context.interface.js → adapter/interfaces/request.interface.js} +0 -0
- /package/cjs/{interfaces/i18n-options.interface.js → adapter/interfaces/response.interface.js} +0 -0
- /package/esm/{interfaces → adapter/interfaces}/logger.interface.js +0 -0
- /package/esm/{enums/issue-severity.enum.js → adapter/interfaces/request-context.interface.js} +0 -0
- /package/esm/{interfaces/execution-context.interface.js → adapter/interfaces/request.interface.js} +0 -0
- /package/esm/{interfaces/i18n-options.interface.js → adapter/interfaces/response.interface.js} +0 -0
package/esm/adapter/adapter.d.ts
CHANGED
|
@@ -1,38 +1,62 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import { SingleRequestContext } from './request-contexts/single-request-context.js';
|
|
1
|
+
import { ApiDocument, FallbackLng, I18n, LanguageResource } from '@opra/common';
|
|
2
|
+
import { ILogger } from './interfaces/logger.interface.js';
|
|
3
|
+
import { RequestContext } from './interfaces/request-context.interface.js';
|
|
4
|
+
/**
|
|
5
|
+
* @namespace OpraAdapter
|
|
6
|
+
*/
|
|
8
7
|
export declare namespace OpraAdapter {
|
|
9
|
-
type UserContextResolver = (executionContext: IExecutionContext) => object | Promise<object>;
|
|
10
8
|
interface Options {
|
|
11
9
|
i18n?: I18n | I18nOptions | (() => Promise<I18n>);
|
|
12
|
-
userContext?: UserContextResolver;
|
|
13
10
|
logger?: ILogger;
|
|
14
11
|
}
|
|
12
|
+
interface I18nOptions {
|
|
13
|
+
/**
|
|
14
|
+
* Language to use
|
|
15
|
+
* @default undefined
|
|
16
|
+
*/
|
|
17
|
+
lng?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Language to use if translations in user language are not available.
|
|
20
|
+
* @default 'dev'
|
|
21
|
+
*/
|
|
22
|
+
fallbackLng?: false | FallbackLng;
|
|
23
|
+
/**
|
|
24
|
+
* Default namespace used if not passed to translation function
|
|
25
|
+
* @default 'translation'
|
|
26
|
+
*/
|
|
27
|
+
defaultNS?: string;
|
|
28
|
+
/**
|
|
29
|
+
* Resources to initialize with
|
|
30
|
+
* @default undefined
|
|
31
|
+
*/
|
|
32
|
+
resources?: LanguageResource;
|
|
33
|
+
/**
|
|
34
|
+
* Resource directories to initialize with (if not using loading or not appending using addResourceBundle)
|
|
35
|
+
* @default undefined
|
|
36
|
+
*/
|
|
37
|
+
resourceDirs?: string[];
|
|
38
|
+
}
|
|
15
39
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
40
|
+
/**
|
|
41
|
+
* @class OpraAdapter
|
|
42
|
+
*/
|
|
43
|
+
export declare abstract class OpraAdapter {
|
|
44
|
+
readonly api: ApiDocument;
|
|
45
|
+
protected _internalDoc: ApiDocument;
|
|
20
46
|
i18n: I18n;
|
|
21
47
|
logger?: ILogger;
|
|
22
|
-
constructor(
|
|
48
|
+
protected constructor(api: ApiDocument);
|
|
49
|
+
/**
|
|
50
|
+
* Initializes the adapter
|
|
51
|
+
* @param options
|
|
52
|
+
* @protected
|
|
53
|
+
*/
|
|
54
|
+
protected init(options?: OpraAdapter.Options): Promise<void>;
|
|
55
|
+
/**
|
|
56
|
+
* Calls shutDown hook for all resources
|
|
57
|
+
*/
|
|
23
58
|
close(): Promise<void>;
|
|
24
|
-
protected
|
|
25
|
-
protected
|
|
26
|
-
protected
|
|
27
|
-
protected handler(executionContext: TExecutionContext): Promise<void>;
|
|
28
|
-
protected _requestContextToTask(executionContext: TExecutionContext, requestContext: RequestContext): Task;
|
|
29
|
-
protected _init(options?: OpraAdapter.Options): Promise<void>;
|
|
30
|
-
protected _executeResourceQuery(document: OpraDocument, resource: ResourceInfo, context: SingleRequestContext): Promise<void>;
|
|
31
|
-
protected _executeCollectionResource(document: OpraDocument, resource: CollectionResourceInfo, context: SingleRequestContext): Promise<any>;
|
|
32
|
-
protected _executeSingletonResource(document: OpraDocument, resource: SingletonResourceInfo, context: SingleRequestContext): Promise<any>;
|
|
33
|
-
protected _pathWalkThrough(query: CollectionGetQuery | SingletonGetQuery | FieldGetQuery, dataType: ComplexType, value: any, parentPath: string): Promise<{
|
|
34
|
-
value?: any;
|
|
35
|
-
dataType?: DataType;
|
|
36
|
-
path: string;
|
|
37
|
-
}>;
|
|
59
|
+
protected executeRequest(context: RequestContext): Promise<void>;
|
|
60
|
+
protected afterExecuteRequest(context: RequestContext): Promise<void>;
|
|
61
|
+
protected _createI18n(options?: OpraAdapter.I18nOptions): Promise<I18n>;
|
|
38
62
|
}
|
package/esm/adapter/adapter.js
CHANGED
|
@@ -1,318 +1,111 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import { SingleRequestContext } from './request-contexts/single-request-context.js';
|
|
8
|
-
const noOp = () => void 0;
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { Collection, DocumentFactory, ForbiddenError, getStackFileName, I18n, OpraSchema, ResourceNotFoundError, Singleton, translate, } from '@opra/common';
|
|
3
|
+
import { MetadataResource } from './internal/metadata.resource.js';
|
|
4
|
+
/**
|
|
5
|
+
* @class OpraAdapter
|
|
6
|
+
*/
|
|
9
7
|
export class OpraAdapter {
|
|
10
|
-
constructor(
|
|
11
|
-
this.
|
|
12
|
-
this._internalResources = new ResponsiveMap();
|
|
8
|
+
constructor(api) {
|
|
9
|
+
this.api = api;
|
|
13
10
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
promises.push((async () => shutDown.call(r.instance))());
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
await Promise.allSettled(promises);
|
|
24
|
-
}
|
|
25
|
-
async handler(executionContext) {
|
|
26
|
-
let requestContext;
|
|
27
|
-
let failed = false;
|
|
28
|
-
try {
|
|
29
|
-
if (this.userContextResolver)
|
|
30
|
-
executionContext.userContext = this.userContextResolver(executionContext);
|
|
31
|
-
requestContext = await this.parse(executionContext);
|
|
32
|
-
const task = this._requestContextToTask(executionContext, requestContext);
|
|
33
|
-
await task.toPromise().catch(noOp);
|
|
34
|
-
await this.sendResponse(executionContext, requestContext);
|
|
35
|
-
}
|
|
36
|
-
catch (e) {
|
|
37
|
-
failed = true;
|
|
38
|
-
const error = wrapException(e);
|
|
39
|
-
await this.sendError(executionContext, error);
|
|
40
|
-
if (this.logger)
|
|
41
|
-
try {
|
|
42
|
-
this.logger.error(e);
|
|
43
|
-
}
|
|
44
|
-
catch {
|
|
45
|
-
// noop
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
finally {
|
|
49
|
-
if (executionContext instanceof AsyncEventEmitter) {
|
|
50
|
-
await executionContext
|
|
51
|
-
.emitAsyncSerial('finish', {
|
|
52
|
-
context: executionContext,
|
|
53
|
-
failed
|
|
54
|
-
}).catch();
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
_requestContextToTask(executionContext, requestContext) {
|
|
59
|
-
if (requestContext instanceof BatchRequestContext) {
|
|
60
|
-
const children = requestContext.queries.map(q => this._requestContextToTask(executionContext, q));
|
|
61
|
-
return new Task(children, { bail: true });
|
|
62
|
-
}
|
|
63
|
-
else if (requestContext instanceof SingleRequestContext) {
|
|
64
|
-
const { query } = requestContext;
|
|
65
|
-
const task = new Task(async () => {
|
|
66
|
-
if (query.resource) {
|
|
67
|
-
const { resource } = query;
|
|
68
|
-
// call pre_xxx method
|
|
69
|
-
const fn = resource.metadata['pre_' + query.method];
|
|
70
|
-
if (fn && typeof fn === 'function') {
|
|
71
|
-
await fn(requestContext);
|
|
72
|
-
}
|
|
73
|
-
await this._executeResourceQuery(this.document, resource, requestContext);
|
|
74
|
-
// todo execute sub property queries
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
throw new InternalServerError('Not implemented yet');
|
|
78
|
-
}, {
|
|
79
|
-
id: requestContext.contentId,
|
|
80
|
-
exclusive: query.operation !== 'read'
|
|
81
|
-
});
|
|
82
|
-
task.on('finish', () => {
|
|
83
|
-
if (task.error) {
|
|
84
|
-
if (task.failedDependencies)
|
|
85
|
-
requestContext.errors.push(new FailedDependencyError());
|
|
86
|
-
else
|
|
87
|
-
requestContext.errors.push(wrapException(task.error));
|
|
88
|
-
if (this.logger)
|
|
89
|
-
try {
|
|
90
|
-
this.logger.error(task.error);
|
|
91
|
-
}
|
|
92
|
-
catch {
|
|
93
|
-
// noop
|
|
94
|
-
}
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
});
|
|
98
|
-
return task;
|
|
99
|
-
}
|
|
100
|
-
/* istanbul ignore next */
|
|
101
|
-
throw new TypeError('Invalid request context instance');
|
|
102
|
-
}
|
|
103
|
-
async _init(options) {
|
|
11
|
+
/**
|
|
12
|
+
* Initializes the adapter
|
|
13
|
+
* @param options
|
|
14
|
+
* @protected
|
|
15
|
+
*/
|
|
16
|
+
async init(options) {
|
|
104
17
|
this.logger = options?.logger;
|
|
105
18
|
if (options?.i18n instanceof I18n)
|
|
106
19
|
this.i18n = options.i18n;
|
|
107
20
|
else if (typeof options?.i18n === 'function')
|
|
108
21
|
this.i18n = await options.i18n();
|
|
109
22
|
else
|
|
110
|
-
this.i18n = await
|
|
23
|
+
this.i18n = await this._createI18n(options?.i18n);
|
|
111
24
|
this.i18n = this.i18n || I18n.defaultInstance;
|
|
112
25
|
if (!this.i18n.isInitialized)
|
|
113
26
|
await this.i18n.init();
|
|
114
|
-
this.
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
27
|
+
this._internalDoc = await DocumentFactory.createDocument({
|
|
28
|
+
version: OpraSchema.SpecVersion,
|
|
29
|
+
info: {
|
|
30
|
+
version: OpraSchema.SpecVersion,
|
|
31
|
+
title: 'Internal resources',
|
|
32
|
+
},
|
|
33
|
+
references: { 'api': this.api },
|
|
34
|
+
resources: [new MetadataResource(this.api)]
|
|
123
35
|
});
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
if (
|
|
128
|
-
|
|
129
|
-
if (init)
|
|
130
|
-
await init.call(r.instance, r);
|
|
131
|
-
}
|
|
36
|
+
const promises = [];
|
|
37
|
+
for (const r of this.api.resources.values()) {
|
|
38
|
+
const onInit = r.onInit;
|
|
39
|
+
if (onInit)
|
|
40
|
+
promises.push((async () => onInit.call(r.controller, r))());
|
|
132
41
|
}
|
|
42
|
+
await Promise.all(promises);
|
|
133
43
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const ctx = {
|
|
144
|
-
query: new CollectionCountQuery(query.resource, { filter: query.filter }),
|
|
145
|
-
resultPath: ''
|
|
146
|
-
};
|
|
147
|
-
Object.setPrototypeOf(ctx, context);
|
|
148
|
-
promises.push(this._executeCollectionResource(document, resource, ctx)
|
|
149
|
-
.then(r => {
|
|
150
|
-
context.responseHeaders[HttpHeaderCodes.X_Opra_Count] = r;
|
|
151
|
-
}));
|
|
152
|
-
}
|
|
153
|
-
await Promise.all(promises);
|
|
154
|
-
context.response = search;
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
context.response = await this._executeCollectionResource(document, resource, context);
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
else if (resource instanceof SingletonResourceInfo) {
|
|
161
|
-
context.response = await this._executeSingletonResource(document, resource, context);
|
|
162
|
-
return;
|
|
44
|
+
/**
|
|
45
|
+
* Calls shutDown hook for all resources
|
|
46
|
+
*/
|
|
47
|
+
async close() {
|
|
48
|
+
const promises = [];
|
|
49
|
+
for (const r of this.api.resources.values()) {
|
|
50
|
+
const onShutdown = r.onShutdown;
|
|
51
|
+
if (onShutdown)
|
|
52
|
+
promises.push((async () => onShutdown.call(r.controller, r))());
|
|
163
53
|
}
|
|
164
|
-
|
|
54
|
+
await Promise.allSettled(promises);
|
|
165
55
|
}
|
|
166
|
-
async
|
|
167
|
-
const
|
|
168
|
-
const
|
|
169
|
-
if (
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
if (result)
|
|
182
|
-
context.status = 201;
|
|
183
|
-
context.responseHeaders[HttpHeaderCodes.X_Opra_DataType] = resource.dataType.name;
|
|
184
|
-
return result;
|
|
185
|
-
}
|
|
186
|
-
case 'count': {
|
|
187
|
-
const query = context.query;
|
|
188
|
-
result = await handler(context, query);
|
|
189
|
-
return result;
|
|
190
|
-
}
|
|
191
|
-
case 'get': {
|
|
192
|
-
const query = context.query;
|
|
193
|
-
result = await handler(context, query.keyValue, query);
|
|
194
|
-
result = Array.isArray(result) ? result[0] : result;
|
|
195
|
-
if (!result)
|
|
196
|
-
throw new ResourceNotFoundError(resource.name, query.keyValue);
|
|
197
|
-
const v = await this._pathWalkThrough(query, query.dataType, result, resource.name);
|
|
198
|
-
if (v.value === undefined)
|
|
199
|
-
throw new ResourceNotFoundError(v.path);
|
|
200
|
-
if (v.dataType)
|
|
201
|
-
context.responseHeaders[HttpHeaderCodes.X_Opra_DataType] = v.dataType.name;
|
|
202
|
-
return v.value;
|
|
203
|
-
}
|
|
204
|
-
case 'search': {
|
|
205
|
-
const query = context.query;
|
|
206
|
-
result = await handler(context, query);
|
|
207
|
-
const items = Array.isArray(result) ? result : (context.response ? [result] : []);
|
|
208
|
-
context.responseHeaders[HttpHeaderCodes.X_Opra_DataType] = resource.dataType.name;
|
|
209
|
-
return items;
|
|
210
|
-
}
|
|
211
|
-
case 'update': {
|
|
212
|
-
const query = context.query;
|
|
213
|
-
result = await handler(context, query.keyValue, query.data, query);
|
|
214
|
-
result = Array.isArray(result) ? result[0] : result;
|
|
215
|
-
if (!result)
|
|
216
|
-
throw new ResourceNotFoundError(resource.name, query.keyValue);
|
|
217
|
-
context.responseHeaders[HttpHeaderCodes.X_Opra_DataType] = resource.dataType.name;
|
|
218
|
-
return result;
|
|
219
|
-
}
|
|
220
|
-
case 'delete':
|
|
221
|
-
case 'deleteMany':
|
|
222
|
-
case 'updateMany': {
|
|
223
|
-
switch (method) {
|
|
224
|
-
case 'delete': {
|
|
225
|
-
const query = context.query;
|
|
226
|
-
result = await handler(context, query.keyValue, query);
|
|
227
|
-
break;
|
|
228
|
-
}
|
|
229
|
-
case 'deleteMany': {
|
|
230
|
-
const query = context.query;
|
|
231
|
-
result = await handler(context, query);
|
|
232
|
-
break;
|
|
233
|
-
}
|
|
234
|
-
case 'updateMany': {
|
|
235
|
-
const query = context.query;
|
|
236
|
-
result = await handler(context, query.data, query);
|
|
237
|
-
break;
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
let affected;
|
|
241
|
-
if (typeof result === 'number')
|
|
242
|
-
affected = result;
|
|
243
|
-
if (typeof result === 'boolean')
|
|
244
|
-
affected = result ? 1 : 0;
|
|
245
|
-
if (typeof result === 'object')
|
|
246
|
-
affected = result.affectedRows || result.affected;
|
|
247
|
-
return {
|
|
248
|
-
operation: context.query.method,
|
|
249
|
-
affected
|
|
250
|
-
};
|
|
251
|
-
}
|
|
56
|
+
async executeRequest(context) {
|
|
57
|
+
const { request, response } = context;
|
|
58
|
+
const { resource, operation } = request;
|
|
59
|
+
if (resource instanceof Collection || resource instanceof Singleton) {
|
|
60
|
+
const endpoint = resource.operations[operation];
|
|
61
|
+
if (!endpoint?.handler)
|
|
62
|
+
throw new ForbiddenError({
|
|
63
|
+
message: translate('RESOLVER_FORBIDDEN', { operation }, `The resource endpoint does not accept '{{operation}}' operations`),
|
|
64
|
+
severity: 'error',
|
|
65
|
+
code: 'RESOLVER_FORBIDDEN'
|
|
66
|
+
});
|
|
67
|
+
const value = await endpoint.handler(context);
|
|
68
|
+
if (value != null)
|
|
69
|
+
response.value = value;
|
|
70
|
+
await this.afterExecuteRequest(context);
|
|
252
71
|
}
|
|
253
72
|
}
|
|
254
|
-
async
|
|
255
|
-
const
|
|
256
|
-
const
|
|
257
|
-
if (
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
if (!result)
|
|
270
|
-
throw new ResourceNotFoundError(resource.name);
|
|
271
|
-
const v = await this._pathWalkThrough(query, query.dataType, result, resource.name);
|
|
272
|
-
if (v.value === undefined)
|
|
273
|
-
throw new ResourceNotFoundError(v.path);
|
|
274
|
-
if (v.dataType)
|
|
275
|
-
context.responseHeaders[HttpHeaderCodes.X_Opra_DataType] = v.dataType.name;
|
|
276
|
-
return v.value;
|
|
277
|
-
}
|
|
73
|
+
async afterExecuteRequest(context) {
|
|
74
|
+
const { request, response } = context;
|
|
75
|
+
const { resource, crud, many } = request;
|
|
76
|
+
if (crud === 'delete' || (crud === 'update' && many)) {
|
|
77
|
+
let affected = 0;
|
|
78
|
+
if (typeof response.value === 'number')
|
|
79
|
+
affected = response.value;
|
|
80
|
+
if (typeof response.value === 'boolean')
|
|
81
|
+
affected = response.value ? 1 : 0;
|
|
82
|
+
if (typeof response.value === 'object')
|
|
83
|
+
affected = response.value.affectedRows || response.value.affected;
|
|
84
|
+
response.value = {
|
|
85
|
+
operation: request.operation,
|
|
86
|
+
affected
|
|
87
|
+
};
|
|
278
88
|
}
|
|
279
|
-
if (
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
const pathArray = context.resultPath.split('.');
|
|
285
|
-
for (const field of pathArray) {
|
|
286
|
-
const prop = dataType instanceof ComplexType ? dataType.fields.get(field) : undefined;
|
|
287
|
-
dataType = prop && prop.type ? this.document.types.get(prop.type) : undefined;
|
|
288
|
-
result = result && typeof result === 'object' && result[field];
|
|
289
|
-
}
|
|
89
|
+
else if (response.value != null) {
|
|
90
|
+
if (!request.many)
|
|
91
|
+
response.value = Array.isArray(response.value) ? response.value[0] : response.value;
|
|
92
|
+
else
|
|
93
|
+
response.value = Array.isArray(response.value) ? response.value : [response.value];
|
|
290
94
|
}
|
|
291
|
-
if (
|
|
292
|
-
|
|
293
|
-
context.responseHeaders[HttpHeaderCodes.X_Opra_DataType] = resource.dataType.name;
|
|
294
|
-
return result;
|
|
95
|
+
if ((request.operation === 'get' || request.operation === 'update') && response.value == null)
|
|
96
|
+
throw new ResourceNotFoundError(resource.name, request.args.key);
|
|
295
97
|
}
|
|
296
|
-
async
|
|
297
|
-
const
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
if (child.child && child.dataType instanceof ComplexType) {
|
|
309
|
-
if (Array.isArray(v))
|
|
310
|
-
v = v[0];
|
|
311
|
-
return this._pathWalkThrough(child, child.dataType, v, path);
|
|
312
|
-
}
|
|
313
|
-
return { value: v, dataType: child.dataType, path };
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
return { path };
|
|
98
|
+
async _createI18n(options) {
|
|
99
|
+
const opts = {
|
|
100
|
+
...options,
|
|
101
|
+
};
|
|
102
|
+
delete opts.resourceDirs;
|
|
103
|
+
const instance = I18n.createInstance(opts);
|
|
104
|
+
await instance.init();
|
|
105
|
+
await instance.loadResourceDir(path.resolve(getStackFileName(), '../../../i18n'));
|
|
106
|
+
if (options?.resourceDirs)
|
|
107
|
+
for (const dir of options.resourceDirs)
|
|
108
|
+
await instance.loadResourceDir(dir);
|
|
109
|
+
return instance;
|
|
317
110
|
}
|
|
318
111
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Application } from 'express';
|
|
2
|
+
import { ApiDocument } from '@opra/common';
|
|
3
|
+
import { OpraHttpAdapter } from './http-adapter.js';
|
|
4
|
+
export declare namespace OpraExpressAdapter {
|
|
5
|
+
interface Options extends OpraHttpAdapter.Options {
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
export declare class OpraExpressAdapter extends OpraHttpAdapter {
|
|
9
|
+
protected platform: string;
|
|
10
|
+
static create(app: Application, document: ApiDocument, options?: OpraExpressAdapter.Options): Promise<OpraExpressAdapter>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import bodyParser from 'body-parser';
|
|
2
|
+
import { normalizePath } from '@opra/common';
|
|
3
|
+
import { OpraHttpAdapter } from './http-adapter.js';
|
|
4
|
+
const noOp = () => void 0;
|
|
5
|
+
export class OpraExpressAdapter extends OpraHttpAdapter {
|
|
6
|
+
constructor() {
|
|
7
|
+
super(...arguments);
|
|
8
|
+
this.platform = 'express';
|
|
9
|
+
}
|
|
10
|
+
static async create(app, document, options) {
|
|
11
|
+
const adapter = new OpraExpressAdapter(document);
|
|
12
|
+
await adapter.init(options);
|
|
13
|
+
const prefix = '/' + normalizePath(options?.prefix, true);
|
|
14
|
+
app.use(prefix, bodyParser.json());
|
|
15
|
+
app.use(prefix, (req, res, next) => {
|
|
16
|
+
req.end = noOp;
|
|
17
|
+
req.send = noOp;
|
|
18
|
+
adapter.handler(req, res).catch(e => next(e));
|
|
19
|
+
});
|
|
20
|
+
return adapter;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { HttpRequestMessage, HttpResponseMessage } from '@opra/common';
|
|
2
|
+
import { OpraAdapter } from '../adapter.js';
|
|
3
|
+
import { ILogger } from '../interfaces/logger.interface.js';
|
|
4
|
+
import { Request } from '../interfaces/request.interface.js';
|
|
5
|
+
import { RequestContext } from '../interfaces/request-context.interface.js';
|
|
6
|
+
/**
|
|
7
|
+
* @namespace OpraHttpAdapter
|
|
8
|
+
*/
|
|
9
|
+
export declare namespace OpraHttpAdapter {
|
|
10
|
+
type Options = OpraAdapter.Options & {
|
|
11
|
+
prefix?: string;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
*
|
|
16
|
+
* @class OpraHttpAdapter
|
|
17
|
+
*/
|
|
18
|
+
export declare abstract class OpraHttpAdapter extends OpraAdapter {
|
|
19
|
+
protected abstract platform: string;
|
|
20
|
+
/**
|
|
21
|
+
* Main http request handler
|
|
22
|
+
* @param incoming
|
|
23
|
+
* @param outgoing
|
|
24
|
+
* @protected
|
|
25
|
+
*/
|
|
26
|
+
protected handler(incoming: HttpRequestMessage, outgoing: HttpResponseMessage): Promise<void>;
|
|
27
|
+
protected errorHandler(incoming: HttpRequestMessage, outgoing: HttpResponseMessage, errors: any[]): Promise<void>;
|
|
28
|
+
protected log(logType: keyof ILogger, incoming: HttpRequestMessage, message: string, ...optionalParams: any[]): void;
|
|
29
|
+
protected afterExecuteRequest(context: RequestContext): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
*
|
|
32
|
+
* @param incoming
|
|
33
|
+
* @protected
|
|
34
|
+
*/
|
|
35
|
+
protected parseRequest(incoming: HttpRequestMessage): Promise<Request>;
|
|
36
|
+
protected sendResponse(context: RequestContext): Promise<void>;
|
|
37
|
+
}
|