@opra/core 0.13.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
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
import { Task } from 'power-tasks';
|
|
2
|
+
import { BadRequestError, Collection, HttpHeaderCodes, HttpStatusCodes, InternalServerError, isReadable, IssueSeverity, MethodNotAllowedError, OpraException, OpraSchema, OpraURL, Singleton, wrapException } from '@opra/common';
|
|
3
|
+
import { OpraAdapter } from '../adapter.js';
|
|
4
|
+
import { HttpRequestHost } from './http-request.host.js';
|
|
5
|
+
import { HttpRequestContextHost } from './http-request-context.host.js';
|
|
6
|
+
import { HttpResponseHost } from './http-response.host.js';
|
|
7
|
+
/**
|
|
8
|
+
*
|
|
9
|
+
* @class OpraHttpAdapter
|
|
10
|
+
*/
|
|
11
|
+
export class OpraHttpAdapter extends OpraAdapter {
|
|
12
|
+
/**
|
|
13
|
+
* Main http request handler
|
|
14
|
+
* @param incoming
|
|
15
|
+
* @param outgoing
|
|
16
|
+
* @protected
|
|
17
|
+
*/
|
|
18
|
+
async handler(incoming, outgoing) {
|
|
19
|
+
try {
|
|
20
|
+
// Batch
|
|
21
|
+
if (incoming.is('multipart/mixed')) {
|
|
22
|
+
throw new BadRequestError({ message: 'Not implemented yet' });
|
|
23
|
+
}
|
|
24
|
+
if (!(incoming.method === 'POST' || incoming.method === 'PATCH') || incoming.is('json')) {
|
|
25
|
+
const request = await this.parseRequest(incoming);
|
|
26
|
+
const response = new HttpResponseHost({}, outgoing);
|
|
27
|
+
const context = new HttpRequestContextHost(this.platform, this.api, request, response);
|
|
28
|
+
const task = new Task(async () => {
|
|
29
|
+
try {
|
|
30
|
+
await this.executeRequest(context);
|
|
31
|
+
if (request.operation === 'search' && request.args.count && response.count != null) {
|
|
32
|
+
response.switchToHttp().header(HttpHeaderCodes.X_Opra_Total_Matches, String(response.count));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
return this.errorHandler(incoming, outgoing, [error]);
|
|
37
|
+
}
|
|
38
|
+
await this.sendResponse(context);
|
|
39
|
+
}, {
|
|
40
|
+
id: incoming.get('content-id'),
|
|
41
|
+
exclusive: request.crud !== 'read'
|
|
42
|
+
});
|
|
43
|
+
await task.toPromise().catch(e => {
|
|
44
|
+
this.logger?.error?.(e);
|
|
45
|
+
outgoing.sendStatus(500);
|
|
46
|
+
});
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
throw new BadRequestError({ message: 'Unsupported Content-Type' });
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
await this.errorHandler(incoming, outgoing, [error]);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async errorHandler(incoming, outgoing, errors) {
|
|
56
|
+
errors.forEach(e => {
|
|
57
|
+
this.log((e instanceof OpraException) ? 'error' : 'fatal', incoming, e); // todo. implement a better logger
|
|
58
|
+
});
|
|
59
|
+
errors = errors.map(wrapException);
|
|
60
|
+
let status = outgoing.statusCode || 0;
|
|
61
|
+
// Sort errors from fatal to info
|
|
62
|
+
errors.sort((a, b) => {
|
|
63
|
+
const i = IssueSeverity.Keys.indexOf(a.issue.severity) - IssueSeverity.Keys.indexOf(b.issue.severity);
|
|
64
|
+
if (i === 0)
|
|
65
|
+
return b.status - a.status;
|
|
66
|
+
return i;
|
|
67
|
+
});
|
|
68
|
+
if (!status || status < HttpStatusCodes.BAD_REQUEST) {
|
|
69
|
+
status = errors[0].status;
|
|
70
|
+
if (status < HttpStatusCodes.BAD_REQUEST)
|
|
71
|
+
status = HttpStatusCodes.INTERNAL_SERVER_ERROR;
|
|
72
|
+
}
|
|
73
|
+
const body = this.i18n.deep({
|
|
74
|
+
errors: errors.map(e => e.issue)
|
|
75
|
+
});
|
|
76
|
+
outgoing.set(HttpHeaderCodes.Content_Type, 'application/json; charset=utf-8');
|
|
77
|
+
outgoing.set(HttpHeaderCodes.Cache_Control, 'no-cache');
|
|
78
|
+
outgoing.set(HttpHeaderCodes.Pragma, 'no-cache');
|
|
79
|
+
outgoing.set(HttpHeaderCodes.Expires, '-1');
|
|
80
|
+
outgoing.set(HttpHeaderCodes.X_Opra_Version, OpraSchema.SpecVersion);
|
|
81
|
+
outgoing.status(status);
|
|
82
|
+
outgoing.send(JSON.stringify(body));
|
|
83
|
+
outgoing.end();
|
|
84
|
+
}
|
|
85
|
+
log(logType, incoming, message, ...optionalParams) {
|
|
86
|
+
const logFn = logType === 'fatal'
|
|
87
|
+
? this.logger?.fatal || this.logger?.error
|
|
88
|
+
: this.logger?.[logType];
|
|
89
|
+
if (!logFn)
|
|
90
|
+
return;
|
|
91
|
+
logFn.apply(this.logger, [String(message), ...optionalParams]);
|
|
92
|
+
}
|
|
93
|
+
async afterExecuteRequest(context) {
|
|
94
|
+
await super.afterExecuteRequest(context);
|
|
95
|
+
const { request } = context;
|
|
96
|
+
const response = context.response;
|
|
97
|
+
const { crud } = request;
|
|
98
|
+
const httpResponse = response.switchToHttp();
|
|
99
|
+
if (request.resource instanceof Singleton || request.resource instanceof Collection) {
|
|
100
|
+
httpResponse.set(HttpHeaderCodes.X_Opra_Data_Type, request.resource.type.name);
|
|
101
|
+
httpResponse.set(HttpHeaderCodes.X_Opra_Operation, request.operation);
|
|
102
|
+
}
|
|
103
|
+
if (crud === 'create') {
|
|
104
|
+
if (!response.value)
|
|
105
|
+
throw new InternalServerError();
|
|
106
|
+
// todo validate
|
|
107
|
+
httpResponse.status(201);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
*
|
|
112
|
+
* @param incoming
|
|
113
|
+
* @protected
|
|
114
|
+
*/
|
|
115
|
+
async parseRequest(incoming) {
|
|
116
|
+
try {
|
|
117
|
+
const url = new OpraURL();
|
|
118
|
+
url.searchParams.define({
|
|
119
|
+
'$pick': { codec: 'string', array: true },
|
|
120
|
+
'$omit': { codec: 'string', array: true },
|
|
121
|
+
'$include': { codec: 'string', array: true },
|
|
122
|
+
'$sort': { codec: 'string', array: true },
|
|
123
|
+
'$filter': { codec: 'filter' },
|
|
124
|
+
'$limit': { codec: 'number' },
|
|
125
|
+
'$skip': { codec: 'number' },
|
|
126
|
+
'$distinct': { codec: 'boolean' },
|
|
127
|
+
'$count': { codec: 'boolean' },
|
|
128
|
+
});
|
|
129
|
+
url.parse(incoming.url);
|
|
130
|
+
// const {context, url, method, headers, body, contentId} = args;
|
|
131
|
+
if (!url.path.size)
|
|
132
|
+
throw new BadRequestError();
|
|
133
|
+
const method = incoming.method;
|
|
134
|
+
if (method !== 'GET' && url.path.size > 1)
|
|
135
|
+
throw new BadRequestError();
|
|
136
|
+
// const pathLen = url.path.size;
|
|
137
|
+
// let pathIndex = 0;
|
|
138
|
+
const params = url.searchParams;
|
|
139
|
+
const p = url.path.get(0);
|
|
140
|
+
const resource = this._internalDoc.getResource(p.resource);
|
|
141
|
+
// let container: IResourceContainer | undefined;
|
|
142
|
+
// while (resource && resource instanceof ContainerResourceInfo) {
|
|
143
|
+
// container = resource;
|
|
144
|
+
// p = url.path.get(++pathIndex);
|
|
145
|
+
// resource = container.getResource(p.resource);
|
|
146
|
+
// }
|
|
147
|
+
// const headers = incoming.headers;
|
|
148
|
+
/*
|
|
149
|
+
* Collection
|
|
150
|
+
*/
|
|
151
|
+
if (resource instanceof Collection) {
|
|
152
|
+
switch (method) {
|
|
153
|
+
case 'POST': {
|
|
154
|
+
if (!p.key) {
|
|
155
|
+
return new HttpRequestHost({
|
|
156
|
+
kind: 'CollectionCreateRequest',
|
|
157
|
+
resource,
|
|
158
|
+
operation: 'create',
|
|
159
|
+
crud: 'create',
|
|
160
|
+
many: false,
|
|
161
|
+
args: {
|
|
162
|
+
data: incoming.body,
|
|
163
|
+
pick: resource.normalizeFieldNames(params.get('$pick')),
|
|
164
|
+
omit: resource.normalizeFieldNames(params.get('$omit')),
|
|
165
|
+
include: resource.normalizeFieldNames(params.get('$include'))
|
|
166
|
+
}
|
|
167
|
+
}, incoming);
|
|
168
|
+
}
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
case 'DELETE': {
|
|
172
|
+
if (p.key) {
|
|
173
|
+
return new HttpRequestHost({
|
|
174
|
+
kind: 'CollectionDeleteRequest',
|
|
175
|
+
resource,
|
|
176
|
+
operation: 'delete',
|
|
177
|
+
crud: 'delete',
|
|
178
|
+
many: false,
|
|
179
|
+
args: {
|
|
180
|
+
key: resource.parseKeyValue(p.key)
|
|
181
|
+
}
|
|
182
|
+
}, incoming);
|
|
183
|
+
}
|
|
184
|
+
return new HttpRequestHost({
|
|
185
|
+
kind: 'CollectionDeleteManyRequest',
|
|
186
|
+
resource,
|
|
187
|
+
operation: 'deleteMany',
|
|
188
|
+
crud: 'delete',
|
|
189
|
+
many: true,
|
|
190
|
+
args: {
|
|
191
|
+
filter: resource.normalizeFilterFields(params.get('$filter'))
|
|
192
|
+
}
|
|
193
|
+
}, incoming);
|
|
194
|
+
}
|
|
195
|
+
case 'GET': {
|
|
196
|
+
if (p.key) {
|
|
197
|
+
return new HttpRequestHost({
|
|
198
|
+
kind: 'CollectionGetRequest',
|
|
199
|
+
resource,
|
|
200
|
+
operation: 'get',
|
|
201
|
+
crud: 'read',
|
|
202
|
+
many: false,
|
|
203
|
+
args: {
|
|
204
|
+
key: resource.parseKeyValue(p.key),
|
|
205
|
+
pick: resource.normalizeFieldNames(params.get('$pick')),
|
|
206
|
+
omit: resource.normalizeFieldNames(params.get('$omit')),
|
|
207
|
+
include: resource.normalizeFieldNames(params.get('$include'))
|
|
208
|
+
}
|
|
209
|
+
}, incoming);
|
|
210
|
+
}
|
|
211
|
+
return new HttpRequestHost({
|
|
212
|
+
kind: 'CollectionSearchRequest',
|
|
213
|
+
resource,
|
|
214
|
+
operation: 'search',
|
|
215
|
+
crud: 'read',
|
|
216
|
+
many: true,
|
|
217
|
+
args: {
|
|
218
|
+
sort: resource.normalizeSortFields(params.get('$sort')),
|
|
219
|
+
pick: resource.normalizeFieldNames(params.get('$pick')),
|
|
220
|
+
omit: resource.normalizeFieldNames(params.get('$omit')),
|
|
221
|
+
include: resource.normalizeFieldNames(params.get('$include')),
|
|
222
|
+
filter: resource.normalizeFilterFields(params.get('$filter')),
|
|
223
|
+
limit: params.get('$limit'),
|
|
224
|
+
skip: params.get('$skip'),
|
|
225
|
+
distinct: params.get('$distinct'),
|
|
226
|
+
count: params.get('$count'),
|
|
227
|
+
}
|
|
228
|
+
}, incoming);
|
|
229
|
+
}
|
|
230
|
+
case 'PATCH': {
|
|
231
|
+
if (p.key) {
|
|
232
|
+
return new HttpRequestHost({
|
|
233
|
+
kind: 'CollectionUpdateRequest',
|
|
234
|
+
resource,
|
|
235
|
+
operation: 'update',
|
|
236
|
+
crud: 'update',
|
|
237
|
+
many: false,
|
|
238
|
+
args: {
|
|
239
|
+
key: resource.parseKeyValue(p.key),
|
|
240
|
+
data: incoming.body,
|
|
241
|
+
pick: resource.normalizeFieldNames(params.get('$pick')),
|
|
242
|
+
omit: resource.normalizeFieldNames(params.get('$omit')),
|
|
243
|
+
include: resource.normalizeFieldNames(params.get('$include'))
|
|
244
|
+
}
|
|
245
|
+
}, incoming);
|
|
246
|
+
}
|
|
247
|
+
return new HttpRequestHost({
|
|
248
|
+
kind: 'CollectionUpdateManyRequest',
|
|
249
|
+
resource,
|
|
250
|
+
operation: 'updateMany',
|
|
251
|
+
crud: 'update',
|
|
252
|
+
many: true,
|
|
253
|
+
args: {
|
|
254
|
+
data: incoming.body,
|
|
255
|
+
filter: resource.normalizeFilterFields(params.get('$filter'))
|
|
256
|
+
}
|
|
257
|
+
}, incoming);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
else
|
|
262
|
+
/*
|
|
263
|
+
* Singleton
|
|
264
|
+
*/
|
|
265
|
+
if (resource instanceof Singleton && !p.key) {
|
|
266
|
+
switch (method) {
|
|
267
|
+
case 'POST': {
|
|
268
|
+
return new HttpRequestHost({
|
|
269
|
+
kind: 'SingletonCreateRequest',
|
|
270
|
+
resource,
|
|
271
|
+
operation: 'create',
|
|
272
|
+
crud: 'create',
|
|
273
|
+
many: false,
|
|
274
|
+
args: {
|
|
275
|
+
data: incoming.body,
|
|
276
|
+
pick: resource.normalizeFieldNames(params.get('$pick')),
|
|
277
|
+
omit: resource.normalizeFieldNames(params.get('$omit')),
|
|
278
|
+
include: resource.normalizeFieldNames(params.get('$include'))
|
|
279
|
+
}
|
|
280
|
+
}, incoming);
|
|
281
|
+
}
|
|
282
|
+
case 'DELETE': {
|
|
283
|
+
return new HttpRequestHost({
|
|
284
|
+
kind: 'SingletonDeleteRequest',
|
|
285
|
+
resource,
|
|
286
|
+
operation: 'delete',
|
|
287
|
+
crud: 'delete',
|
|
288
|
+
many: false,
|
|
289
|
+
args: {}
|
|
290
|
+
}, incoming);
|
|
291
|
+
}
|
|
292
|
+
case 'GET': {
|
|
293
|
+
return new HttpRequestHost({
|
|
294
|
+
kind: 'SingletonGetRequest',
|
|
295
|
+
resource,
|
|
296
|
+
operation: 'get',
|
|
297
|
+
crud: 'read',
|
|
298
|
+
many: false,
|
|
299
|
+
args: {
|
|
300
|
+
pick: resource.normalizeFieldNames(params.get('$pick')),
|
|
301
|
+
omit: resource.normalizeFieldNames(params.get('$omit')),
|
|
302
|
+
include: resource.normalizeFieldNames(params.get('$include'))
|
|
303
|
+
}
|
|
304
|
+
}, incoming);
|
|
305
|
+
}
|
|
306
|
+
case 'PATCH': {
|
|
307
|
+
return new HttpRequestHost({
|
|
308
|
+
kind: 'SingletonUpdateRequest',
|
|
309
|
+
resource,
|
|
310
|
+
operation: 'update',
|
|
311
|
+
crud: 'update',
|
|
312
|
+
many: false,
|
|
313
|
+
args: {
|
|
314
|
+
data: incoming.body,
|
|
315
|
+
pick: resource.normalizeFieldNames(params.get('$pick')),
|
|
316
|
+
omit: resource.normalizeFieldNames(params.get('$omit')),
|
|
317
|
+
include: resource.normalizeFieldNames(params.get('$include'))
|
|
318
|
+
}
|
|
319
|
+
}, incoming);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
else
|
|
324
|
+
throw new InternalServerError();
|
|
325
|
+
// if (query instanceof SingletonGetQuery || query instanceof CollectionGetQuery || query instanceof ElementReadQuery) {
|
|
326
|
+
// // Move through properties
|
|
327
|
+
// let parentType: DataType;
|
|
328
|
+
// const curPath: string[] = [];
|
|
329
|
+
// let parent: SingletonGetQuery | CollectionGetQuery | ElementReadQuery = query;
|
|
330
|
+
// while (++pathIndex < pathLen) {
|
|
331
|
+
// p = url.path.get(pathIndex);
|
|
332
|
+
// parentType = parent.type;
|
|
333
|
+
// if (parent.type instanceof UnionType) {
|
|
334
|
+
// if (parent.type.name === 'any')
|
|
335
|
+
// parentType = this.document.getComplexType('object');
|
|
336
|
+
// else
|
|
337
|
+
// throw new TypeError(`"${resource.name}.${curPath.join()}" is a UnionType and needs type casting.`);
|
|
338
|
+
// }
|
|
339
|
+
// if (!(parentType instanceof ComplexType))
|
|
340
|
+
// throw new TypeError(`"${resource.name}.${curPath.join()}" is not a ComplexType and has no fields.`);
|
|
341
|
+
// curPath.push(p.resource);
|
|
342
|
+
// parent.child = new ElementReadQuery(parent, p.resource, {castingType: parentType});
|
|
343
|
+
// parent = parent.child;
|
|
344
|
+
// }
|
|
345
|
+
// }
|
|
346
|
+
throw new MethodNotAllowedError({
|
|
347
|
+
message: `Method "${method}" is not allowed by target endpoint`
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
catch (e) {
|
|
351
|
+
if (e instanceof OpraException)
|
|
352
|
+
throw e;
|
|
353
|
+
throw new BadRequestError(e);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
// async parseMultiPart(
|
|
357
|
+
// context: TExecutionContext,
|
|
358
|
+
// url: OpraURL,
|
|
359
|
+
// headers: IncomingHttpHeaders,
|
|
360
|
+
// input: Readable,
|
|
361
|
+
// boundary: string
|
|
362
|
+
// ): Promise<BatchRequestContext> {
|
|
363
|
+
// return await new Promise((resolve, reject) => {
|
|
364
|
+
// let _resolved = false;
|
|
365
|
+
// const dicer = new Dicer({boundary});
|
|
366
|
+
// const doReject = (e) => {
|
|
367
|
+
// if (_resolved) return;
|
|
368
|
+
// _resolved = true;
|
|
369
|
+
// reject(e);
|
|
370
|
+
// taskQueue.clearQueue();
|
|
371
|
+
// dicer.destroy();
|
|
372
|
+
// }
|
|
373
|
+
// const taskQueue = new TaskQueue({concurrency: 1});
|
|
374
|
+
// taskQueue.on('error', doReject);
|
|
375
|
+
//
|
|
376
|
+
// const queries: SingleRequestContext[] = [];
|
|
377
|
+
// let partCounter = 0;
|
|
378
|
+
// dicer.on('error', doReject);
|
|
379
|
+
// dicer.on('part', part => {
|
|
380
|
+
// const partIndex = partCounter++;
|
|
381
|
+
// let header: any;
|
|
382
|
+
// const chunks: Buffer[] = [];
|
|
383
|
+
// part.on('error', doReject);
|
|
384
|
+
// part.on('header', (_header) => header = normalizeHeaders(_header));
|
|
385
|
+
// part.on('data', (chunk: Buffer) => chunks.push(chunk));
|
|
386
|
+
// part.on('end', () => {
|
|
387
|
+
// if (_resolved || !(header || chunks.length))
|
|
388
|
+
// return;
|
|
389
|
+
// const ct = header['content-type'];
|
|
390
|
+
// if (ct === 'application/http') {
|
|
391
|
+
// taskQueue.enqueue(async () => {
|
|
392
|
+
// const data = Buffer.concat(chunks);
|
|
393
|
+
// if (!(data && data.length))
|
|
394
|
+
// return;
|
|
395
|
+
// const r = HttpRequest.parse(data);
|
|
396
|
+
// await callMiddlewares(r, [jsonBodyParser]);
|
|
397
|
+
// const subUrl = new OpraURL(r.url);
|
|
398
|
+
// const contentId = header && header['content-id'];
|
|
399
|
+
// queries.push(this.parseSingleQuery({
|
|
400
|
+
// context,
|
|
401
|
+
// url: subUrl,
|
|
402
|
+
// method: r.method,
|
|
403
|
+
// headers: r.headers,
|
|
404
|
+
// body: r.body,
|
|
405
|
+
// contentId
|
|
406
|
+
// }));
|
|
407
|
+
// });
|
|
408
|
+
// } else doReject(new BadRequestError({
|
|
409
|
+
// message: 'Unaccepted "content-type" header in multipart data',
|
|
410
|
+
// details: {
|
|
411
|
+
// position: `${boundary}[${partIndex}]`
|
|
412
|
+
// }
|
|
413
|
+
// }))
|
|
414
|
+
// });
|
|
415
|
+
// });
|
|
416
|
+
// dicer.on('finish', () => {
|
|
417
|
+
// taskQueue.enqueue(() => {
|
|
418
|
+
// if (_resolved) return;
|
|
419
|
+
// _resolved = true;
|
|
420
|
+
// const batch = new BatchRequestContext({
|
|
421
|
+
// service: this.document,
|
|
422
|
+
// context,
|
|
423
|
+
// headers,
|
|
424
|
+
// queries,
|
|
425
|
+
// params: url.searchParams,
|
|
426
|
+
// continueOnError: false
|
|
427
|
+
// });
|
|
428
|
+
// resolve(batch);
|
|
429
|
+
// });
|
|
430
|
+
// });
|
|
431
|
+
// input.pipe(dicer);
|
|
432
|
+
// });
|
|
433
|
+
// }
|
|
434
|
+
async sendResponse(context) {
|
|
435
|
+
const { request, response } = context;
|
|
436
|
+
const outgoing = response.switchToHttp();
|
|
437
|
+
const errors = response.errors?.map(e => wrapException(e));
|
|
438
|
+
if (errors && errors.length) {
|
|
439
|
+
await this.errorHandler(request.switchToHttp(), outgoing, errors);
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
outgoing.set(HttpHeaderCodes.Cache_Control, 'no-cache');
|
|
443
|
+
outgoing.set(HttpHeaderCodes.Pragma, 'no-cache');
|
|
444
|
+
outgoing.set(HttpHeaderCodes.Expires, '-1');
|
|
445
|
+
outgoing.set(HttpHeaderCodes.X_Opra_Version, OpraSchema.SpecVersion);
|
|
446
|
+
outgoing.status(outgoing.statusCode || HttpStatusCodes.OK);
|
|
447
|
+
if (response.value) {
|
|
448
|
+
if (typeof response.value === 'object') {
|
|
449
|
+
if (isReadable(response.value) || Buffer.isBuffer(response.value))
|
|
450
|
+
outgoing.send(response.value);
|
|
451
|
+
else {
|
|
452
|
+
const body = this.i18n.deep(response.value);
|
|
453
|
+
outgoing.set(HttpHeaderCodes.Content_Type, 'application/json; charset=utf-8');
|
|
454
|
+
outgoing.send(JSON.stringify(body));
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
else
|
|
458
|
+
outgoing.send(JSON.stringify(response.value));
|
|
459
|
+
}
|
|
460
|
+
outgoing.end();
|
|
461
|
+
}
|
|
462
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ApiDocument } from '@opra/common';
|
|
2
|
+
import { Request } from '../interfaces/request.interface.js';
|
|
3
|
+
import { HttpRequestContext, RpcRequestContext, WsRequestContext } from '../interfaces/request-context.interface.js';
|
|
4
|
+
import { Response } from '../interfaces/response.interface.js';
|
|
5
|
+
import { RequestContextHost } from '../request-context.host.js';
|
|
6
|
+
export declare class HttpRequestContextHost extends RequestContextHost {
|
|
7
|
+
readonly platform: string;
|
|
8
|
+
readonly api: ApiDocument;
|
|
9
|
+
protected _request: Request;
|
|
10
|
+
protected _response: Response;
|
|
11
|
+
user?: any;
|
|
12
|
+
constructor(platform: string, api: ApiDocument, _request: Request, _response: Response);
|
|
13
|
+
switchToHttp(): HttpRequestContext;
|
|
14
|
+
switchToWs(): WsRequestContext;
|
|
15
|
+
switchToRpc(): RpcRequestContext;
|
|
16
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { RequestContextHost } from '../request-context.host.js';
|
|
2
|
+
export class HttpRequestContextHost extends RequestContextHost {
|
|
3
|
+
constructor(platform, api, _request, _response) {
|
|
4
|
+
super('http', platform, api, _request, _response);
|
|
5
|
+
this.platform = platform;
|
|
6
|
+
this.api = api;
|
|
7
|
+
this._request = _request;
|
|
8
|
+
this._response = _response;
|
|
9
|
+
}
|
|
10
|
+
switchToHttp() {
|
|
11
|
+
const obj = {
|
|
12
|
+
request: this._request.switchToHttp(),
|
|
13
|
+
response: this._response.switchToHttp()
|
|
14
|
+
};
|
|
15
|
+
Object.setPrototypeOf(obj, this);
|
|
16
|
+
return obj;
|
|
17
|
+
}
|
|
18
|
+
switchToWs() {
|
|
19
|
+
throw new TypeError('Not executing in an "WebSocket" protocol');
|
|
20
|
+
}
|
|
21
|
+
switchToRpc() {
|
|
22
|
+
throw new TypeError('Not executing in an "RPC" protocol');
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { HttpRequestMessage } from '@opra/common';
|
|
2
|
+
import { RequestHost } from '../request.host.js';
|
|
3
|
+
export declare class HttpRequestHost extends RequestHost {
|
|
4
|
+
protected _incoming: HttpRequestMessage;
|
|
5
|
+
constructor(init: RequestHost.Initiator, _incoming: HttpRequestMessage);
|
|
6
|
+
switchToHttp(): HttpRequestMessage;
|
|
7
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { HttpResponseMessage } from '@opra/common';
|
|
2
|
+
import { ResponseHost } from '../response.host.js';
|
|
3
|
+
export declare class HttpResponseHost extends ResponseHost {
|
|
4
|
+
protected _outgoing: HttpResponseMessage;
|
|
5
|
+
constructor(init: ResponseHost.Initiator, _outgoing: HttpResponseMessage);
|
|
6
|
+
switchToHttp(): HttpResponseMessage;
|
|
7
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export interface ILogger {
|
|
2
2
|
log(message: any, ...optionalParams: any[]): any;
|
|
3
3
|
error(message: any, ...optionalParams: any[]): any;
|
|
4
|
+
fatal?(message: any, ...optionalParams: any[]): any;
|
|
4
5
|
warn(message: any, ...optionalParams: any[]): any;
|
|
5
6
|
debug?(message: any, ...optionalParams: any[]): any;
|
|
6
7
|
verbose?(message: any, ...optionalParams: any[]): any;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { ApiDocument, HttpRequestMessage, HttpResponseMessage } from '@opra/common';
|
|
2
|
+
import { Request } from './request.interface.js';
|
|
3
|
+
import { Response } from './response.interface.js';
|
|
4
|
+
export declare namespace RequestContext {
|
|
5
|
+
type Protocol = 'http' | 'ws' | 'rpc';
|
|
6
|
+
}
|
|
7
|
+
export interface RequestContext {
|
|
8
|
+
readonly protocol: RequestContext.Protocol;
|
|
9
|
+
readonly platform: string;
|
|
10
|
+
readonly api: ApiDocument;
|
|
11
|
+
readonly request: Request;
|
|
12
|
+
readonly response: Response;
|
|
13
|
+
user?: any;
|
|
14
|
+
switchToHttp(): HttpRequestContext;
|
|
15
|
+
switchToWs(): WsRequestContext;
|
|
16
|
+
switchToRpc(): RpcRequestContext;
|
|
17
|
+
}
|
|
18
|
+
export interface HttpRequestContext extends Omit<RequestContext, 'request' | 'response'> {
|
|
19
|
+
readonly request: HttpRequestMessage;
|
|
20
|
+
readonly response: HttpResponseMessage;
|
|
21
|
+
}
|
|
22
|
+
export interface WsRequestContext extends Omit<RequestContext, 'request' | 'response'> {
|
|
23
|
+
}
|
|
24
|
+
export interface RpcRequestContext extends Omit<RequestContext, 'request' | 'response'> {
|
|
25
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { HttpRequestMessage, OpraSchema, Resource } from '@opra/common';
|
|
2
|
+
export interface Request {
|
|
3
|
+
readonly kind: string;
|
|
4
|
+
readonly resource: Resource;
|
|
5
|
+
readonly resourceKind: OpraSchema.Resource.Kind;
|
|
6
|
+
readonly operation: string;
|
|
7
|
+
readonly crud: 'create' | 'read' | 'update' | 'delete';
|
|
8
|
+
readonly many: boolean;
|
|
9
|
+
readonly args: any;
|
|
10
|
+
switchToHttp(): HttpRequestMessage;
|
|
11
|
+
switchToWs(): never;
|
|
12
|
+
switchToRpc(): never;
|
|
13
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { HttpResponseMessage } from '@opra/common';
|
|
2
|
+
export interface Response {
|
|
3
|
+
/**
|
|
4
|
+
* Result value
|
|
5
|
+
*/
|
|
6
|
+
value?: any;
|
|
7
|
+
/**
|
|
8
|
+
* List of errors
|
|
9
|
+
*/
|
|
10
|
+
errors: Error[];
|
|
11
|
+
/**
|
|
12
|
+
*
|
|
13
|
+
*/
|
|
14
|
+
continueOnError?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Total count of matched entities. (Used in "search" operation with "count" option
|
|
17
|
+
*/
|
|
18
|
+
count?: number;
|
|
19
|
+
switchToHttp(): HttpResponseMessage;
|
|
20
|
+
switchToWs(): never;
|
|
21
|
+
switchToRpc(): never;
|
|
22
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { __decorate, __metadata } from "tslib";
|
|
2
|
+
import { ApiDocument, cloneObject, Singleton } from '@opra/common';
|
|
3
|
+
let MetadataResource = class MetadataResource {
|
|
4
|
+
constructor(document) {
|
|
5
|
+
this.document = document;
|
|
6
|
+
this._schema = document.exportSchema();
|
|
7
|
+
}
|
|
8
|
+
get() {
|
|
9
|
+
return cloneObject(this.document.exportSchema(), true);
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
__decorate([
|
|
13
|
+
Singleton.GetOperation(),
|
|
14
|
+
__metadata("design:type", Function),
|
|
15
|
+
__metadata("design:paramtypes", []),
|
|
16
|
+
__metadata("design:returntype", void 0)
|
|
17
|
+
], MetadataResource.prototype, "get", null);
|
|
18
|
+
MetadataResource = __decorate([
|
|
19
|
+
Singleton(Object, {
|
|
20
|
+
name: '$metadata',
|
|
21
|
+
}),
|
|
22
|
+
__metadata("design:paramtypes", [ApiDocument])
|
|
23
|
+
], MetadataResource);
|
|
24
|
+
export { MetadataResource };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { AsyncEventEmitter } from 'strict-typed-events';
|
|
2
|
+
import { ApiDocument } from '@opra/common';
|
|
3
|
+
import { Request } from './interfaces/request.interface.js';
|
|
4
|
+
import { HttpRequestContext, RequestContext, RpcRequestContext, WsRequestContext } from './interfaces/request-context.interface.js';
|
|
5
|
+
import { Response } from './interfaces/response.interface.js';
|
|
6
|
+
export declare abstract class RequestContextHost extends AsyncEventEmitter implements RequestContext {
|
|
7
|
+
readonly protocol: RequestContext.Protocol;
|
|
8
|
+
readonly platform: string;
|
|
9
|
+
readonly api: ApiDocument;
|
|
10
|
+
protected _request: Request;
|
|
11
|
+
protected _response: Response;
|
|
12
|
+
user?: any;
|
|
13
|
+
protected constructor(protocol: RequestContext.Protocol, platform: string, api: ApiDocument, _request: Request, _response: Response);
|
|
14
|
+
get request(): Request;
|
|
15
|
+
get response(): Response;
|
|
16
|
+
switchToHttp(): HttpRequestContext;
|
|
17
|
+
switchToWs(): WsRequestContext;
|
|
18
|
+
switchToRpc(): RpcRequestContext;
|
|
19
|
+
}
|