@opra/core 0.33.13 → 1.0.0-alpha.10
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 +18 -5
- package/cjs/augmentation/http-controller.augmentation.js +25 -0
- package/cjs/constants.js +5 -0
- package/cjs/execution-context.js +24 -12
- package/cjs/{services → helpers}/logger.js +1 -2
- package/cjs/{services/api-service.js → helpers/service-base.js} +8 -8
- package/cjs/http/express-adapter.js +153 -0
- package/cjs/http/http-adapter.js +27 -0
- package/cjs/http/http-context.js +119 -0
- package/cjs/http/impl/asset-cache.js +21 -0
- package/cjs/http/impl/http-handler.js +584 -0
- package/cjs/http/{http-server-request.js → impl/http-incoming.host.js} +24 -49
- package/cjs/http/{http-server-response.js → impl/http-outgoing.host.js} +9 -28
- package/cjs/http/{helpers/multipart-helper.js → impl/multipart-reader.js} +23 -27
- package/cjs/http/impl/{http-incoming-message.host.js → node-incoming-message.host.js} +18 -57
- package/cjs/http/impl/{http-outgoing-message.host.js → node-outgoing-message.host.js} +11 -14
- package/cjs/http/interfaces/http-incoming.interface.js +25 -0
- package/cjs/http/interfaces/http-outgoing.interface.js +22 -0
- package/cjs/http/interfaces/node-incoming-message.interface.js +64 -0
- package/cjs/http/interfaces/node-outgoing-message.interface.js +15 -0
- package/cjs/http/utils/body-reader.js +217 -0
- package/cjs/http/{helpers → utils}/common.js +2 -1
- package/cjs/http/{helpers → utils}/convert-to-raw-headers.js +1 -2
- package/cjs/http/{helpers → utils}/match-known-fields.js +11 -9
- package/cjs/http/utils/wrap-exception.js +34 -0
- package/cjs/index.js +26 -26
- package/cjs/platform-adapter.js +21 -0
- package/cjs/type-guards.js +23 -0
- package/esm/augmentation/18n.augmentation.js +20 -7
- package/esm/augmentation/http-controller.augmentation.js +23 -0
- package/esm/constants.js +2 -0
- package/esm/execution-context.js +24 -13
- package/esm/{services → helpers}/logger.js +1 -2
- package/esm/{services/api-service.js → helpers/service-base.js} +6 -6
- package/esm/http/express-adapter.js +148 -0
- package/esm/http/http-adapter.js +23 -0
- package/esm/http/http-context.js +114 -0
- package/esm/http/impl/asset-cache.js +17 -0
- package/esm/http/impl/http-handler.js +579 -0
- package/esm/http/{http-server-request.js → impl/http-incoming.host.js} +20 -46
- package/esm/http/{http-server-response.js → impl/http-outgoing.host.js} +7 -27
- package/esm/http/{helpers/multipart-helper.js → impl/multipart-reader.js} +21 -25
- package/esm/http/impl/{http-incoming-message.host.js → node-incoming-message.host.js} +16 -55
- package/esm/http/impl/{http-outgoing-message.host.js → node-outgoing-message.host.js} +9 -12
- package/esm/http/interfaces/http-incoming.interface.js +22 -0
- package/esm/http/interfaces/http-outgoing.interface.js +19 -0
- package/esm/http/interfaces/node-incoming-message.interface.js +61 -0
- package/esm/http/interfaces/node-outgoing-message.interface.js +12 -0
- package/esm/http/utils/body-reader.js +212 -0
- package/esm/http/{helpers → utils}/common.js +2 -1
- package/esm/http/{helpers → utils}/convert-to-headers.js +1 -1
- package/esm/http/{helpers → utils}/convert-to-raw-headers.js +2 -3
- package/esm/http/{helpers → utils}/match-known-fields.js +11 -9
- package/esm/http/utils/wrap-exception.js +30 -0
- package/esm/index.js +26 -27
- package/esm/platform-adapter.js +19 -1
- package/esm/type-guards.js +16 -0
- package/package.json +31 -13
- package/types/augmentation/18n.augmentation.d.ts +31 -1
- package/types/augmentation/http-controller.augmentation.d.ts +21 -0
- package/types/constants.d.ts +2 -0
- package/types/execution-context.d.ts +22 -26
- package/types/helpers/service-base.d.ts +10 -0
- package/types/http/express-adapter.d.ts +13 -0
- package/types/http/http-adapter.d.ts +27 -0
- package/types/http/http-context.d.ts +44 -0
- package/types/http/impl/asset-cache.d.ts +5 -0
- package/types/http/impl/http-handler.d.ts +73 -0
- package/types/http/impl/http-incoming.host.d.ts +23 -0
- package/types/http/impl/http-outgoing.host.d.ts +17 -0
- package/types/http/{helpers/multipart-helper.d.ts → impl/multipart-reader.d.ts} +8 -6
- package/types/http/impl/{http-incoming-message.host.d.ts → node-incoming-message.host.d.ts} +10 -23
- package/types/http/impl/{http-outgoing-message.host.d.ts → node-outgoing-message.host.d.ts} +11 -27
- package/types/http/{http-server-request.d.ts → interfaces/http-incoming.interface.d.ts} +28 -17
- package/types/http/{http-server-response.d.ts → interfaces/http-outgoing.interface.d.ts} +17 -10
- package/types/http/interfaces/node-incoming-message.interface.d.ts +38 -0
- package/types/http/interfaces/node-outgoing-message.interface.d.ts +29 -0
- package/types/http/utils/body-reader.d.ts +41 -0
- package/types/http/utils/wrap-exception.d.ts +2 -0
- package/types/index.d.ts +25 -27
- package/types/platform-adapter.d.ts +20 -48
- package/types/type-guards.d.ts +8 -0
- package/cjs/augmentation/collection.augmentation.js +0 -2
- package/cjs/augmentation/container.augmentation.js +0 -2
- package/cjs/augmentation/resource.augmentation.js +0 -26
- package/cjs/augmentation/singleton.augmentation.js +0 -2
- package/cjs/augmentation/storage.augmentation.js +0 -2
- package/cjs/execution-context.host.js +0 -46
- package/cjs/http/adapters/express-adapter.host.js +0 -34
- package/cjs/http/adapters/express-adapter.js +0 -14
- package/cjs/http/adapters/node-http-adapter.host.js +0 -70
- package/cjs/http/adapters/node-http-adapter.js +0 -14
- package/cjs/http/helpers/json-body-loader.js +0 -29
- package/cjs/http/helpers/query-parsers.js +0 -16
- package/cjs/http/http-adapter-host.js +0 -715
- package/cjs/interfaces/interceptor.interface.js +0 -2
- package/cjs/interfaces/request-handler.interface.js +0 -2
- package/cjs/platform-adapter.host.js +0 -154
- package/cjs/request-context.js +0 -25
- package/cjs/request.host.js +0 -24
- package/cjs/request.js +0 -2
- package/cjs/response.host.js +0 -22
- package/cjs/response.js +0 -2
- package/esm/augmentation/collection.augmentation.js +0 -1
- package/esm/augmentation/container.augmentation.js +0 -1
- package/esm/augmentation/resource.augmentation.js +0 -24
- package/esm/augmentation/singleton.augmentation.js +0 -1
- package/esm/augmentation/storage.augmentation.js +0 -1
- package/esm/execution-context.host.js +0 -42
- package/esm/http/adapters/express-adapter.host.js +0 -30
- package/esm/http/adapters/express-adapter.js +0 -11
- package/esm/http/adapters/node-http-adapter.host.js +0 -65
- package/esm/http/adapters/node-http-adapter.js +0 -11
- package/esm/http/helpers/json-body-loader.js +0 -24
- package/esm/http/helpers/query-parsers.js +0 -12
- package/esm/http/http-adapter-host.js +0 -710
- package/esm/interfaces/interceptor.interface.js +0 -1
- package/esm/interfaces/request-handler.interface.js +0 -1
- package/esm/platform-adapter.host.js +0 -149
- package/esm/request-context.js +0 -22
- package/esm/request.host.js +0 -20
- package/esm/request.js +0 -1
- package/esm/response.host.js +0 -18
- package/esm/response.js +0 -1
- package/types/augmentation/collection.augmentation.d.ts +0 -146
- package/types/augmentation/container.augmentation.d.ts +0 -14
- package/types/augmentation/resource.augmentation.d.ts +0 -38
- package/types/augmentation/singleton.augmentation.d.ts +0 -83
- package/types/augmentation/storage.augmentation.d.ts +0 -50
- package/types/execution-context.host.d.ts +0 -25
- package/types/http/adapters/express-adapter.d.ts +0 -15
- package/types/http/adapters/express-adapter.host.d.ts +0 -12
- package/types/http/adapters/node-http-adapter.d.ts +0 -17
- package/types/http/adapters/node-http-adapter.host.d.ts +0 -19
- package/types/http/helpers/json-body-loader.d.ts +0 -5
- package/types/http/helpers/query-parsers.d.ts +0 -1
- package/types/http/http-adapter-host.d.ts +0 -34
- package/types/interfaces/interceptor.interface.d.ts +0 -2
- package/types/interfaces/request-handler.interface.d.ts +0 -4
- package/types/platform-adapter.host.d.ts +0 -43
- package/types/request-context.d.ts +0 -13
- package/types/request.d.ts +0 -14
- package/types/request.host.d.ts +0 -27
- package/types/response.d.ts +0 -22
- package/types/response.host.d.ts +0 -22
- package/types/services/api-service.d.ts +0 -10
- /package/cjs/http/{helpers → utils}/concat-readable.js +0 -0
- /package/cjs/http/{helpers → utils}/convert-to-headers.js +0 -0
- /package/esm/http/{helpers → utils}/concat-readable.js +0 -0
- /package/types/{services → helpers}/logger.d.ts +0 -0
- /package/types/http/{helpers → utils}/common.d.ts +0 -0
- /package/types/http/{helpers → utils}/concat-readable.d.ts +0 -0
- /package/types/http/{helpers → utils}/convert-to-headers.d.ts +0 -0
- /package/types/http/{helpers → utils}/convert-to-raw-headers.d.ts +0 -0
- /package/types/http/{helpers → utils}/match-known-fields.d.ts +0 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import typeIs from '@browsery/type-is';
|
|
2
|
+
import { BadRequestError, InternalServerError, OpraHttpError } from '@opra/common';
|
|
3
|
+
import { Base64Decode } from 'base64-stream';
|
|
4
|
+
import byteParser from 'bytes';
|
|
5
|
+
import { parse as parseContentType } from 'content-type';
|
|
6
|
+
import { EventEmitter } from 'events';
|
|
7
|
+
import iconv from 'iconv-lite';
|
|
8
|
+
import { Writable } from 'stream';
|
|
9
|
+
import * as zlib from 'zlib';
|
|
10
|
+
/**
|
|
11
|
+
*
|
|
12
|
+
* @class BodyReader
|
|
13
|
+
*/
|
|
14
|
+
export class BodyReader extends EventEmitter {
|
|
15
|
+
constructor(req, options) {
|
|
16
|
+
super();
|
|
17
|
+
this.req = req;
|
|
18
|
+
this._completed = false;
|
|
19
|
+
this._receivedSize = 0;
|
|
20
|
+
this.onAborted = () => this._onAborted();
|
|
21
|
+
this.onData = (chunk) => this._onData(chunk);
|
|
22
|
+
this.onEnd = (err) => this._onEnd(err);
|
|
23
|
+
this.cleanup = () => this._cleanup();
|
|
24
|
+
this.limit = options?.limit
|
|
25
|
+
? typeof options.limit === 'number'
|
|
26
|
+
? options.limit
|
|
27
|
+
: byteParser(options.limit)
|
|
28
|
+
: undefined;
|
|
29
|
+
}
|
|
30
|
+
async read() {
|
|
31
|
+
/* istanbul ignore next */
|
|
32
|
+
if (this._completed) {
|
|
33
|
+
throw new InternalServerError({
|
|
34
|
+
message: 'Stream already read',
|
|
35
|
+
code: 'STREAM_ALREADY_READ',
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
if (!this.req.readable) {
|
|
39
|
+
throw new InternalServerError({
|
|
40
|
+
message: 'Stream is not readable',
|
|
41
|
+
code: 'STREAM_NOT_READABLE',
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
return new Promise((resolve, reject) => {
|
|
45
|
+
// eslint-disable-next-line prefer-const
|
|
46
|
+
let sizeStream;
|
|
47
|
+
this.once('finish', (error, data) => {
|
|
48
|
+
if (sizeStream)
|
|
49
|
+
this.req.unpipe(sizeStream);
|
|
50
|
+
if (error)
|
|
51
|
+
return reject(error);
|
|
52
|
+
resolve(data);
|
|
53
|
+
});
|
|
54
|
+
/**
|
|
55
|
+
* Check if a request has a request body.
|
|
56
|
+
* A request with a body __must__ either have `transfer-encoding`
|
|
57
|
+
* or `content-length` headers set.
|
|
58
|
+
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.3
|
|
59
|
+
*/
|
|
60
|
+
const contentLength = parseInt(this.req.headers['content-length'] || '0', 10);
|
|
61
|
+
if (this.req.headers['transfer-encoding'] === undefined && !(contentLength && !isNaN(contentLength))) {
|
|
62
|
+
return this.onEnd();
|
|
63
|
+
}
|
|
64
|
+
// check the length and limit options.
|
|
65
|
+
// note: we intentionally leave the stream paused,
|
|
66
|
+
// so users should handle the stream themselves.
|
|
67
|
+
if (this.limit != null && contentLength != null && contentLength > this.limit) {
|
|
68
|
+
return this.onEnd(new OpraHttpError({
|
|
69
|
+
message: 'Content Too Large',
|
|
70
|
+
code: 'HTTP.CONTENT_TOO_LARGE',
|
|
71
|
+
details: {
|
|
72
|
+
length: contentLength,
|
|
73
|
+
limit: this.limit,
|
|
74
|
+
},
|
|
75
|
+
}, 413));
|
|
76
|
+
}
|
|
77
|
+
// Pipe to a Writable stream to count received bytes
|
|
78
|
+
const _this = this;
|
|
79
|
+
sizeStream = new Writable({
|
|
80
|
+
write(chunk, encoding, callback) {
|
|
81
|
+
if (_this._completed)
|
|
82
|
+
return;
|
|
83
|
+
_this._receivedSize += chunk.length;
|
|
84
|
+
if (_this.limit != null && _this._receivedSize > _this.limit) {
|
|
85
|
+
callback(new OpraHttpError({
|
|
86
|
+
message: 'Content Too Large',
|
|
87
|
+
code: 'HTTP.CONTENT_TOO_LARGE',
|
|
88
|
+
details: {
|
|
89
|
+
limit: _this.limit,
|
|
90
|
+
received: _this._receivedSize,
|
|
91
|
+
},
|
|
92
|
+
}, 413));
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
this.req.pipe(sizeStream);
|
|
97
|
+
let stream = BodyReader.encoderPipeline(this.req);
|
|
98
|
+
const mediaType = parseContentType(this.req.headers['content-type'] || '');
|
|
99
|
+
let charset = (mediaType.parameters.charset || '').toLowerCase();
|
|
100
|
+
if (!charset && typeIs.is(mediaType.type, ['json', 'xml', 'txt']))
|
|
101
|
+
charset = 'utf-8';
|
|
102
|
+
if (charset) {
|
|
103
|
+
const newStream = iconv.decodeStream(charset);
|
|
104
|
+
stream.pipe(newStream);
|
|
105
|
+
stream = newStream;
|
|
106
|
+
}
|
|
107
|
+
this._stream = stream;
|
|
108
|
+
// attach listeners
|
|
109
|
+
stream.on('aborted', this.onAborted);
|
|
110
|
+
stream.on('close', this.cleanup);
|
|
111
|
+
stream.on('data', this.onData);
|
|
112
|
+
stream.on('end', this.onEnd);
|
|
113
|
+
stream.on('error', this.onEnd);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
_onEnd(error) {
|
|
117
|
+
if (this._completed)
|
|
118
|
+
return;
|
|
119
|
+
this._completed = true;
|
|
120
|
+
if (error) {
|
|
121
|
+
this._stream?.unpipe();
|
|
122
|
+
this._stream?.pause();
|
|
123
|
+
}
|
|
124
|
+
if (error)
|
|
125
|
+
this.emit('finish', error);
|
|
126
|
+
else if (Array.isArray(this._buffer))
|
|
127
|
+
this.emit('finish', error, Buffer.concat(this._buffer));
|
|
128
|
+
else
|
|
129
|
+
this.emit('finish', error, this._buffer);
|
|
130
|
+
this._cleanup();
|
|
131
|
+
}
|
|
132
|
+
_cleanup() {
|
|
133
|
+
if (this._stream) {
|
|
134
|
+
this._stream.removeListener('aborted', this.onAborted);
|
|
135
|
+
this._stream.removeListener('close', this.cleanup);
|
|
136
|
+
this._stream.removeListener('data', this.onData);
|
|
137
|
+
this._stream.removeListener('end', this.onEnd);
|
|
138
|
+
this._stream.removeListener('error', this.onEnd);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
_onAborted() {
|
|
142
|
+
if (this._completed)
|
|
143
|
+
return;
|
|
144
|
+
this.onEnd(new BadRequestError({
|
|
145
|
+
message: 'request aborted',
|
|
146
|
+
code: 'ECONNABORTED',
|
|
147
|
+
details: {
|
|
148
|
+
length,
|
|
149
|
+
received: this._receivedSize,
|
|
150
|
+
},
|
|
151
|
+
}));
|
|
152
|
+
}
|
|
153
|
+
_onData(chunk) {
|
|
154
|
+
if (this._completed)
|
|
155
|
+
return;
|
|
156
|
+
if (typeof chunk === 'string') {
|
|
157
|
+
this._buffer = this._buffer || '';
|
|
158
|
+
this._buffer += chunk;
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
this._buffer = this._buffer || [];
|
|
162
|
+
this._buffer.push(chunk);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
static async read(req, options) {
|
|
166
|
+
const bodyReady = new BodyReader(req, options);
|
|
167
|
+
return bodyReady.read();
|
|
168
|
+
}
|
|
169
|
+
static encoderPipeline(req) {
|
|
170
|
+
const contentEncoding = req.headers['content-encoding'] || 'identity';
|
|
171
|
+
const contentEncodings = (Array.isArray(contentEncoding) ? contentEncoding : contentEncoding.split(/\s*,\s*/))
|
|
172
|
+
.map(s => s.toLowerCase())
|
|
173
|
+
.reverse();
|
|
174
|
+
return contentEncodings.reduce((prev, encoding) => {
|
|
175
|
+
switch (encoding) {
|
|
176
|
+
case 'gzip':
|
|
177
|
+
case 'x-gzip': {
|
|
178
|
+
const newStream = zlib.createGunzip();
|
|
179
|
+
prev.pipe(newStream);
|
|
180
|
+
return newStream;
|
|
181
|
+
}
|
|
182
|
+
case 'deflate':
|
|
183
|
+
case 'x-deflate': {
|
|
184
|
+
const newStream = zlib.createInflate();
|
|
185
|
+
prev.pipe(newStream);
|
|
186
|
+
return newStream;
|
|
187
|
+
}
|
|
188
|
+
case 'br': {
|
|
189
|
+
const newStream = zlib.createBrotliDecompress();
|
|
190
|
+
prev.pipe(newStream);
|
|
191
|
+
return newStream;
|
|
192
|
+
}
|
|
193
|
+
case 'base64': {
|
|
194
|
+
const newStream = new Base64Decode();
|
|
195
|
+
prev.pipe(newStream);
|
|
196
|
+
return newStream;
|
|
197
|
+
}
|
|
198
|
+
case 'identity':
|
|
199
|
+
// prev.length = 0;
|
|
200
|
+
return prev;
|
|
201
|
+
default:
|
|
202
|
+
throw new BadRequestError({
|
|
203
|
+
message: 'unsupported content encoding "' + encoding + '"',
|
|
204
|
+
code: '',
|
|
205
|
+
details: {
|
|
206
|
+
encoding,
|
|
207
|
+
},
|
|
208
|
+
}, 415);
|
|
209
|
+
}
|
|
210
|
+
}, req);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
@@ -55,6 +55,7 @@ export const validateHeaderValue = hideStackFrames((name, value) => {
|
|
|
55
55
|
}
|
|
56
56
|
});
|
|
57
57
|
export function validateString(value, name) {
|
|
58
|
-
if (typeof value !== 'string')
|
|
58
|
+
if (typeof value !== 'string') {
|
|
59
59
|
throw new TypeError(`Invalid ${name ? name + ' ' : ''}argument. Value must be a string`);
|
|
60
|
+
}
|
|
60
61
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ARRAY_FIELD, COMMA_DELIMITED_FIELD, matchKnownFields, SEMICOLON_DELIMITED_FIELD } from './match-known-fields.js';
|
|
1
|
+
import { ARRAY_FIELD, COMMA_DELIMITED_FIELD, matchKnownFields, SEMICOLON_DELIMITED_FIELD, } from './match-known-fields.js';
|
|
2
2
|
export function convertToHeaders(src, dst, joinDuplicateHeaders) {
|
|
3
3
|
for (let n = 0; n < src.length; n += 2) {
|
|
4
4
|
addHeaderLine(src[n], src[n + 1], dst, joinDuplicateHeaders);
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { ARRAY_FIELD, COMMA_DELIMITED_FIELD, matchKnownFields, SEMICOLON_DELIMITED_FIELD } from './match-known-fields.js';
|
|
1
|
+
import { ARRAY_FIELD, COMMA_DELIMITED_FIELD, matchKnownFields, SEMICOLON_DELIMITED_FIELD, } from './match-known-fields.js';
|
|
2
2
|
export function convertToRawHeaders(src) {
|
|
3
|
-
return Object.entries(src)
|
|
4
|
-
.reduce((a, [field, v]) => {
|
|
3
|
+
return Object.entries(src).reduce((a, [field, v]) => {
|
|
5
4
|
const [name, flag] = matchKnownFields(field);
|
|
6
5
|
if (flag === ARRAY_FIELD) {
|
|
7
6
|
if (Array.isArray(v))
|
|
@@ -3,9 +3,7 @@ export const NO_DUPLICATES_FIELD = 0;
|
|
|
3
3
|
export const COMMA_DELIMITED_FIELD = 1;
|
|
4
4
|
export const SEMICOLON_DELIMITED_FIELD = 2;
|
|
5
5
|
export const ARRAY_FIELD = 3;
|
|
6
|
-
const ARRAY_HEADERS = [
|
|
7
|
-
'set-cookie'
|
|
8
|
-
];
|
|
6
|
+
const ARRAY_HEADERS = ['set-cookie'];
|
|
9
7
|
const NO_DUPLICATES_HEADERS = [
|
|
10
8
|
'age',
|
|
11
9
|
'from',
|
|
@@ -27,13 +25,17 @@ const NO_DUPLICATES_HEADERS = [
|
|
|
27
25
|
'if-unmodified-since',
|
|
28
26
|
];
|
|
29
27
|
const SEMICOLON_DELIMITED_HEADERS = ['cookie'];
|
|
30
|
-
const KNOWN_FIELDS = Object.values(HttpHeaderCodes)
|
|
31
|
-
.reduce((o, k) => {
|
|
28
|
+
const KNOWN_FIELDS = Object.values(HttpHeaderCodes).reduce((o, k) => {
|
|
32
29
|
const n = k.toLowerCase();
|
|
33
|
-
o[n] = [
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
30
|
+
o[n] = [
|
|
31
|
+
k,
|
|
32
|
+
NO_DUPLICATES_HEADERS.includes(n)
|
|
33
|
+
? NO_DUPLICATES_FIELD
|
|
34
|
+
: ARRAY_HEADERS.includes(n)
|
|
35
|
+
? ARRAY_FIELD
|
|
36
|
+
: SEMICOLON_DELIMITED_HEADERS.includes(n)
|
|
37
|
+
? SEMICOLON_DELIMITED_FIELD
|
|
38
|
+
: COMMA_DELIMITED_FIELD,
|
|
37
39
|
];
|
|
38
40
|
return o;
|
|
39
41
|
}, {});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { BadRequestError, FailedDependencyError, ForbiddenError, InternalServerError, MethodNotAllowedError, NotAcceptableError, NotFoundError, OpraHttpError, UnauthorizedError, UnprocessableEntityError, } from '@opra/common';
|
|
2
|
+
export function wrapException(error) {
|
|
3
|
+
if (error instanceof OpraHttpError)
|
|
4
|
+
return error;
|
|
5
|
+
let status = 500;
|
|
6
|
+
if (typeof error.status === 'number')
|
|
7
|
+
status = error.status;
|
|
8
|
+
else if (typeof error.getStatus === 'function')
|
|
9
|
+
status = error.getStatus();
|
|
10
|
+
switch (status) {
|
|
11
|
+
case 400:
|
|
12
|
+
return new BadRequestError(error);
|
|
13
|
+
case 401:
|
|
14
|
+
return new UnauthorizedError(error);
|
|
15
|
+
case 403:
|
|
16
|
+
return new ForbiddenError(error);
|
|
17
|
+
case 404:
|
|
18
|
+
return new NotFoundError(error);
|
|
19
|
+
case 405:
|
|
20
|
+
return new MethodNotAllowedError(error);
|
|
21
|
+
case 406:
|
|
22
|
+
return new NotAcceptableError(error);
|
|
23
|
+
case 422:
|
|
24
|
+
return new UnprocessableEntityError(error);
|
|
25
|
+
case 424:
|
|
26
|
+
return new FailedDependencyError(error);
|
|
27
|
+
default:
|
|
28
|
+
return new InternalServerError(error);
|
|
29
|
+
}
|
|
30
|
+
}
|
package/esm/index.js
CHANGED
|
@@ -1,30 +1,29 @@
|
|
|
1
|
-
import
|
|
1
|
+
import 'reflect-metadata';
|
|
2
2
|
import './augmentation/18n.augmentation.js';
|
|
3
|
-
import './augmentation/
|
|
4
|
-
import './
|
|
5
|
-
import './
|
|
6
|
-
import './
|
|
7
|
-
import './
|
|
3
|
+
import './augmentation/http-controller.augmentation.js';
|
|
4
|
+
import * as HttpIncomingHost_ from './http/impl/http-incoming.host.js';
|
|
5
|
+
import * as HttpOutgoingHost_ from './http/impl/http-outgoing.host.js';
|
|
6
|
+
import * as NodeIncomingMessageHost_ from './http/impl/node-incoming-message.host.js';
|
|
7
|
+
import * as NodeOutgoingMessageHost_ from './http/impl/node-outgoing-message.host.js';
|
|
8
8
|
export * from './execution-context.js';
|
|
9
|
-
export * from './
|
|
10
|
-
export * from './
|
|
11
|
-
export * from './
|
|
12
|
-
export * from './
|
|
13
|
-
export * from './
|
|
14
|
-
export * from './
|
|
15
|
-
export * from './
|
|
16
|
-
export * from './
|
|
17
|
-
export * from './http/
|
|
18
|
-
export * from './http/
|
|
19
|
-
export * from './http/
|
|
20
|
-
export * from './http/adapters/node-http-adapter.host.js';
|
|
21
|
-
export * from './http/impl/http-incoming-message.host.js';
|
|
22
|
-
export * from './http/impl/http-outgoing-message.host.js';
|
|
23
|
-
export * from './http/http-server-request.js';
|
|
24
|
-
export * from './http/http-server-response.js';
|
|
25
|
-
export * from './http/helpers/multipart-helper.js';
|
|
26
|
-
export * from './interfaces/interceptor.interface.js';
|
|
9
|
+
export * from './helpers/logger.js';
|
|
10
|
+
export * from './helpers/service-base.js';
|
|
11
|
+
export * from './http/express-adapter.js';
|
|
12
|
+
export * from './http/http-adapter.js';
|
|
13
|
+
export * from './http/http-context.js';
|
|
14
|
+
export * from './http/impl/multipart-reader.js';
|
|
15
|
+
export * from './http/interfaces/http-incoming.interface.js';
|
|
16
|
+
export * from './http/interfaces/http-outgoing.interface.js';
|
|
17
|
+
export * from './http/interfaces/node-incoming-message.interface.js';
|
|
18
|
+
export * from './http/interfaces/node-outgoing-message.interface.js';
|
|
19
|
+
export * from './http/utils/wrap-exception.js';
|
|
27
20
|
export * from './interfaces/logger.interface.js';
|
|
28
|
-
export * from './
|
|
29
|
-
export * from './
|
|
30
|
-
export
|
|
21
|
+
export * from './platform-adapter.js';
|
|
22
|
+
export * from './type-guards.js';
|
|
23
|
+
export var classes;
|
|
24
|
+
(function (classes) {
|
|
25
|
+
classes.HttpIncomingHost = HttpIncomingHost_.HttpIncomingHost;
|
|
26
|
+
classes.HttpOutgoingHost = HttpOutgoingHost_.HttpOutgoingHost;
|
|
27
|
+
classes.NodeIncomingMessageHost = NodeIncomingMessageHost_.NodeIncomingMessageHost;
|
|
28
|
+
classes.NodeOutgoingMessageHost = NodeOutgoingMessageHost_.NodeOutgoingMessageHost;
|
|
29
|
+
})(classes || (classes = {}));
|
package/esm/platform-adapter.js
CHANGED
|
@@ -1 +1,19 @@
|
|
|
1
|
-
|
|
1
|
+
import './augmentation/18n.augmentation.js';
|
|
2
|
+
import { I18n } from '@opra/common';
|
|
3
|
+
import { AsyncEventEmitter } from 'strict-typed-events';
|
|
4
|
+
import { kAssetCache } from './constants.js';
|
|
5
|
+
import { Logger } from './helpers/logger.js';
|
|
6
|
+
import { AssetCache } from './http/impl/asset-cache.js';
|
|
7
|
+
/**
|
|
8
|
+
* @class PlatformAdapter
|
|
9
|
+
*/
|
|
10
|
+
export class PlatformAdapter extends AsyncEventEmitter {
|
|
11
|
+
constructor(document, options) {
|
|
12
|
+
super();
|
|
13
|
+
this[kAssetCache] = new AssetCache();
|
|
14
|
+
this.document = document;
|
|
15
|
+
this.logger =
|
|
16
|
+
options?.logger && options.logger instanceof Logger ? options.logger : new Logger({ instance: options?.logger });
|
|
17
|
+
this.i18n = options?.i18n || I18n.defaultInstance;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { isReadable, isStream } from '@opra/common';
|
|
2
|
+
export function isNodeIncomingMessage(v) {
|
|
3
|
+
return v && typeof v.method === 'string' && Array.isArray(v.rawHeaders) && isReadable(v);
|
|
4
|
+
}
|
|
5
|
+
export function isHttpIncoming(v) {
|
|
6
|
+
return (isNodeIncomingMessage(v) &&
|
|
7
|
+
typeof v.header === 'function' &&
|
|
8
|
+
typeof v.acceptsLanguages === 'function' &&
|
|
9
|
+
typeof v.readBody === 'function');
|
|
10
|
+
}
|
|
11
|
+
export function isNodeOutgoingMessage(v) {
|
|
12
|
+
return v && typeof v.getHeaders === 'function' && isStream(v);
|
|
13
|
+
}
|
|
14
|
+
export function isHttpOutgoing(v) {
|
|
15
|
+
return isNodeOutgoingMessage(v) && typeof v.clearCookie === 'function' && typeof v.cookie === 'function';
|
|
16
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opra/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0-alpha.10",
|
|
4
4
|
"description": "Opra schema package",
|
|
5
5
|
"author": "Panates",
|
|
6
6
|
"license": "MIT",
|
|
@@ -19,8 +19,11 @@
|
|
|
19
19
|
"_copy_pkg_files": "cp README.md package.json ../../LICENSE ../../build/core && cp ../../package.cjs.json ../../build/core/cjs/package.json",
|
|
20
20
|
"_copyi18n": "cp -R i18n ../../build/core/i18n",
|
|
21
21
|
"lint": "eslint . --max-warnings=0",
|
|
22
|
-
"
|
|
23
|
-
"
|
|
22
|
+
"lint:fix": "eslint . --max-warnings=0 --fix",
|
|
23
|
+
"format": "prettier . --write --log-level=warn",
|
|
24
|
+
"check": "madge --circular src/**",
|
|
25
|
+
"test": "jest --passWithNoTests",
|
|
26
|
+
"cover": "jest --passWithNoTests --collect-coverage",
|
|
24
27
|
"clean": "npm run clean:src && npm run clean:test && npm run clean:dist && npm run clean:cover",
|
|
25
28
|
"clean:src": "ts-cleanup -s src --all",
|
|
26
29
|
"clean:test": "ts-cleanup -s test --all",
|
|
@@ -28,45 +31,60 @@
|
|
|
28
31
|
"clean:cover": "rimraf ../../coverage/client"
|
|
29
32
|
},
|
|
30
33
|
"dependencies": {
|
|
31
|
-
"@browsery/
|
|
32
|
-
"@
|
|
34
|
+
"@browsery/http-parser": "^0.5.8",
|
|
35
|
+
"@browsery/type-is": "^1.6.18-r2",
|
|
36
|
+
"@opra/common": "^1.0.0-alpha.10",
|
|
33
37
|
"@types/formidable": "^3.4.5",
|
|
34
38
|
"accepts": "^1.3.8",
|
|
39
|
+
"base64-stream": "^1.0.0",
|
|
40
|
+
"bytes": "^3.1.2",
|
|
35
41
|
"content-disposition": "^0.5.4",
|
|
36
42
|
"content-type": "^1.0.5",
|
|
37
43
|
"cookie": "^0.6.0",
|
|
38
44
|
"cookie-signature": "^1.2.1",
|
|
39
|
-
"
|
|
45
|
+
"cppzst": "^2.0.12",
|
|
46
|
+
"encodeurl": "^2.0.0",
|
|
40
47
|
"formidable": "^3.5.1",
|
|
41
48
|
"fresh": "^0.5.2",
|
|
49
|
+
"iconv-lite": "^0.6.3",
|
|
42
50
|
"mime-types": "^2.1.35",
|
|
43
|
-
"power-tasks": "^1.7.
|
|
51
|
+
"power-tasks": "^1.7.4",
|
|
44
52
|
"putil-isplainobject": "^1.1.5",
|
|
53
|
+
"putil-merge": "^3.12.1",
|
|
45
54
|
"putil-varhelpers": "^1.6.5",
|
|
46
55
|
"range-parser": "^1.2.1",
|
|
56
|
+
"raw-body": "^2.5.2",
|
|
57
|
+
"reflect-metadata": "^0.2.2",
|
|
47
58
|
"strict-typed-events": "^2.3.3",
|
|
48
59
|
"vary": "^1.1.2"
|
|
49
60
|
},
|
|
50
|
-
"
|
|
51
|
-
"express": "^4.x.x || ^5.x.x"
|
|
61
|
+
"optionalDependencies": {
|
|
62
|
+
"express": "^4.x.x || ^5.x.x",
|
|
63
|
+
"fastify": "^4.x.x"
|
|
52
64
|
},
|
|
53
65
|
"devDependencies": {
|
|
54
|
-
"@faker-js/faker": "^8.4.
|
|
66
|
+
"@faker-js/faker": "^8.4.1",
|
|
55
67
|
"@types/accepts": "^1.3.7",
|
|
68
|
+
"@types/base64-stream": "^1.0.5",
|
|
69
|
+
"@types/bytes": "^3.1.4",
|
|
56
70
|
"@types/content-disposition": "^0.5.8",
|
|
57
71
|
"@types/content-type": "^1.1.8",
|
|
58
72
|
"@types/cookie": "^0.6.0",
|
|
73
|
+
"@types/cookie-parser": "^1.4.7",
|
|
59
74
|
"@types/cookie-signature": "^1.1.2",
|
|
60
75
|
"@types/encodeurl": "^1.0.2",
|
|
61
76
|
"@types/express": "^4.17.21",
|
|
62
77
|
"@types/fresh": "^0.5.2",
|
|
63
78
|
"@types/mime-types": "^2.1.4",
|
|
64
79
|
"@types/range-parser": "^1.2.7",
|
|
65
|
-
"@types/type-is": "^1.6.6",
|
|
66
80
|
"@types/vary": "^1.1.3",
|
|
81
|
+
"cookie-parser": "^1.4.6",
|
|
67
82
|
"crypto-browserify": "^3.12.0",
|
|
83
|
+
"express": "^4.19.2",
|
|
84
|
+
"fastify": "^4.28.1",
|
|
68
85
|
"path-browserify": "^1.0.1",
|
|
69
|
-
"
|
|
86
|
+
"supertest": "^7.0.0",
|
|
87
|
+
"ts-gems": "^3.4.0"
|
|
70
88
|
},
|
|
71
89
|
"type": "module",
|
|
72
90
|
"module": "./esm/index.js",
|
|
@@ -95,4 +113,4 @@
|
|
|
95
113
|
"swagger",
|
|
96
114
|
"raml"
|
|
97
115
|
]
|
|
98
|
-
}
|
|
116
|
+
}
|
|
@@ -1,7 +1,37 @@
|
|
|
1
|
-
declare module
|
|
1
|
+
declare module '@opra/common' {
|
|
2
2
|
interface I18n {
|
|
3
3
|
loadResourceDir(dirnames: string | string[], deep?: boolean, overwrite?: boolean): Promise<void>;
|
|
4
4
|
loadResourceBundle(lang: string, ns: string, filePath: string, deep?: boolean, overwrite?: boolean): Promise<void>;
|
|
5
5
|
}
|
|
6
|
+
namespace I18n {
|
|
7
|
+
function load(): Promise<I18n>;
|
|
8
|
+
interface Options {
|
|
9
|
+
/**
|
|
10
|
+
* Language to use
|
|
11
|
+
* @default undefined
|
|
12
|
+
*/
|
|
13
|
+
lng?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Language to use if translations in user language are not available.
|
|
16
|
+
* @default 'dev'
|
|
17
|
+
*/
|
|
18
|
+
fallbackLng?: false | FallbackLng;
|
|
19
|
+
/**
|
|
20
|
+
* Default namespace used if not passed to translation function
|
|
21
|
+
* @default 'translation'
|
|
22
|
+
*/
|
|
23
|
+
defaultNS?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Resources to initialize with
|
|
26
|
+
* @default undefined
|
|
27
|
+
*/
|
|
28
|
+
resources?: LanguageResource;
|
|
29
|
+
/**
|
|
30
|
+
* Resource directories to initialize with (if not using loading or not appending using addResourceBundle)
|
|
31
|
+
* @default undefined
|
|
32
|
+
*/
|
|
33
|
+
resourceDirs?: string[];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
6
36
|
}
|
|
7
37
|
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { HttpContext } from '../http/http-context';
|
|
2
|
+
declare module '@opra/common' {
|
|
3
|
+
interface HttpControllerStatic {
|
|
4
|
+
OnInit(): PropertyDecorator;
|
|
5
|
+
OnShutdown(): PropertyDecorator;
|
|
6
|
+
}
|
|
7
|
+
interface HttpController {
|
|
8
|
+
onInit?: (resource: HttpController) => void;
|
|
9
|
+
onShutdown?: (resource: HttpController) => void | Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
namespace HttpController {
|
|
12
|
+
interface InitArguments {
|
|
13
|
+
onInit?: (resource: HttpController) => void;
|
|
14
|
+
onShutdown?: (resource: HttpController) => void | Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
namespace HttpOperation {
|
|
18
|
+
interface Context extends HttpContext {
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -1,30 +1,26 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
readonly platform: string;
|
|
7
|
-
switchToHttp(): HttpMessageContext;
|
|
8
|
-
switchToWs(): WsMessageContext;
|
|
9
|
-
switchToRpc(): RpcMessageContext;
|
|
10
|
-
on(event: 'finish', fn: (args: ExecutionContext.OnFinishArgs) => void | Promise<void>): any;
|
|
11
|
-
}
|
|
1
|
+
import { ApiDocument, OpraSchema } from '@opra/common';
|
|
2
|
+
import { AsyncEventEmitter } from 'strict-typed-events';
|
|
3
|
+
/**
|
|
4
|
+
* @namespace ExecutionContext
|
|
5
|
+
*/
|
|
12
6
|
export declare namespace ExecutionContext {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
7
|
+
interface Initiator {
|
|
8
|
+
document: ApiDocument;
|
|
9
|
+
protocol: OpraSchema.Protocol;
|
|
10
|
+
platform: string;
|
|
11
|
+
}
|
|
12
|
+
type OnFinishListener = (error: Error | undefined, context: ExecutionContext) => void | Promise<void>;
|
|
18
13
|
}
|
|
19
|
-
|
|
14
|
+
/**
|
|
15
|
+
* @class ExecutionContext
|
|
16
|
+
*/
|
|
17
|
+
export declare abstract class ExecutionContext extends AsyncEventEmitter {
|
|
18
|
+
readonly document: ApiDocument;
|
|
19
|
+
readonly protocol: OpraSchema.Protocol;
|
|
20
20
|
readonly platform: string;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
switchToContext(): ExecutionContext;
|
|
27
|
-
}
|
|
28
|
-
export interface RpcMessageContext {
|
|
29
|
-
switchToContext(): ExecutionContext;
|
|
21
|
+
protected constructor(init: ExecutionContext.Initiator);
|
|
22
|
+
addListener(event: 'finish', listener: ExecutionContext.OnFinishListener): this;
|
|
23
|
+
removeListener(event: 'finish', listener: ExecutionContext.OnFinishListener): this;
|
|
24
|
+
on(event: 'finish', listener: ExecutionContext.OnFinishListener): this;
|
|
25
|
+
off(event: 'finish', listener: ExecutionContext.OnFinishListener): this;
|
|
30
26
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Nullish } from 'ts-gems';
|
|
2
|
+
import type { HttpContext } from '../http/http-context';
|
|
3
|
+
export declare abstract class ServiceBase {
|
|
4
|
+
protected _context: HttpContext;
|
|
5
|
+
get context(): HttpContext;
|
|
6
|
+
for<C extends HttpContext, P extends Partial<this>>(context: C, overwriteProperties?: Nullish<P>, overwriteContext?: Partial<C>): this & Required<P>;
|
|
7
|
+
}
|
|
8
|
+
export declare namespace ServiceBase {
|
|
9
|
+
const extendSymbol: unique symbol;
|
|
10
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ApiDocument, HttpController } from '@opra/common';
|
|
2
|
+
import { Application } from 'express';
|
|
3
|
+
import { HttpAdapter } from './http-adapter.js';
|
|
4
|
+
export declare class ExpressAdapter extends HttpAdapter {
|
|
5
|
+
readonly app: Application;
|
|
6
|
+
protected _controllerInstances: Map<HttpController, any>;
|
|
7
|
+
constructor(app: Application, document: ApiDocument, options?: HttpAdapter.Options);
|
|
8
|
+
get platform(): string;
|
|
9
|
+
close(): Promise<void>;
|
|
10
|
+
getControllerInstance<T>(controllerPath: string): T | undefined;
|
|
11
|
+
protected _initRouter(basePath?: string): void;
|
|
12
|
+
protected _createControllers(controller: HttpController): void;
|
|
13
|
+
}
|