@opra/core 1.0.0-alpha.6 → 1.0.0-alpha.8
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 +7 -12
- package/cjs/http/http-context.js +5 -3
- package/cjs/http/impl/http-handler.js +54 -41
- 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 +7 -12
- package/esm/http/http-context.js +5 -3
- package/esm/http/impl/http-handler.js +54 -41
- 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'),
|
|
@@ -72,11 +73,10 @@ export class HttpHandler {
|
|
|
72
73
|
e = wrapException(e);
|
|
73
74
|
response.status(e.statusCode || e.status || HttpStatusCode.INTERNAL_SERVER_ERROR);
|
|
74
75
|
response.contentType(MimeTypes.opra_response_json);
|
|
75
|
-
await this._sendResponse(context, new OperationResult({ errors: [e] })).finally(() => {
|
|
76
|
+
await this._sendResponse(context, new OperationResult({ errors: [e.toJSON()] })).finally(() => {
|
|
76
77
|
if (!response.finished)
|
|
77
78
|
response.end();
|
|
78
79
|
});
|
|
79
|
-
// if (!outgoing.writableEnded) await this._sendErrorResponse(context.response, [error]);
|
|
80
80
|
}
|
|
81
81
|
finally {
|
|
82
82
|
await context.emitAsync('finish');
|
|
@@ -109,10 +109,10 @@ export class HttpHandler {
|
|
|
109
109
|
*/
|
|
110
110
|
async _parseParameters(context) {
|
|
111
111
|
const { operation, request } = context;
|
|
112
|
-
let
|
|
112
|
+
let key = '';
|
|
113
113
|
try {
|
|
114
114
|
const onFail = (issue) => {
|
|
115
|
-
issue.location =
|
|
115
|
+
issue.location = key;
|
|
116
116
|
return issue;
|
|
117
117
|
};
|
|
118
118
|
/** prepare decoders */
|
|
@@ -127,9 +127,9 @@ export class HttpHandler {
|
|
|
127
127
|
const paramsLeft = new Set([...operation.parameters, ...operation.owner.parameters]);
|
|
128
128
|
/** parse cookie parameters */
|
|
129
129
|
if (request.cookies) {
|
|
130
|
-
for (
|
|
131
|
-
const oprPrm = operation.findParameter(
|
|
132
|
-
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');
|
|
133
133
|
const prm = oprPrm || cntPrm;
|
|
134
134
|
if (!prm)
|
|
135
135
|
continue;
|
|
@@ -138,16 +138,17 @@ export class HttpHandler {
|
|
|
138
138
|
if (cntPrm)
|
|
139
139
|
paramsLeft.delete(cntPrm);
|
|
140
140
|
const decode = getDecoder(prm);
|
|
141
|
-
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;
|
|
142
143
|
if (v !== undefined)
|
|
143
144
|
context.cookies[prmName] = v;
|
|
144
145
|
}
|
|
145
146
|
}
|
|
146
147
|
/** parse headers */
|
|
147
148
|
if (request.headers) {
|
|
148
|
-
for (
|
|
149
|
-
const oprPrm = operation.findParameter(
|
|
150
|
-
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');
|
|
151
152
|
const prm = oprPrm || cntPrm;
|
|
152
153
|
if (!prm)
|
|
153
154
|
continue;
|
|
@@ -156,16 +157,17 @@ export class HttpHandler {
|
|
|
156
157
|
if (cntPrm)
|
|
157
158
|
paramsLeft.delete(cntPrm);
|
|
158
159
|
const decode = getDecoder(prm);
|
|
159
|
-
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;
|
|
160
162
|
if (v !== undefined)
|
|
161
163
|
context.headers[prmName] = v;
|
|
162
164
|
}
|
|
163
165
|
}
|
|
164
166
|
/** parse path parameters */
|
|
165
167
|
if (request.params) {
|
|
166
|
-
for (
|
|
167
|
-
const oprPrm = operation.findParameter(
|
|
168
|
-
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');
|
|
169
171
|
const prm = oprPrm || cntPrm;
|
|
170
172
|
if (!prm)
|
|
171
173
|
continue;
|
|
@@ -174,17 +176,17 @@ export class HttpHandler {
|
|
|
174
176
|
if (cntPrm)
|
|
175
177
|
paramsLeft.delete(cntPrm);
|
|
176
178
|
const decode = getDecoder(prm);
|
|
177
|
-
const v = decode(request.params[
|
|
179
|
+
const v = decode(request.params[key], { coerce: true, label: key, onFail });
|
|
178
180
|
if (v !== undefined)
|
|
179
|
-
context.pathParams[
|
|
181
|
+
context.pathParams[key] = v;
|
|
180
182
|
}
|
|
181
183
|
}
|
|
182
184
|
/** parse query parameters */
|
|
183
185
|
const url = new URL(request.originalUrl || request.url || '/', 'http://tempuri.org');
|
|
184
186
|
const { searchParams } = url;
|
|
185
|
-
for (
|
|
186
|
-
const oprPrm = operation.findParameter(
|
|
187
|
-
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');
|
|
188
190
|
const prm = oprPrm || cntPrm;
|
|
189
191
|
if (!prm)
|
|
190
192
|
continue;
|
|
@@ -193,15 +195,16 @@ export class HttpHandler {
|
|
|
193
195
|
if (cntPrm)
|
|
194
196
|
paramsLeft.delete(cntPrm);
|
|
195
197
|
const decode = getDecoder(prm);
|
|
196
|
-
let values = searchParams?.getAll(
|
|
198
|
+
let values = searchParams?.getAll(key);
|
|
199
|
+
const prmName = typeof prm.name === 'string' ? prm.name : key;
|
|
197
200
|
if (values?.length && prm.isArray) {
|
|
198
201
|
values = values.map(v => splitString(v, { delimiters: prm.arraySeparator, quotes: true })).flat();
|
|
199
|
-
values = values.map(v => decode(v, { coerce: true, label:
|
|
202
|
+
values = values.map(v => decode(v, { coerce: true, label: key, onFail }));
|
|
200
203
|
if (values.length)
|
|
201
204
|
context.queryParams[prmName] = values;
|
|
202
205
|
}
|
|
203
206
|
else {
|
|
204
|
-
const v = decode(values[0], { coerce: true, label:
|
|
207
|
+
const v = decode(values[0], { coerce: true, label: key, onFail });
|
|
205
208
|
if (values.length)
|
|
206
209
|
context.queryParams[prmName] = v;
|
|
207
210
|
}
|
|
@@ -216,8 +219,8 @@ export class HttpHandler {
|
|
|
216
219
|
}
|
|
217
220
|
catch (e) {
|
|
218
221
|
if (e instanceof ValidationError) {
|
|
219
|
-
|
|
220
|
-
message: `Invalid parameter (${
|
|
222
|
+
throw new BadRequestError({
|
|
223
|
+
message: `Invalid parameter (${key}) value. ` + e.message,
|
|
221
224
|
code: 'REQUEST_VALIDATION',
|
|
222
225
|
details: e.issues,
|
|
223
226
|
}, e);
|
|
@@ -237,9 +240,8 @@ export class HttpHandler {
|
|
|
237
240
|
let contentType = request.header('content-type');
|
|
238
241
|
if (contentType) {
|
|
239
242
|
contentType = parseContentType(contentType).type;
|
|
240
|
-
mediaType = operation.requestBody.content.find(mc =>
|
|
241
|
-
|
|
242
|
-
});
|
|
243
|
+
mediaType = operation.requestBody.content.find(mc => mc.contentType &&
|
|
244
|
+
typeIs.is(contentType, Array.isArray(mc.contentType) ? mc.contentType : [mc.contentType]));
|
|
243
245
|
}
|
|
244
246
|
if (!mediaType) {
|
|
245
247
|
const contentTypes = operation.requestBody.content.map(mc => mc.contentType).flat();
|
|
@@ -258,11 +260,12 @@ export class HttpHandler {
|
|
|
258
260
|
throw new MethodNotAllowedError();
|
|
259
261
|
const responseValue = await context.operationHandler.call(context.controllerInstance, context);
|
|
260
262
|
const { response } = context;
|
|
261
|
-
if (!response.writableEnded)
|
|
263
|
+
if (!response.writableEnded) {
|
|
262
264
|
await this._sendResponse(context, responseValue).finally(() => {
|
|
263
265
|
if (!response.writableEnded)
|
|
264
266
|
response.end();
|
|
265
267
|
});
|
|
268
|
+
}
|
|
266
269
|
}
|
|
267
270
|
/**
|
|
268
271
|
*
|
|
@@ -388,8 +391,9 @@ export class HttpHandler {
|
|
|
388
391
|
/** Filter available HttpOperationResponse instances according to status code. */
|
|
389
392
|
const filteredResponses = operation.responses.filter(r => r.statusCode.find(sc => sc.start <= statusCode && sc.end >= statusCode));
|
|
390
393
|
/** Throw InternalServerError if controller returns non-configured status code */
|
|
391
|
-
if (!filteredResponses.length && statusCode < 400)
|
|
394
|
+
if (!filteredResponses.length && statusCode < 400) {
|
|
392
395
|
throw new InternalServerError(`No responses defined for status code ${statusCode} in operation "${operation.name}"`);
|
|
396
|
+
}
|
|
393
397
|
/** We search for content-type in filtered HttpOperationResponse array */
|
|
394
398
|
if (filteredResponses.length) {
|
|
395
399
|
/** If no response returned, and content-type has not been set (No response wants to be returned by operation) */
|
|
@@ -402,8 +406,9 @@ export class HttpHandler {
|
|
|
402
406
|
if (contentType) {
|
|
403
407
|
// Find HttpEndpointResponse instance according to content-type header
|
|
404
408
|
operationResponse = filteredResponses.find(r => typeIs.is(contentType, toArray(r.contentType)));
|
|
405
|
-
if (!operationResponse)
|
|
409
|
+
if (!operationResponse) {
|
|
406
410
|
throw new InternalServerError(`Operation didn't configured to return "${contentType}" content`);
|
|
411
|
+
}
|
|
407
412
|
}
|
|
408
413
|
else {
|
|
409
414
|
/** Select first HttpOperationResponse if content-type header has not been set */
|
|
@@ -435,23 +440,26 @@ export class HttpHandler {
|
|
|
435
440
|
case 'Entity.Get':
|
|
436
441
|
case 'Entity.FindMany':
|
|
437
442
|
case 'Entity.Update': {
|
|
438
|
-
if (!(body instanceof OperationResult))
|
|
443
|
+
if (!(body instanceof OperationResult)) {
|
|
439
444
|
body = new OperationResult({
|
|
440
445
|
payload: body,
|
|
441
446
|
});
|
|
447
|
+
}
|
|
442
448
|
if ((composition === 'Entity.Create' || composition === 'Entity.Update') &&
|
|
443
449
|
composition &&
|
|
444
|
-
body.affected == null)
|
|
450
|
+
body.affected == null) {
|
|
445
451
|
body.affected = 1;
|
|
452
|
+
}
|
|
446
453
|
break;
|
|
447
454
|
}
|
|
448
455
|
case 'Entity.Delete':
|
|
449
456
|
case 'Entity.DeleteMany':
|
|
450
457
|
case 'Entity.UpdateMany': {
|
|
451
|
-
if (!(body instanceof OperationResult))
|
|
458
|
+
if (!(body instanceof OperationResult)) {
|
|
452
459
|
body = new OperationResult({
|
|
453
460
|
affected: body,
|
|
454
461
|
});
|
|
462
|
+
}
|
|
455
463
|
body.affected =
|
|
456
464
|
typeof body.affected === 'number'
|
|
457
465
|
? body.affected
|
|
@@ -462,15 +470,19 @@ export class HttpHandler {
|
|
|
462
470
|
: undefined;
|
|
463
471
|
break;
|
|
464
472
|
}
|
|
473
|
+
default:
|
|
474
|
+
break;
|
|
465
475
|
}
|
|
466
476
|
}
|
|
467
|
-
if (responseArgs.contentType && responseArgs.contentType !== parsedContentType?.type)
|
|
477
|
+
if (responseArgs.contentType && responseArgs.contentType !== parsedContentType?.type) {
|
|
468
478
|
response.setHeader('content-type', responseArgs.contentType);
|
|
479
|
+
}
|
|
469
480
|
if (responseArgs.contentType &&
|
|
470
481
|
body != null &&
|
|
471
482
|
!(body instanceof OperationResult) &&
|
|
472
|
-
typeIs.is(responseArgs.contentType, [MimeTypes.opra_response_json]))
|
|
483
|
+
typeIs.is(responseArgs.contentType, [MimeTypes.opra_response_json])) {
|
|
473
484
|
body = new OperationResult({ payload: body });
|
|
485
|
+
}
|
|
474
486
|
if (hasBody)
|
|
475
487
|
responseArgs.body = body;
|
|
476
488
|
return responseArgs;
|
|
@@ -483,12 +495,13 @@ export class HttpHandler {
|
|
|
483
495
|
const { searchParams } = url;
|
|
484
496
|
const documentId = searchParams.get('id');
|
|
485
497
|
const doc = documentId ? document.findDocument(documentId) : document;
|
|
486
|
-
if (!doc)
|
|
498
|
+
if (!doc) {
|
|
487
499
|
return this.sendErrorResponse(response, [
|
|
488
500
|
new BadRequestError({
|
|
489
501
|
message: `Document with given id [${documentId}] does not exists`,
|
|
490
502
|
}),
|
|
491
503
|
]);
|
|
504
|
+
}
|
|
492
505
|
/** Check if response cache exists */
|
|
493
506
|
let responseBody = this[kAssetCache].get(doc, `$schema`);
|
|
494
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.8",
|
|
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.8",
|
|
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;
|