@opra/core 1.0.0-alpha.7 → 1.0.0-alpha.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cjs/augmentation/18n.augmentation.js +1 -1
- package/cjs/http/express-adapter.js +6 -12
- package/cjs/http/http-context.js +5 -3
- package/cjs/http/impl/http-handler.js +53 -39
- 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 +4 -10
- 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 -4
- package/cjs/http/utils/common.js +2 -1
- package/cjs/index.js +3 -3
- package/cjs/platform-adapter.js +1 -1
- package/esm/augmentation/18n.augmentation.js +1 -1
- package/esm/http/express-adapter.js +6 -12
- package/esm/http/http-context.js +5 -3
- package/esm/http/impl/http-handler.js +53 -39
- 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 +4 -10
- 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 -4
- package/esm/http/utils/common.js +2 -1
- package/esm/index.js +3 -3
- package/esm/platform-adapter.js +1 -1
- package/package.json +10 -4
- package/types/augmentation/18n.augmentation.d.ts +1 -1
- package/types/execution-context.d.ts +1 -1
- package/types/http/express-adapter.d.ts +1 -1
- package/types/http/impl/node-incoming-message.host.d.ts +1 -1
- package/types/http/utils/body-reader.d.ts +1 -1
- package/types/index.d.ts +3 -3
- package/types/platform-adapter.d.ts +1 -1
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
2
|
import { BadRequestError, 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';
|
|
@@ -74,10 +74,11 @@ export class HttpContext extends ExecutionContext {
|
|
|
74
74
|
}
|
|
75
75
|
/** Check required fields */
|
|
76
76
|
for (const field of multipartFields) {
|
|
77
|
-
if (field.required && !fieldsFound.get(field))
|
|
77
|
+
if (field.required && !fieldsFound.get(field)) {
|
|
78
78
|
throw new BadRequestError({
|
|
79
79
|
message: `Multipart field (${field.fieldName}) is required`,
|
|
80
80
|
});
|
|
81
|
+
}
|
|
81
82
|
}
|
|
82
83
|
}
|
|
83
84
|
return this._body;
|
|
@@ -85,8 +86,9 @@ export class HttpContext extends ExecutionContext {
|
|
|
85
86
|
this._body = await this.request.readBody({ limit: operation.requestBody?.maxContentSize });
|
|
86
87
|
if (this._body != null) {
|
|
87
88
|
// Convert Buffer to string if media is text
|
|
88
|
-
if (Buffer.isBuffer(this._body) && request.is(['json', 'xml', 'txt', 'text']))
|
|
89
|
+
if (Buffer.isBuffer(this._body) && request.is(['json', 'xml', 'txt', 'text'])) {
|
|
89
90
|
this._body = this._body.toString('utf-8');
|
|
91
|
+
}
|
|
90
92
|
// Transform text to Object if media is JSON
|
|
91
93
|
if (typeof this._body === 'string' && request.is(['json']))
|
|
92
94
|
this._body = JSON.parse(this._body);
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
import * as process from 'node:process';
|
|
2
|
+
import typeIs from '@browsery/type-is';
|
|
3
|
+
import { BadRequestError, HttpHeaderCodes, HttpStatusCode, InternalServerError, isBlob, isReadableStream, IssueSeverity, MethodNotAllowedError, MimeTypes, OperationResult, OpraException, OpraSchema, translate, } from '@opra/common';
|
|
1
4
|
import { parse as parseContentType } from 'content-type';
|
|
2
5
|
import { splitString } from 'fast-tokenizer';
|
|
3
|
-
import * as process from 'node:process';
|
|
4
6
|
import { asMutable } from 'ts-gems';
|
|
5
7
|
import { toArray, ValidationError, vg } from 'valgen';
|
|
6
|
-
import typeIs from '@browsery/type-is';
|
|
7
|
-
import { BadRequestError, HttpHeaderCodes, HttpStatusCode, InternalServerError, isBlob, isReadableStream, IssueSeverity, MethodNotAllowedError, MimeTypes, OperationResult, OpraException, OpraSchema, translate, } from '@opra/common';
|
|
8
8
|
import { kAssetCache } from '../../constants.js';
|
|
9
9
|
import { wrapException } from '../utils/wrap-exception.js';
|
|
10
10
|
/**
|
|
@@ -60,7 +60,8 @@ export class HttpHandler {
|
|
|
60
60
|
else
|
|
61
61
|
await this._executeRequest(context);
|
|
62
62
|
}
|
|
63
|
-
catch (
|
|
63
|
+
catch (error) {
|
|
64
|
+
let e = error;
|
|
64
65
|
if (e instanceof ValidationError) {
|
|
65
66
|
e = new InternalServerError({
|
|
66
67
|
message: translate('error:RESPONSE_VALIDATION,', 'Response validation failed'),
|
|
@@ -108,10 +109,10 @@ export class HttpHandler {
|
|
|
108
109
|
*/
|
|
109
110
|
async _parseParameters(context) {
|
|
110
111
|
const { operation, request } = context;
|
|
111
|
-
let
|
|
112
|
+
let key = '';
|
|
112
113
|
try {
|
|
113
114
|
const onFail = (issue) => {
|
|
114
|
-
issue.location =
|
|
115
|
+
issue.location = key;
|
|
115
116
|
return issue;
|
|
116
117
|
};
|
|
117
118
|
/** prepare decoders */
|
|
@@ -126,9 +127,9 @@ export class HttpHandler {
|
|
|
126
127
|
const paramsLeft = new Set([...operation.parameters, ...operation.owner.parameters]);
|
|
127
128
|
/** parse cookie parameters */
|
|
128
129
|
if (request.cookies) {
|
|
129
|
-
for (
|
|
130
|
-
const oprPrm = operation.findParameter(
|
|
131
|
-
const cntPrm = operation.owner.findParameter(
|
|
130
|
+
for (key of Object.keys(request.cookies)) {
|
|
131
|
+
const oprPrm = operation.findParameter(key, 'cookie');
|
|
132
|
+
const cntPrm = operation.owner.findParameter(key, 'cookie');
|
|
132
133
|
const prm = oprPrm || cntPrm;
|
|
133
134
|
if (!prm)
|
|
134
135
|
continue;
|
|
@@ -137,16 +138,17 @@ export class HttpHandler {
|
|
|
137
138
|
if (cntPrm)
|
|
138
139
|
paramsLeft.delete(cntPrm);
|
|
139
140
|
const decode = getDecoder(prm);
|
|
140
|
-
const v = decode(request.cookies[
|
|
141
|
+
const v = decode(request.cookies[key], { coerce: true, label: key, onFail });
|
|
142
|
+
const prmName = typeof prm.name === 'string' ? prm.name : key;
|
|
141
143
|
if (v !== undefined)
|
|
142
144
|
context.cookies[prmName] = v;
|
|
143
145
|
}
|
|
144
146
|
}
|
|
145
147
|
/** parse headers */
|
|
146
148
|
if (request.headers) {
|
|
147
|
-
for (
|
|
148
|
-
const oprPrm = operation.findParameter(
|
|
149
|
-
const cntPrm = operation.owner.findParameter(
|
|
149
|
+
for (key of Object.keys(request.headers)) {
|
|
150
|
+
const oprPrm = operation.findParameter(key, 'header');
|
|
151
|
+
const cntPrm = operation.owner.findParameter(key, 'header');
|
|
150
152
|
const prm = oprPrm || cntPrm;
|
|
151
153
|
if (!prm)
|
|
152
154
|
continue;
|
|
@@ -155,16 +157,17 @@ export class HttpHandler {
|
|
|
155
157
|
if (cntPrm)
|
|
156
158
|
paramsLeft.delete(cntPrm);
|
|
157
159
|
const decode = getDecoder(prm);
|
|
158
|
-
const v = decode(request.headers[
|
|
160
|
+
const v = decode(request.headers[key], { coerce: true, label: key, onFail });
|
|
161
|
+
const prmName = typeof prm.name === 'string' ? prm.name : key;
|
|
159
162
|
if (v !== undefined)
|
|
160
163
|
context.headers[prmName] = v;
|
|
161
164
|
}
|
|
162
165
|
}
|
|
163
166
|
/** parse path parameters */
|
|
164
167
|
if (request.params) {
|
|
165
|
-
for (
|
|
166
|
-
const oprPrm = operation.findParameter(
|
|
167
|
-
const cntPrm = operation.owner.findParameter(
|
|
168
|
+
for (key of Object.keys(request.params)) {
|
|
169
|
+
const oprPrm = operation.findParameter(key, 'path');
|
|
170
|
+
const cntPrm = operation.owner.findParameter(key, 'path');
|
|
168
171
|
const prm = oprPrm || cntPrm;
|
|
169
172
|
if (!prm)
|
|
170
173
|
continue;
|
|
@@ -173,17 +176,17 @@ export class HttpHandler {
|
|
|
173
176
|
if (cntPrm)
|
|
174
177
|
paramsLeft.delete(cntPrm);
|
|
175
178
|
const decode = getDecoder(prm);
|
|
176
|
-
const v = decode(request.params[
|
|
179
|
+
const v = decode(request.params[key], { coerce: true, label: key, onFail });
|
|
177
180
|
if (v !== undefined)
|
|
178
|
-
context.pathParams[
|
|
181
|
+
context.pathParams[key] = v;
|
|
179
182
|
}
|
|
180
183
|
}
|
|
181
184
|
/** parse query parameters */
|
|
182
185
|
const url = new URL(request.originalUrl || request.url || '/', 'http://tempuri.org');
|
|
183
186
|
const { searchParams } = url;
|
|
184
|
-
for (
|
|
185
|
-
const oprPrm = operation.findParameter(
|
|
186
|
-
const cntPrm = operation.owner.findParameter(
|
|
187
|
+
for (key of searchParams.keys()) {
|
|
188
|
+
const oprPrm = operation.findParameter(key, 'query');
|
|
189
|
+
const cntPrm = operation.owner.findParameter(key, 'query');
|
|
187
190
|
const prm = oprPrm || cntPrm;
|
|
188
191
|
if (!prm)
|
|
189
192
|
continue;
|
|
@@ -192,15 +195,16 @@ export class HttpHandler {
|
|
|
192
195
|
if (cntPrm)
|
|
193
196
|
paramsLeft.delete(cntPrm);
|
|
194
197
|
const decode = getDecoder(prm);
|
|
195
|
-
let values = searchParams?.getAll(
|
|
198
|
+
let values = searchParams?.getAll(key);
|
|
199
|
+
const prmName = typeof prm.name === 'string' ? prm.name : key;
|
|
196
200
|
if (values?.length && prm.isArray) {
|
|
197
201
|
values = values.map(v => splitString(v, { delimiters: prm.arraySeparator, quotes: true })).flat();
|
|
198
|
-
values = values.map(v => decode(v, { coerce: true, label:
|
|
202
|
+
values = values.map(v => decode(v, { coerce: true, label: key, onFail }));
|
|
199
203
|
if (values.length)
|
|
200
204
|
context.queryParams[prmName] = values;
|
|
201
205
|
}
|
|
202
206
|
else {
|
|
203
|
-
const v = decode(values[0], { coerce: true, label:
|
|
207
|
+
const v = decode(values[0], { coerce: true, label: key, onFail });
|
|
204
208
|
if (values.length)
|
|
205
209
|
context.queryParams[prmName] = v;
|
|
206
210
|
}
|
|
@@ -215,8 +219,8 @@ export class HttpHandler {
|
|
|
215
219
|
}
|
|
216
220
|
catch (e) {
|
|
217
221
|
if (e instanceof ValidationError) {
|
|
218
|
-
|
|
219
|
-
message: `Invalid parameter (${
|
|
222
|
+
throw new BadRequestError({
|
|
223
|
+
message: `Invalid parameter (${key}) value. ` + e.message,
|
|
220
224
|
code: 'REQUEST_VALIDATION',
|
|
221
225
|
details: e.issues,
|
|
222
226
|
}, e);
|
|
@@ -236,9 +240,8 @@ export class HttpHandler {
|
|
|
236
240
|
let contentType = request.header('content-type');
|
|
237
241
|
if (contentType) {
|
|
238
242
|
contentType = parseContentType(contentType).type;
|
|
239
|
-
mediaType = operation.requestBody.content.find(mc =>
|
|
240
|
-
|
|
241
|
-
});
|
|
243
|
+
mediaType = operation.requestBody.content.find(mc => mc.contentType &&
|
|
244
|
+
typeIs.is(contentType, Array.isArray(mc.contentType) ? mc.contentType : [mc.contentType]));
|
|
242
245
|
}
|
|
243
246
|
if (!mediaType) {
|
|
244
247
|
const contentTypes = operation.requestBody.content.map(mc => mc.contentType).flat();
|
|
@@ -257,11 +260,12 @@ export class HttpHandler {
|
|
|
257
260
|
throw new MethodNotAllowedError();
|
|
258
261
|
const responseValue = await context.operationHandler.call(context.controllerInstance, context);
|
|
259
262
|
const { response } = context;
|
|
260
|
-
if (!response.writableEnded)
|
|
263
|
+
if (!response.writableEnded) {
|
|
261
264
|
await this._sendResponse(context, responseValue).finally(() => {
|
|
262
265
|
if (!response.writableEnded)
|
|
263
266
|
response.end();
|
|
264
267
|
});
|
|
268
|
+
}
|
|
265
269
|
}
|
|
266
270
|
/**
|
|
267
271
|
*
|
|
@@ -387,8 +391,9 @@ export class HttpHandler {
|
|
|
387
391
|
/** Filter available HttpOperationResponse instances according to status code. */
|
|
388
392
|
const filteredResponses = operation.responses.filter(r => r.statusCode.find(sc => sc.start <= statusCode && sc.end >= statusCode));
|
|
389
393
|
/** Throw InternalServerError if controller returns non-configured status code */
|
|
390
|
-
if (!filteredResponses.length && statusCode < 400)
|
|
394
|
+
if (!filteredResponses.length && statusCode < 400) {
|
|
391
395
|
throw new InternalServerError(`No responses defined for status code ${statusCode} in operation "${operation.name}"`);
|
|
396
|
+
}
|
|
392
397
|
/** We search for content-type in filtered HttpOperationResponse array */
|
|
393
398
|
if (filteredResponses.length) {
|
|
394
399
|
/** If no response returned, and content-type has not been set (No response wants to be returned by operation) */
|
|
@@ -401,8 +406,9 @@ export class HttpHandler {
|
|
|
401
406
|
if (contentType) {
|
|
402
407
|
// Find HttpEndpointResponse instance according to content-type header
|
|
403
408
|
operationResponse = filteredResponses.find(r => typeIs.is(contentType, toArray(r.contentType)));
|
|
404
|
-
if (!operationResponse)
|
|
409
|
+
if (!operationResponse) {
|
|
405
410
|
throw new InternalServerError(`Operation didn't configured to return "${contentType}" content`);
|
|
411
|
+
}
|
|
406
412
|
}
|
|
407
413
|
else {
|
|
408
414
|
/** Select first HttpOperationResponse if content-type header has not been set */
|
|
@@ -434,23 +440,26 @@ export class HttpHandler {
|
|
|
434
440
|
case 'Entity.Get':
|
|
435
441
|
case 'Entity.FindMany':
|
|
436
442
|
case 'Entity.Update': {
|
|
437
|
-
if (!(body instanceof OperationResult))
|
|
443
|
+
if (!(body instanceof OperationResult)) {
|
|
438
444
|
body = new OperationResult({
|
|
439
445
|
payload: body,
|
|
440
446
|
});
|
|
447
|
+
}
|
|
441
448
|
if ((composition === 'Entity.Create' || composition === 'Entity.Update') &&
|
|
442
449
|
composition &&
|
|
443
|
-
body.affected == null)
|
|
450
|
+
body.affected == null) {
|
|
444
451
|
body.affected = 1;
|
|
452
|
+
}
|
|
445
453
|
break;
|
|
446
454
|
}
|
|
447
455
|
case 'Entity.Delete':
|
|
448
456
|
case 'Entity.DeleteMany':
|
|
449
457
|
case 'Entity.UpdateMany': {
|
|
450
|
-
if (!(body instanceof OperationResult))
|
|
458
|
+
if (!(body instanceof OperationResult)) {
|
|
451
459
|
body = new OperationResult({
|
|
452
460
|
affected: body,
|
|
453
461
|
});
|
|
462
|
+
}
|
|
454
463
|
body.affected =
|
|
455
464
|
typeof body.affected === 'number'
|
|
456
465
|
? body.affected
|
|
@@ -461,15 +470,19 @@ export class HttpHandler {
|
|
|
461
470
|
: undefined;
|
|
462
471
|
break;
|
|
463
472
|
}
|
|
473
|
+
default:
|
|
474
|
+
break;
|
|
464
475
|
}
|
|
465
476
|
}
|
|
466
|
-
if (responseArgs.contentType && responseArgs.contentType !== parsedContentType?.type)
|
|
477
|
+
if (responseArgs.contentType && responseArgs.contentType !== parsedContentType?.type) {
|
|
467
478
|
response.setHeader('content-type', responseArgs.contentType);
|
|
479
|
+
}
|
|
468
480
|
if (responseArgs.contentType &&
|
|
469
481
|
body != null &&
|
|
470
482
|
!(body instanceof OperationResult) &&
|
|
471
|
-
typeIs.is(responseArgs.contentType, [MimeTypes.opra_response_json]))
|
|
483
|
+
typeIs.is(responseArgs.contentType, [MimeTypes.opra_response_json])) {
|
|
472
484
|
body = new OperationResult({ payload: body });
|
|
485
|
+
}
|
|
473
486
|
if (hasBody)
|
|
474
487
|
responseArgs.body = body;
|
|
475
488
|
return responseArgs;
|
|
@@ -482,12 +495,13 @@ export class HttpHandler {
|
|
|
482
495
|
const { searchParams } = url;
|
|
483
496
|
const documentId = searchParams.get('id');
|
|
484
497
|
const doc = documentId ? document.findDocument(documentId) : document;
|
|
485
|
-
if (!doc)
|
|
498
|
+
if (!doc) {
|
|
486
499
|
return this.sendErrorResponse(response, [
|
|
487
500
|
new BadRequestError({
|
|
488
501
|
message: `Document with given id [${documentId}] does not exists`,
|
|
489
502
|
}),
|
|
490
503
|
]);
|
|
504
|
+
}
|
|
491
505
|
/** Check if response cache exists */
|
|
492
506
|
let responseBody = this[kAssetCache].get(doc, `$schema`);
|
|
493
507
|
/** Create response if response cache does not exists */
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
Some parts of this file contains codes from open source express library
|
|
3
3
|
https://github.com/expressjs
|
|
4
4
|
*/
|
|
5
|
+
import typeIs from '@browsery/type-is';
|
|
5
6
|
import accepts from 'accepts';
|
|
6
7
|
import fresh from 'fresh';
|
|
7
8
|
import parseRange from 'range-parser';
|
|
8
|
-
import typeIs from '@browsery/type-is';
|
|
9
9
|
import { BodyReader } from '../utils/body-reader.js';
|
|
10
10
|
export class HttpIncomingHost {
|
|
11
11
|
get protocol() {
|
|
@@ -37,11 +37,11 @@ export class HttpIncomingHost {
|
|
|
37
37
|
get fresh() {
|
|
38
38
|
const method = this.method;
|
|
39
39
|
// GET or HEAD for weak freshness validation only
|
|
40
|
-
if ('GET'
|
|
40
|
+
if (method !== 'GET' && method !== 'HEAD')
|
|
41
41
|
return false;
|
|
42
42
|
const status = this.res?.statusCode;
|
|
43
43
|
// 2xx or 304 as per rfc2616 14.26
|
|
44
|
-
if ((status >= 200 && status < 300) ||
|
|
44
|
+
if ((status >= 200 && status < 300) || status === 304) {
|
|
45
45
|
return fresh(this.headers, {
|
|
46
46
|
etag: this.res.getHeader('ETag'),
|
|
47
47
|
'last-modified': this.res.getHeader('Last-Modified'),
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Some parts of this file contains codes from open source express library
|
|
3
3
|
https://github.com/expressjs
|
|
4
4
|
*/
|
|
5
|
+
import { HttpStatusCode } from '@opra/common';
|
|
5
6
|
import contentDisposition from 'content-disposition';
|
|
6
7
|
import contentType from 'content-type';
|
|
7
8
|
import cookie from 'cookie';
|
|
@@ -11,7 +12,6 @@ import mime from 'mime-types';
|
|
|
11
12
|
import path from 'path';
|
|
12
13
|
import { toString } from 'putil-varhelpers';
|
|
13
14
|
import vary from 'vary';
|
|
14
|
-
import { HttpStatusCode } from '@opra/common';
|
|
15
15
|
const charsetRegExp = /;\s*charset\s*=/;
|
|
16
16
|
export class HttpOutgoingHost {
|
|
17
17
|
attachment(filename) {
|
|
@@ -160,7 +160,7 @@ export class HttpOutgoingHost {
|
|
|
160
160
|
if (req?.fresh)
|
|
161
161
|
this.statusCode = 304;
|
|
162
162
|
// strip irrelevant headers
|
|
163
|
-
if (
|
|
163
|
+
if (this.statusCode === 204 || this.statusCode === 304) {
|
|
164
164
|
this.removeHeader('Content-Type');
|
|
165
165
|
this.removeHeader('Content-Length');
|
|
166
166
|
this.removeHeader('Transfer-Encoding');
|
|
@@ -12,9 +12,7 @@ export class MultipartReader extends EventEmitter {
|
|
|
12
12
|
this._incoming = incoming;
|
|
13
13
|
const form = (this._form = formidable({
|
|
14
14
|
...options,
|
|
15
|
-
filter: (part) =>
|
|
16
|
-
return !this._cancelled && (!options?.filter || options.filter(part));
|
|
17
|
-
},
|
|
15
|
+
filter: (part) => !this._cancelled && (!options?.filter || options.filter(part)),
|
|
18
16
|
}));
|
|
19
17
|
form.once('error', () => {
|
|
20
18
|
this._cancelled = true;
|
|
@@ -69,7 +67,7 @@ export class MultipartReader extends EventEmitter {
|
|
|
69
67
|
}
|
|
70
68
|
resume() {
|
|
71
69
|
if (!this._started)
|
|
72
|
-
this._form.parse(this._incoming, () =>
|
|
70
|
+
this._form.parse(this._incoming, () => undefined);
|
|
73
71
|
if (this._form.req)
|
|
74
72
|
this._form.resume();
|
|
75
73
|
}
|
|
@@ -88,12 +86,8 @@ export class MultipartReader extends EventEmitter {
|
|
|
88
86
|
return resolve();
|
|
89
87
|
file._writeStream.once('close', resolve);
|
|
90
88
|
})
|
|
91
|
-
.then(() =>
|
|
92
|
-
|
|
93
|
-
})
|
|
94
|
-
.then(() => {
|
|
95
|
-
return 0;
|
|
96
|
-
}));
|
|
89
|
+
.then(() => fs.unlink(file.filepath))
|
|
90
|
+
.then(() => 0));
|
|
97
91
|
});
|
|
98
92
|
return Promise.allSettled(promises);
|
|
99
93
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Duplex, Readable } from 'stream';
|
|
2
1
|
import { isAsyncIterable, isIterable } from '@opra/common';
|
|
2
|
+
import { Duplex, Readable } from 'stream';
|
|
3
3
|
import { convertToHeaders, convertToHeadersDistinct } from '../utils/convert-to-headers.js';
|
|
4
4
|
import { convertToRawHeaders } from '../utils/convert-to-raw-headers.js';
|
|
5
5
|
export const CRLF = Buffer.from('\r\n');
|
|
@@ -33,10 +33,12 @@ export class NodeIncomingMessageHost extends Duplex {
|
|
|
33
33
|
else
|
|
34
34
|
this.body = Buffer.from(JSON.stringify(init.body), 'utf-8');
|
|
35
35
|
}
|
|
36
|
-
if (init.headers)
|
|
36
|
+
if (init.headers) {
|
|
37
37
|
this.rawHeaders = Array.isArray(init.headers) ? init.headers : convertToRawHeaders(init.headers);
|
|
38
|
-
|
|
38
|
+
}
|
|
39
|
+
if (init.trailers) {
|
|
39
40
|
this.rawTrailers = Array.isArray(init.trailers) ? init.trailers : convertToRawHeaders(init.trailers);
|
|
41
|
+
}
|
|
40
42
|
this.ip = init.ip || '';
|
|
41
43
|
this.ips = init.ips || (this.ip ? [this.ip] : []);
|
|
42
44
|
if (this.body && !this.headers['content-length'])
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Readable } from 'stream';
|
|
2
1
|
import { HTTPParser } from '@browsery/http-parser';
|
|
3
2
|
import { isAsyncIterable, isIterable } from '@opra/common';
|
|
3
|
+
import { Readable } from 'stream';
|
|
4
4
|
import { CRLF, kHttpParser, NodeIncomingMessageHost } from '../impl/node-incoming-message.host.js';
|
|
5
5
|
import { concatReadable } from '../utils/concat-readable.js';
|
|
6
6
|
/**
|
|
@@ -14,8 +14,9 @@ export var NodeIncomingMessage;
|
|
|
14
14
|
* @param iterable
|
|
15
15
|
*/
|
|
16
16
|
function from(iterable) {
|
|
17
|
-
if (typeof iterable === 'object' && !(isIterable(iterable) || isAsyncIterable(iterable)))
|
|
17
|
+
if (typeof iterable === 'object' && !(isIterable(iterable) || isAsyncIterable(iterable))) {
|
|
18
18
|
return new NodeIncomingMessageHost(iterable);
|
|
19
|
+
}
|
|
19
20
|
const msg = new NodeIncomingMessageHost();
|
|
20
21
|
const parser = (msg[kHttpParser] = new HTTPParser(HTTPParser.REQUEST));
|
|
21
22
|
let bodyChunks;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import typeIs from '@browsery/type-is';
|
|
2
|
+
import { BadRequestError, InternalServerError, OpraHttpError } from '@opra/common';
|
|
1
3
|
import { Base64Decode } from 'base64-stream';
|
|
2
4
|
import byteParser from 'bytes';
|
|
3
5
|
import { parse as parseContentType } from 'content-type';
|
|
@@ -5,8 +7,6 @@ import { EventEmitter } from 'events';
|
|
|
5
7
|
import iconv from 'iconv-lite';
|
|
6
8
|
import { Writable } from 'stream';
|
|
7
9
|
import * as zlib from 'zlib';
|
|
8
|
-
import typeIs from '@browsery/type-is';
|
|
9
|
-
import { BadRequestError, InternalServerError, OpraHttpError } from '@opra/common';
|
|
10
10
|
/**
|
|
11
11
|
*
|
|
12
12
|
* @class BodyReader
|
|
@@ -29,11 +29,12 @@ export class BodyReader extends EventEmitter {
|
|
|
29
29
|
}
|
|
30
30
|
async read() {
|
|
31
31
|
/* istanbul ignore next */
|
|
32
|
-
if (this._completed)
|
|
32
|
+
if (this._completed) {
|
|
33
33
|
throw new InternalServerError({
|
|
34
34
|
message: 'Stream already read',
|
|
35
35
|
code: 'STREAM_ALREADY_READ',
|
|
36
36
|
});
|
|
37
|
+
}
|
|
37
38
|
if (!this.req.readable) {
|
|
38
39
|
throw new InternalServerError({
|
|
39
40
|
message: 'Stream is not readable',
|
|
@@ -57,8 +58,9 @@ export class BodyReader extends EventEmitter {
|
|
|
57
58
|
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.3
|
|
58
59
|
*/
|
|
59
60
|
const contentLength = parseInt(this.req.headers['content-length'] || '0', 10);
|
|
60
|
-
if (this.req.headers['transfer-encoding'] === undefined && !(contentLength && !isNaN(contentLength)))
|
|
61
|
+
if (this.req.headers['transfer-encoding'] === undefined && !(contentLength && !isNaN(contentLength))) {
|
|
61
62
|
return this.onEnd();
|
|
63
|
+
}
|
|
62
64
|
// check the length and limit options.
|
|
63
65
|
// note: we intentionally leave the stream paused,
|
|
64
66
|
// so users should handle the stream themselves.
|
package/esm/http/utils/common.js
CHANGED
|
@@ -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
|
}
|
package/esm/index.js
CHANGED
|
@@ -6,20 +6,20 @@ import * as HttpOutgoingHost_ from './http/impl/http-outgoing.host.js';
|
|
|
6
6
|
import * as NodeIncomingMessageHost_ from './http/impl/node-incoming-message.host.js';
|
|
7
7
|
import * as NodeOutgoingMessageHost_ from './http/impl/node-outgoing-message.host.js';
|
|
8
8
|
export * from './execution-context.js';
|
|
9
|
-
export * from './platform-adapter.js';
|
|
10
|
-
export * from './type-guards.js';
|
|
11
9
|
export * from './helpers/logger.js';
|
|
12
10
|
export * from './helpers/service-base.js';
|
|
13
11
|
export * from './http/express-adapter.js';
|
|
14
12
|
export * from './http/http-adapter.js';
|
|
15
13
|
export * from './http/http-context.js';
|
|
14
|
+
export * from './http/impl/multipart-reader.js';
|
|
16
15
|
export * from './http/interfaces/http-incoming.interface.js';
|
|
17
16
|
export * from './http/interfaces/http-outgoing.interface.js';
|
|
18
17
|
export * from './http/interfaces/node-incoming-message.interface.js';
|
|
19
18
|
export * from './http/interfaces/node-outgoing-message.interface.js';
|
|
20
|
-
export * from './http/impl/multipart-reader.js';
|
|
21
19
|
export * from './http/utils/wrap-exception.js';
|
|
22
20
|
export * from './interfaces/logger.interface.js';
|
|
21
|
+
export * from './platform-adapter.js';
|
|
22
|
+
export * from './type-guards.js';
|
|
23
23
|
export var classes;
|
|
24
24
|
(function (classes) {
|
|
25
25
|
classes.HttpIncomingHost = HttpIncomingHost_.HttpIncomingHost;
|
package/esm/platform-adapter.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import './augmentation/18n.augmentation.js';
|
|
2
|
-
import { AsyncEventEmitter } from 'strict-typed-events';
|
|
3
2
|
import { I18n } from '@opra/common';
|
|
3
|
+
import { AsyncEventEmitter } from 'strict-typed-events';
|
|
4
4
|
import { kAssetCache } from './constants.js';
|
|
5
5
|
import { Logger } from './helpers/logger.js';
|
|
6
6
|
import { AssetCache } from './http/impl/asset-cache.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opra/core",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.9",
|
|
4
4
|
"description": "Opra schema package",
|
|
5
5
|
"author": "Panates",
|
|
6
6
|
"license": "MIT",
|
|
@@ -19,8 +19,9 @@
|
|
|
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
|
-
"
|
|
22
|
+
"lint:fix": "eslint . --max-warnings=0 --fix",
|
|
23
23
|
"format": "prettier . --write --log-level=warn",
|
|
24
|
+
"check": "madge --circular src/**",
|
|
24
25
|
"test": "jest --passWithNoTests",
|
|
25
26
|
"cover": "jest --passWithNoTests --collect-coverage",
|
|
26
27
|
"clean": "npm run clean:src && npm run clean:test && npm run clean:dist && npm run clean:cover",
|
|
@@ -30,8 +31,9 @@
|
|
|
30
31
|
"clean:cover": "rimraf ../../coverage/client"
|
|
31
32
|
},
|
|
32
33
|
"dependencies": {
|
|
34
|
+
"@browsery/http-parser": "^0.5.8",
|
|
33
35
|
"@browsery/type-is": "^1.6.18-r2",
|
|
34
|
-
"@opra/common": "^1.0.0-alpha.
|
|
36
|
+
"@opra/common": "^1.0.0-alpha.9",
|
|
35
37
|
"@types/formidable": "^3.4.5",
|
|
36
38
|
"accepts": "^1.3.8",
|
|
37
39
|
"base64-stream": "^1.0.0",
|
|
@@ -44,12 +46,15 @@
|
|
|
44
46
|
"encodeurl": "^2.0.0",
|
|
45
47
|
"formidable": "^3.5.1",
|
|
46
48
|
"fresh": "^0.5.2",
|
|
49
|
+
"iconv-lite": "^0.6.3",
|
|
47
50
|
"mime-types": "^2.1.35",
|
|
48
51
|
"power-tasks": "^1.7.3",
|
|
49
52
|
"putil-isplainobject": "^1.1.5",
|
|
53
|
+
"putil-merge": "^3.12.1",
|
|
50
54
|
"putil-varhelpers": "^1.6.5",
|
|
51
55
|
"range-parser": "^1.2.1",
|
|
52
56
|
"raw-body": "^2.5.2",
|
|
57
|
+
"reflect-metadata": "^0.2.2",
|
|
53
58
|
"strict-typed-events": "^2.3.3",
|
|
54
59
|
"vary": "^1.1.2"
|
|
55
60
|
},
|
|
@@ -76,8 +81,9 @@
|
|
|
76
81
|
"cookie-parser": "^1.4.6",
|
|
77
82
|
"crypto-browserify": "^3.12.0",
|
|
78
83
|
"express": "^4.19.2",
|
|
79
|
-
"fastify": "^4.28.
|
|
84
|
+
"fastify": "^4.28.1",
|
|
80
85
|
"path-browserify": "^1.0.1",
|
|
86
|
+
"supertest": "^7.0.0",
|
|
81
87
|
"ts-gems": "^3.4.0"
|
|
82
88
|
},
|
|
83
89
|
"type": "module",
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { FallbackLng, LanguageResource } from '@opra/common';
|
|
2
1
|
declare module '@opra/common' {
|
|
3
2
|
interface I18n {
|
|
4
3
|
loadResourceDir(dirnames: string | string[], deep?: boolean, overwrite?: boolean): Promise<void>;
|
|
@@ -35,3 +34,4 @@ declare module '@opra/common' {
|
|
|
35
34
|
}
|
|
36
35
|
}
|
|
37
36
|
}
|
|
37
|
+
export {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Application } from 'express';
|
|
2
1
|
import { ApiDocument, HttpController } from '@opra/common';
|
|
2
|
+
import { Application } from 'express';
|
|
3
3
|
import { HttpAdapter } from './http-adapter.js';
|
|
4
4
|
export declare class ExpressAdapter extends HttpAdapter {
|
|
5
5
|
readonly app: Application;
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
/// <reference types="node" />
|
|
3
3
|
/// <reference types="node" />
|
|
4
4
|
/// <reference types="node" />
|
|
5
|
+
import { HTTPParserJS } from '@browsery/http-parser';
|
|
5
6
|
import { IncomingHttpHeaders } from 'http';
|
|
6
7
|
import { Duplex, Readable } from 'stream';
|
|
7
|
-
import { HTTPParserJS } from '@browsery/http-parser';
|
|
8
8
|
import type { NodeIncomingMessage } from '../interfaces/node-incoming-message.interface';
|
|
9
9
|
export declare const CRLF: Buffer;
|
|
10
10
|
export declare const kHeaders: unique symbol;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
/// <reference types="node" />
|
|
3
3
|
/// <reference types="node" />
|
|
4
|
-
import { EventEmitter } from 'events';
|
|
5
4
|
import nodeStream from 'node:stream';
|
|
5
|
+
import { EventEmitter } from 'events';
|
|
6
6
|
import type { HttpIncoming } from '../interfaces/http-incoming.interface.js';
|
|
7
7
|
/**
|
|
8
8
|
*
|
package/types/index.d.ts
CHANGED
|
@@ -6,20 +6,20 @@ import * as HttpOutgoingHost_ from './http/impl/http-outgoing.host.js';
|
|
|
6
6
|
import * as NodeIncomingMessageHost_ from './http/impl/node-incoming-message.host.js';
|
|
7
7
|
import * as NodeOutgoingMessageHost_ from './http/impl/node-outgoing-message.host.js';
|
|
8
8
|
export * from './execution-context.js';
|
|
9
|
-
export * from './platform-adapter.js';
|
|
10
|
-
export * from './type-guards.js';
|
|
11
9
|
export * from './helpers/logger.js';
|
|
12
10
|
export * from './helpers/service-base.js';
|
|
13
11
|
export * from './http/express-adapter.js';
|
|
14
12
|
export * from './http/http-adapter.js';
|
|
15
13
|
export * from './http/http-context.js';
|
|
14
|
+
export * from './http/impl/multipart-reader.js';
|
|
16
15
|
export * from './http/interfaces/http-incoming.interface.js';
|
|
17
16
|
export * from './http/interfaces/http-outgoing.interface.js';
|
|
18
17
|
export * from './http/interfaces/node-incoming-message.interface.js';
|
|
19
18
|
export * from './http/interfaces/node-outgoing-message.interface.js';
|
|
20
|
-
export * from './http/impl/multipart-reader.js';
|
|
21
19
|
export * from './http/utils/wrap-exception.js';
|
|
22
20
|
export * from './interfaces/logger.interface.js';
|
|
21
|
+
export * from './platform-adapter.js';
|
|
22
|
+
export * from './type-guards.js';
|
|
23
23
|
export declare namespace classes {
|
|
24
24
|
export import HttpIncomingHost = HttpIncomingHost_.HttpIncomingHost;
|
|
25
25
|
export import HttpOutgoingHost = HttpOutgoingHost_.HttpOutgoingHost;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import './augmentation/18n.augmentation.js';
|
|
2
|
-
import { AsyncEventEmitter } from 'strict-typed-events';
|
|
3
2
|
import { ApiDocument, I18n, OpraSchema } from '@opra/common';
|
|
3
|
+
import { AsyncEventEmitter } from 'strict-typed-events';
|
|
4
4
|
import { kAssetCache } from './constants.js';
|
|
5
5
|
import { Logger } from './helpers/logger.js';
|
|
6
6
|
import { AssetCache } from './http/impl/asset-cache.js';
|