@opra/core 1.0.0-alpha.3 → 1.0.0-alpha.31
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/augmentation/18n.augmentation.js +1 -1
- package/cjs/constants.js +1 -2
- package/cjs/execution-context.js +1 -1
- package/cjs/http/express-adapter.js +25 -34
- package/cjs/http/http-adapter.js +2 -5
- package/cjs/http/http-context.js +20 -32
- package/cjs/http/{impl/http-handler.js → http-handler.js} +249 -213
- package/cjs/http/impl/http-incoming.host.js +3 -3
- package/cjs/http/impl/http-outgoing.host.js +2 -2
- package/cjs/http/impl/multipart-reader.js +141 -50
- package/cjs/http/impl/node-incoming-message.host.js +5 -3
- package/cjs/http/interfaces/node-incoming-message.interface.js +3 -2
- package/cjs/http/utils/body-reader.js +6 -5
- package/cjs/http/utils/common.js +6 -5
- package/cjs/http/utils/concat-readable.js +1 -2
- package/cjs/http/utils/convert-to-headers.js +2 -3
- package/cjs/http/utils/convert-to-raw-headers.js +1 -2
- package/cjs/http/utils/match-known-fields.js +2 -2
- package/cjs/http/utils/wrap-exception.js +1 -2
- package/cjs/index.js +4 -4
- package/cjs/platform-adapter.js +1 -4
- package/cjs/type-guards.js +4 -5
- package/esm/augmentation/18n.augmentation.js +1 -1
- package/esm/constants.js +0 -1
- package/esm/execution-context.js +1 -1
- package/esm/http/express-adapter.js +25 -34
- package/esm/http/http-adapter.js +2 -5
- package/esm/http/http-context.js +21 -33
- package/esm/http/{impl/http-handler.js → http-handler.js} +243 -207
- package/esm/http/impl/http-incoming.host.js +3 -3
- package/esm/http/impl/http-outgoing.host.js +2 -2
- package/esm/http/impl/multipart-reader.js +142 -51
- package/esm/http/impl/node-incoming-message.host.js +5 -3
- package/esm/http/interfaces/node-incoming-message.interface.js +3 -2
- package/esm/http/utils/body-reader.js +6 -5
- package/esm/http/utils/common.js +2 -1
- package/esm/index.js +4 -4
- package/esm/platform-adapter.js +1 -4
- package/package.json +21 -14
- package/types/augmentation/18n.augmentation.d.ts +1 -1
- package/types/constants.d.ts +0 -1
- package/types/execution-context.d.ts +2 -3
- package/types/http/express-adapter.d.ts +1 -1
- package/types/http/http-adapter.d.ts +35 -8
- package/types/http/http-context.d.ts +4 -4
- package/types/http/{impl/http-handler.d.ts → http-handler.d.ts} +11 -9
- package/types/http/impl/http-incoming.host.d.ts +1 -2
- package/types/http/impl/http-outgoing.host.d.ts +1 -1
- package/types/http/impl/multipart-reader.d.ts +38 -20
- package/types/http/impl/node-incoming-message.host.d.ts +2 -6
- package/types/http/impl/node-outgoing-message.host.d.ts +4 -7
- package/types/http/interfaces/http-incoming.interface.d.ts +1 -2
- package/types/http/interfaces/http-outgoing.interface.d.ts +1 -1
- package/types/http/interfaces/node-incoming-message.interface.d.ts +0 -2
- package/types/http/interfaces/node-outgoing-message.interface.d.ts +0 -2
- package/types/http/utils/body-reader.d.ts +2 -5
- package/types/http/utils/concat-readable.d.ts +0 -1
- package/types/http/utils/convert-to-raw-headers.d.ts +0 -1
- package/types/index.d.ts +4 -4
- package/types/platform-adapter.d.ts +1 -5
- package/cjs/helpers/logger.js +0 -35
- package/esm/helpers/logger.js +0 -31
- package/types/helpers/logger.d.ts +0 -14
|
@@ -1,11 +1,11 @@
|
|
|
1
|
+
import { HttpApi, NotFoundError } from '@opra/common';
|
|
1
2
|
import { Router } from 'express';
|
|
2
3
|
import * as nodePath from 'path';
|
|
3
|
-
import { HttpApi, NotFoundError } from '@opra/common';
|
|
4
|
-
import { kHandler } from '../constants.js';
|
|
5
4
|
import { HttpAdapter } from './http-adapter.js';
|
|
6
5
|
import { HttpContext } from './http-context.js';
|
|
7
6
|
import { HttpIncoming } from './interfaces/http-incoming.interface.js';
|
|
8
7
|
import { HttpOutgoing } from './interfaces/http-outgoing.interface.js';
|
|
8
|
+
import { wrapException } from './utils/wrap-exception';
|
|
9
9
|
export class ExpressAdapter extends HttpAdapter {
|
|
10
10
|
constructor(app, document, options) {
|
|
11
11
|
super(document, options);
|
|
@@ -31,13 +31,15 @@ export class ExpressAdapter extends HttpAdapter {
|
|
|
31
31
|
}
|
|
32
32
|
if (resource.onShutdown) {
|
|
33
33
|
const instance = this._controllerInstances.get(resource) || resource.instance;
|
|
34
|
-
if (instance)
|
|
34
|
+
if (instance) {
|
|
35
35
|
try {
|
|
36
36
|
await resource.onShutdown.call(instance, resource);
|
|
37
37
|
}
|
|
38
38
|
catch (e) {
|
|
39
|
-
this.
|
|
39
|
+
if (this.listenerCount('error'))
|
|
40
|
+
this.emit('error', wrapException(e));
|
|
40
41
|
}
|
|
42
|
+
}
|
|
41
43
|
}
|
|
42
44
|
};
|
|
43
45
|
for (const c of this.api.controllers.values())
|
|
@@ -58,17 +60,12 @@ export class ExpressAdapter extends HttpAdapter {
|
|
|
58
60
|
}
|
|
59
61
|
else
|
|
60
62
|
this.app.use(router);
|
|
61
|
-
const createContext = (_req, _res, args) => {
|
|
63
|
+
const createContext = async (_req, _res, args) => {
|
|
62
64
|
const request = HttpIncoming.from(_req);
|
|
63
65
|
const response = HttpOutgoing.from(_res);
|
|
64
|
-
const
|
|
65
|
-
request: _req,
|
|
66
|
-
response: _res,
|
|
67
|
-
};
|
|
68
|
-
return new HttpContext({
|
|
66
|
+
const ctx = new HttpContext({
|
|
69
67
|
adapter: this,
|
|
70
68
|
platform: this.platform,
|
|
71
|
-
platformArgs,
|
|
72
69
|
request,
|
|
73
70
|
response,
|
|
74
71
|
controller: args?.controller,
|
|
@@ -76,18 +73,14 @@ export class ExpressAdapter extends HttpAdapter {
|
|
|
76
73
|
operation: args?.operation,
|
|
77
74
|
operationHandler: args?.operationHandler,
|
|
78
75
|
});
|
|
76
|
+
await this.emitAsync('createContext', ctx);
|
|
77
|
+
return ctx;
|
|
79
78
|
};
|
|
80
79
|
/** Add an endpoint that returns document schema */
|
|
81
|
-
router.get('
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const context = createContext(_req, _res);
|
|
86
|
-
this[kHandler].sendDocumentSchema(context).catch(next);
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
next();
|
|
80
|
+
router.get('/\\$schema', (_req, _res, next) => {
|
|
81
|
+
createContext(_req, _res)
|
|
82
|
+
.then(ctx => this.handler.sendDocumentSchema(ctx).catch(next))
|
|
83
|
+
.catch(next);
|
|
91
84
|
});
|
|
92
85
|
/** Add operation endpoints */
|
|
93
86
|
if (this.api.controllers.size) {
|
|
@@ -101,19 +94,18 @@ export class ExpressAdapter extends HttpAdapter {
|
|
|
101
94
|
continue;
|
|
102
95
|
/** Define router callback */
|
|
103
96
|
router[operation.method.toLowerCase()](routePath, (_req, _res, _next) => {
|
|
104
|
-
|
|
97
|
+
createContext(_req, _res, {
|
|
105
98
|
controller,
|
|
106
99
|
controllerInstance,
|
|
107
100
|
operation,
|
|
108
101
|
operationHandler,
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
.handleRequest(context)
|
|
102
|
+
})
|
|
103
|
+
.then(ctx => this.handler.handleRequest(ctx))
|
|
112
104
|
.then(() => {
|
|
113
105
|
if (!_res.headersSent)
|
|
114
106
|
_next();
|
|
115
107
|
})
|
|
116
|
-
.catch((e) => this.
|
|
108
|
+
.catch((e) => this.emit('error', e));
|
|
117
109
|
});
|
|
118
110
|
}
|
|
119
111
|
if (controller.controllers.size) {
|
|
@@ -126,18 +118,17 @@ export class ExpressAdapter extends HttpAdapter {
|
|
|
126
118
|
}
|
|
127
119
|
/** Add an endpoint that returns 404 error at last */
|
|
128
120
|
router.use('*', (_req, _res, next) => {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
new NotFoundError({
|
|
134
|
-
message: `No endpoint found for [${_req.method}]${_req.baseUrl}`,
|
|
121
|
+
createContext(_req, _res)
|
|
122
|
+
.then(ctx => {
|
|
123
|
+
ctx.errors.push(new NotFoundError({
|
|
124
|
+
message: `No endpoint found at [${_req.method}]${_req.baseUrl}`,
|
|
135
125
|
details: {
|
|
136
126
|
path: _req.baseUrl,
|
|
137
127
|
method: _req.method,
|
|
138
128
|
},
|
|
139
|
-
})
|
|
140
|
-
|
|
129
|
+
}));
|
|
130
|
+
this.handler.sendResponse(ctx).catch(next);
|
|
131
|
+
})
|
|
141
132
|
.catch(next);
|
|
142
133
|
});
|
|
143
134
|
}
|
package/esm/http/http-adapter.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { HttpApi } from '@opra/common';
|
|
2
|
-
import { kHandler } from '../constants.js';
|
|
3
2
|
import { PlatformAdapter } from '../platform-adapter.js';
|
|
4
|
-
import { HttpHandler } from './
|
|
3
|
+
import { HttpHandler } from './http-handler.js';
|
|
5
4
|
/**
|
|
6
5
|
*
|
|
7
6
|
* @class HttpAdapter
|
|
@@ -12,10 +11,8 @@ export class HttpAdapter extends PlatformAdapter {
|
|
|
12
11
|
this.protocol = 'http';
|
|
13
12
|
if (!(document.api instanceof HttpApi))
|
|
14
13
|
throw new TypeError(`The document does not expose an HTTP Api`);
|
|
15
|
-
this
|
|
14
|
+
this.handler = new HttpHandler(this);
|
|
16
15
|
this.interceptors = [...(options?.interceptors || [])];
|
|
17
|
-
if (options?.onRequest)
|
|
18
|
-
this.on('request', options.onRequest);
|
|
19
16
|
}
|
|
20
17
|
get api() {
|
|
21
18
|
return this.document.api;
|
package/esm/http/http-context.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { vg } from 'valgen';
|
|
2
1
|
import typeIs from '@browsery/type-is';
|
|
3
|
-
import {
|
|
2
|
+
import { InternalServerError, NotAcceptableError, } from '@opra/common';
|
|
3
|
+
import { vg } from 'valgen';
|
|
4
4
|
import { kAssetCache } from '../constants.js';
|
|
5
5
|
import { ExecutionContext } from '../execution-context.js';
|
|
6
6
|
import { MultipartReader } from './impl/multipart-reader.js';
|
|
@@ -25,6 +25,10 @@ export class HttpContext extends ExecutionContext {
|
|
|
25
25
|
this.pathParams = init.pathParams || {};
|
|
26
26
|
this.queryParams = init.queryParams || {};
|
|
27
27
|
this._body = init.body;
|
|
28
|
+
this.on('finish', () => {
|
|
29
|
+
if (this._multipartReader)
|
|
30
|
+
this._multipartReader.purge().catch(() => undefined);
|
|
31
|
+
});
|
|
28
32
|
}
|
|
29
33
|
get isMultipart() {
|
|
30
34
|
return !!this.request.is('multipart');
|
|
@@ -34,21 +38,21 @@ export class HttpContext extends ExecutionContext {
|
|
|
34
38
|
throw new InternalServerError('Request content is not a multipart content');
|
|
35
39
|
if (this._multipartReader)
|
|
36
40
|
return this._multipartReader;
|
|
37
|
-
const {
|
|
41
|
+
const { mediaType } = this;
|
|
38
42
|
if (mediaType?.contentType) {
|
|
39
43
|
const arr = Array.isArray(mediaType.contentType) ? mediaType.contentType : [mediaType.contentType];
|
|
40
44
|
const contentType = arr.find(ct => typeIs.is(ct, ['multipart']));
|
|
41
45
|
if (!contentType)
|
|
42
46
|
throw new NotAcceptableError('This endpoint does not accept multipart requests');
|
|
43
47
|
}
|
|
44
|
-
const reader = new MultipartReader(
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
});
|
|
48
|
+
const reader = new MultipartReader(this, {
|
|
49
|
+
limits: {
|
|
50
|
+
fields: mediaType?.maxFields,
|
|
51
|
+
fieldSize: mediaType?.maxFieldsSize,
|
|
52
|
+
files: mediaType?.maxFiles,
|
|
53
|
+
fileSize: mediaType?.maxFileSize,
|
|
54
|
+
},
|
|
55
|
+
}, mediaType);
|
|
52
56
|
this._multipartReader = reader;
|
|
53
57
|
return reader;
|
|
54
58
|
}
|
|
@@ -61,32 +65,15 @@ export class HttpContext extends ExecutionContext {
|
|
|
61
65
|
/** Retrieve all fields */
|
|
62
66
|
const parts = await reader.getAll();
|
|
63
67
|
/** Filter fields according to configuration */
|
|
64
|
-
this._body = [];
|
|
65
|
-
const multipartFields = mediaType?.multipartFields;
|
|
66
|
-
if (mediaType && multipartFields?.length) {
|
|
67
|
-
const fieldsFound = new Map();
|
|
68
|
-
for (const item of parts) {
|
|
69
|
-
const field = mediaType.findMultipartField(item.fieldName, item.type);
|
|
70
|
-
if (field) {
|
|
71
|
-
fieldsFound.set(field, true);
|
|
72
|
-
this._body.push(item);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
/** Check required fields */
|
|
76
|
-
for (const field of multipartFields) {
|
|
77
|
-
if (field.required && !fieldsFound.get(field))
|
|
78
|
-
throw new BadRequestError({
|
|
79
|
-
message: `Multipart field (${field.fieldName}) is required`,
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
}
|
|
68
|
+
this._body = [...parts];
|
|
83
69
|
return this._body;
|
|
84
70
|
}
|
|
85
|
-
this._body = await this.request.readBody({ limit: operation
|
|
71
|
+
this._body = await this.request.readBody({ limit: operation?.requestBody?.maxContentSize });
|
|
86
72
|
if (this._body != null) {
|
|
87
73
|
// Convert Buffer to string if media is text
|
|
88
|
-
if (Buffer.isBuffer(this._body) && request.is(['json', 'xml', 'txt', 'text']))
|
|
74
|
+
if (Buffer.isBuffer(this._body) && request.is(['json', 'xml', 'txt', 'text'])) {
|
|
89
75
|
this._body = this._body.toString('utf-8');
|
|
76
|
+
}
|
|
90
77
|
// Transform text to Object if media is JSON
|
|
91
78
|
if (typeof this._body === 'string' && request.is(['json']))
|
|
92
79
|
this._body = JSON.parse(this._body);
|
|
@@ -98,8 +85,9 @@ export class HttpContext extends ExecutionContext {
|
|
|
98
85
|
if (!decode) {
|
|
99
86
|
decode =
|
|
100
87
|
mediaType.type?.generateCodec('decode', {
|
|
101
|
-
partial: operation
|
|
88
|
+
partial: operation?.requestBody?.partial,
|
|
102
89
|
projection: '*',
|
|
90
|
+
ignoreReadonlyFields: true,
|
|
103
91
|
}) || vg.isAny();
|
|
104
92
|
this.adapter[kAssetCache].set(mediaType, 'decode', decode);
|
|
105
93
|
}
|