@opra/core 1.0.0-alpha.3 → 1.0.0-alpha.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cjs/augmentation/18n.augmentation.js +1 -1
- package/cjs/constants.js +1 -2
- package/cjs/execution-context.js +1 -1
- package/cjs/http/express-adapter.js +25 -34
- package/cjs/http/http-adapter.js +2 -5
- package/cjs/http/http-context.js +20 -32
- package/cjs/http/{impl/http-handler.js → http-handler.js} +249 -213
- package/cjs/http/impl/http-incoming.host.js +3 -3
- package/cjs/http/impl/http-outgoing.host.js +2 -2
- package/cjs/http/impl/multipart-reader.js +141 -50
- package/cjs/http/impl/node-incoming-message.host.js +5 -3
- package/cjs/http/interfaces/node-incoming-message.interface.js +3 -2
- package/cjs/http/utils/body-reader.js +6 -5
- package/cjs/http/utils/common.js +6 -5
- package/cjs/http/utils/concat-readable.js +1 -2
- package/cjs/http/utils/convert-to-headers.js +2 -3
- package/cjs/http/utils/convert-to-raw-headers.js +1 -2
- package/cjs/http/utils/match-known-fields.js +2 -2
- package/cjs/http/utils/wrap-exception.js +1 -2
- package/cjs/index.js +4 -4
- package/cjs/platform-adapter.js +1 -4
- package/cjs/type-guards.js +4 -5
- package/esm/augmentation/18n.augmentation.js +1 -1
- package/esm/constants.js +0 -1
- package/esm/execution-context.js +1 -1
- package/esm/http/express-adapter.js +25 -34
- package/esm/http/http-adapter.js +2 -5
- package/esm/http/http-context.js +21 -33
- package/esm/http/{impl/http-handler.js → http-handler.js} +243 -207
- package/esm/http/impl/http-incoming.host.js +3 -3
- package/esm/http/impl/http-outgoing.host.js +2 -2
- package/esm/http/impl/multipart-reader.js +142 -51
- package/esm/http/impl/node-incoming-message.host.js +5 -3
- package/esm/http/interfaces/node-incoming-message.interface.js +3 -2
- package/esm/http/utils/body-reader.js +6 -5
- package/esm/http/utils/common.js +2 -1
- package/esm/index.js +4 -4
- package/esm/platform-adapter.js +1 -4
- package/package.json +21 -14
- package/types/augmentation/18n.augmentation.d.ts +1 -1
- package/types/constants.d.ts +0 -1
- package/types/execution-context.d.ts +2 -3
- package/types/http/express-adapter.d.ts +1 -1
- package/types/http/http-adapter.d.ts +35 -8
- package/types/http/http-context.d.ts +4 -4
- package/types/http/{impl/http-handler.d.ts → http-handler.d.ts} +11 -9
- package/types/http/impl/http-incoming.host.d.ts +1 -2
- package/types/http/impl/http-outgoing.host.d.ts +1 -1
- package/types/http/impl/multipart-reader.d.ts +38 -20
- package/types/http/impl/node-incoming-message.host.d.ts +2 -6
- package/types/http/impl/node-outgoing-message.host.d.ts +4 -7
- package/types/http/interfaces/http-incoming.interface.d.ts +1 -2
- package/types/http/interfaces/http-outgoing.interface.d.ts +1 -1
- package/types/http/interfaces/node-incoming-message.interface.d.ts +0 -2
- package/types/http/interfaces/node-outgoing-message.interface.d.ts +0 -2
- package/types/http/utils/body-reader.d.ts +2 -5
- package/types/http/utils/concat-readable.d.ts +0 -1
- package/types/http/utils/convert-to-raw-headers.d.ts +0 -1
- package/types/index.d.ts +4 -4
- package/types/platform-adapter.d.ts +1 -5
- package/cjs/helpers/logger.js +0 -35
- package/esm/helpers/logger.js +0 -31
- package/types/helpers/logger.d.ts +0 -14
|
@@ -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');
|
|
@@ -1,57 +1,155 @@
|
|
|
1
|
+
import { randomFillSync } from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import nodePath from 'node:path';
|
|
5
|
+
import typeIs from '@browsery/type-is';
|
|
6
|
+
import { BadRequestError } from '@opra/common';
|
|
7
|
+
import busboy from 'busboy';
|
|
1
8
|
import { EventEmitter } from 'events';
|
|
2
|
-
import
|
|
3
|
-
import
|
|
9
|
+
import fsPromise from 'fs/promises';
|
|
10
|
+
import { isNotNullish } from 'valgen';
|
|
4
11
|
export class MultipartReader extends EventEmitter {
|
|
5
|
-
constructor(
|
|
12
|
+
constructor(context, options, mediaType) {
|
|
6
13
|
super();
|
|
14
|
+
this.context = context;
|
|
15
|
+
this.mediaType = mediaType;
|
|
7
16
|
this._started = false;
|
|
17
|
+
this._finished = false;
|
|
8
18
|
this._cancelled = false;
|
|
9
19
|
this._items = [];
|
|
10
20
|
this._stack = [];
|
|
11
21
|
this.setMaxListeners(1000);
|
|
12
|
-
this.
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
},
|
|
18
|
-
}));
|
|
19
|
-
form.once('error', () => {
|
|
22
|
+
this.tempDirectory = options?.tempDirectory || os.tmpdir();
|
|
23
|
+
const { request } = context;
|
|
24
|
+
const form = busboy({ headers: request.headers });
|
|
25
|
+
this._form = form;
|
|
26
|
+
form.once('error', (e) => {
|
|
20
27
|
this._cancelled = true;
|
|
28
|
+
this._finished = true;
|
|
21
29
|
if (this.listenerCount('error') > 0)
|
|
22
|
-
this.emit('error');
|
|
30
|
+
this.emit('error', e);
|
|
23
31
|
});
|
|
24
|
-
form.on('
|
|
25
|
-
|
|
32
|
+
form.on('close', () => {
|
|
33
|
+
this._finished = true;
|
|
34
|
+
});
|
|
35
|
+
form.on('field', (field, value, info) => {
|
|
36
|
+
const item = {
|
|
37
|
+
kind: 'field',
|
|
38
|
+
field,
|
|
39
|
+
value,
|
|
40
|
+
mimeType: info.mimeType,
|
|
41
|
+
encoding: info.encoding,
|
|
42
|
+
};
|
|
26
43
|
this._items.push(item);
|
|
27
44
|
this._stack.push(item);
|
|
28
45
|
this.emit('field', item);
|
|
29
46
|
this.emit('item', item);
|
|
30
47
|
});
|
|
31
|
-
form.on('file', (
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
48
|
+
form.on('file', (field, file, info) => {
|
|
49
|
+
const saveTo = nodePath.join(this.tempDirectory, `opra-${generateFileName()}`);
|
|
50
|
+
file.pipe(fs.createWriteStream(saveTo));
|
|
51
|
+
file.once('end', () => {
|
|
52
|
+
const item = {
|
|
53
|
+
kind: 'file',
|
|
54
|
+
field,
|
|
55
|
+
storedPath: saveTo,
|
|
56
|
+
filename: info.filename,
|
|
57
|
+
mimeType: info.mimeType,
|
|
58
|
+
encoding: info.encoding,
|
|
59
|
+
};
|
|
60
|
+
this._items.push(item);
|
|
61
|
+
this._stack.push(item);
|
|
62
|
+
this.emit('file', item);
|
|
63
|
+
this.emit('item', item);
|
|
64
|
+
});
|
|
37
65
|
});
|
|
38
66
|
}
|
|
39
67
|
get items() {
|
|
40
68
|
return this._items;
|
|
41
69
|
}
|
|
42
|
-
getNext() {
|
|
43
|
-
|
|
70
|
+
async getNext() {
|
|
71
|
+
let item = this._stack.shift();
|
|
72
|
+
if (!item && !this._finished) {
|
|
44
73
|
this.resume();
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
74
|
+
item = await new Promise((resolve, reject) => {
|
|
75
|
+
let resolved = false;
|
|
76
|
+
if (this._stack.length)
|
|
77
|
+
return resolve(this._stack.shift());
|
|
78
|
+
if (this._form.ended)
|
|
79
|
+
return resolve(undefined);
|
|
80
|
+
this._form.once('close', () => {
|
|
81
|
+
if (resolved)
|
|
82
|
+
return;
|
|
83
|
+
resolved = true;
|
|
84
|
+
resolve(this._stack.shift());
|
|
85
|
+
});
|
|
86
|
+
this.once('item', () => {
|
|
87
|
+
this.pause();
|
|
88
|
+
if (resolved)
|
|
89
|
+
return;
|
|
90
|
+
resolved = true;
|
|
91
|
+
resolve(this._stack.shift());
|
|
92
|
+
});
|
|
93
|
+
this.once('error', e => reject(e));
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
if (item && this.mediaType) {
|
|
97
|
+
const field = this.mediaType.findMultipartField(item.field);
|
|
98
|
+
if (!field)
|
|
99
|
+
throw new BadRequestError(`Unknown multipart field (${item.field})`);
|
|
100
|
+
if (item.kind === 'field') {
|
|
101
|
+
const decode = field.generateCodec('decode', { ignoreReadonlyFields: true, projection: '*' });
|
|
102
|
+
item.value = decode(item.value, {
|
|
103
|
+
onFail: issue => `Multipart field (${item.field}) validation failed: ` + issue.message,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
else if (item.kind === 'file') {
|
|
107
|
+
if (field.contentType) {
|
|
108
|
+
const arr = Array.isArray(field.contentType) ? field.contentType : [field.contentType];
|
|
109
|
+
if (!(item.mimeType && arr.find(ct => typeIs.is(item.mimeType, [ct])))) {
|
|
110
|
+
throw new BadRequestError(`Multipart field (${item.field}) do not accept this content type`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/** if all items received we check for required items */
|
|
116
|
+
if (this._finished && this.mediaType && this.mediaType.multipartFields?.length > 0) {
|
|
117
|
+
const fieldsLeft = new Set(this.mediaType.multipartFields);
|
|
118
|
+
for (const x of this._items) {
|
|
119
|
+
const field = this.mediaType.findMultipartField(x.field);
|
|
120
|
+
if (field)
|
|
121
|
+
fieldsLeft.delete(field);
|
|
122
|
+
}
|
|
123
|
+
let issues;
|
|
124
|
+
for (const field of fieldsLeft) {
|
|
125
|
+
if (!field.required)
|
|
126
|
+
continue;
|
|
127
|
+
try {
|
|
128
|
+
isNotNullish(null, { onFail: () => `Multi part field "${String(field.fieldName)}" is required` });
|
|
129
|
+
}
|
|
130
|
+
catch (e) {
|
|
131
|
+
if (!issues) {
|
|
132
|
+
issues = e.issues;
|
|
133
|
+
this.context.errors.push(e);
|
|
134
|
+
}
|
|
135
|
+
else
|
|
136
|
+
issues.push(...e.issues);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (this.context.errors.length)
|
|
140
|
+
throw this.context.errors[0];
|
|
141
|
+
}
|
|
142
|
+
return item;
|
|
143
|
+
}
|
|
144
|
+
async getAll() {
|
|
145
|
+
const items = [...this._items];
|
|
146
|
+
let item;
|
|
147
|
+
while (!this._cancelled && (item = await this.getNext())) {
|
|
148
|
+
items.push(item);
|
|
149
|
+
}
|
|
150
|
+
return items;
|
|
53
151
|
}
|
|
54
|
-
|
|
152
|
+
getAll_() {
|
|
55
153
|
if (this._form.ended)
|
|
56
154
|
return Promise.resolve([...this._items]);
|
|
57
155
|
this.resume();
|
|
@@ -68,33 +166,26 @@ export class MultipartReader extends EventEmitter {
|
|
|
68
166
|
this.resume();
|
|
69
167
|
}
|
|
70
168
|
resume() {
|
|
71
|
-
if (!this._started)
|
|
72
|
-
this.
|
|
73
|
-
|
|
74
|
-
|
|
169
|
+
if (!this._started) {
|
|
170
|
+
this._started = true;
|
|
171
|
+
this.context.request.pipe(this._form);
|
|
172
|
+
}
|
|
173
|
+
this.context.request.resume();
|
|
75
174
|
}
|
|
76
175
|
pause() {
|
|
77
|
-
|
|
78
|
-
this._form.pause();
|
|
176
|
+
this.context.request.pause();
|
|
79
177
|
}
|
|
80
|
-
async
|
|
178
|
+
async purge() {
|
|
81
179
|
const promises = [];
|
|
82
180
|
this._items.forEach(item => {
|
|
83
|
-
if (
|
|
181
|
+
if (item.kind !== 'file')
|
|
84
182
|
return;
|
|
85
|
-
|
|
86
|
-
promises.push(new Promise(resolve => {
|
|
87
|
-
if (file._writeStream.closed)
|
|
88
|
-
return resolve();
|
|
89
|
-
file._writeStream.once('close', resolve);
|
|
90
|
-
})
|
|
91
|
-
.then(() => {
|
|
92
|
-
return fs.unlink(file.filepath);
|
|
93
|
-
})
|
|
94
|
-
.then(() => {
|
|
95
|
-
return 0;
|
|
96
|
-
}));
|
|
183
|
+
promises.push(fsPromise.unlink(item.storedPath));
|
|
97
184
|
});
|
|
98
185
|
return Promise.allSettled(promises);
|
|
99
186
|
}
|
|
100
187
|
}
|
|
188
|
+
function generateFileName() {
|
|
189
|
+
const buf = Buffer.alloc(10);
|
|
190
|
+
return new Date().toISOString().substring(0, 10).replace(/-/g, '') + randomFillSync(buf).toString('hex');
|
|
191
|
+
}
|
|
@@ -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.
|
|
@@ -143,7 +145,6 @@ export class BodyReader extends EventEmitter {
|
|
|
143
145
|
message: 'request aborted',
|
|
144
146
|
code: 'ECONNABORTED',
|
|
145
147
|
details: {
|
|
146
|
-
length,
|
|
147
148
|
received: this._receivedSize,
|
|
148
149
|
},
|
|
149
150
|
}));
|
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
|
-
export * from './helpers/logger.js';
|
|
12
9
|
export * from './helpers/service-base.js';
|
|
13
10
|
export * from './http/express-adapter.js';
|
|
14
11
|
export * from './http/http-adapter.js';
|
|
15
12
|
export * from './http/http-context.js';
|
|
13
|
+
export * from './http/http-handler.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,8 +1,7 @@
|
|
|
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
|
-
import { Logger } from './helpers/logger.js';
|
|
6
5
|
import { AssetCache } from './http/impl/asset-cache.js';
|
|
7
6
|
/**
|
|
8
7
|
* @class PlatformAdapter
|
|
@@ -12,8 +11,6 @@ export class PlatformAdapter extends AsyncEventEmitter {
|
|
|
12
11
|
super();
|
|
13
12
|
this[kAssetCache] = new AssetCache();
|
|
14
13
|
this.document = document;
|
|
15
|
-
this.logger =
|
|
16
|
-
options?.logger && options.logger instanceof Logger ? options.logger : new Logger({ instance: options?.logger });
|
|
17
14
|
this.i18n = options?.i18n || I18n.defaultInstance;
|
|
18
15
|
}
|
|
19
16
|
}
|
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.31",
|
|
4
4
|
"description": "Opra schema package",
|
|
5
5
|
"author": "Panates",
|
|
6
6
|
"license": "MIT",
|
|
@@ -15,14 +15,15 @@
|
|
|
15
15
|
"build": "npm run build:cjs && npm run build:esm",
|
|
16
16
|
"build:cjs": "tsc -b tsconfig-build-cjs.json",
|
|
17
17
|
"build:esm": "tsc -b tsconfig-build-esm.json",
|
|
18
|
-
"postbuild": "npm run
|
|
19
|
-
"
|
|
20
|
-
"
|
|
18
|
+
"postbuild": "npm run postbuild:copy_1 && npm run postbuild:copy_2",
|
|
19
|
+
"postbuild:copy_1": "cp README.md package.json ../../LICENSE ../../build/core && cp ../../package.cjs.json ../../build/core/cjs/package.json",
|
|
20
|
+
"postbuild:copy_2": "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
|
-
"
|
|
25
|
-
"
|
|
24
|
+
"check": "madge --circular src/**",
|
|
25
|
+
"test": "jest --passWithNoTests",
|
|
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",
|
|
27
28
|
"clean:src": "ts-cleanup -s src --all",
|
|
28
29
|
"clean:test": "ts-cleanup -s test --all",
|
|
@@ -30,11 +31,12 @@
|
|
|
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.
|
|
35
|
-
"@types/formidable": "^3.4.5",
|
|
36
|
+
"@opra/common": "^1.0.0-alpha.31",
|
|
36
37
|
"accepts": "^1.3.8",
|
|
37
38
|
"base64-stream": "^1.0.0",
|
|
39
|
+
"busboy": "^1.6.0",
|
|
38
40
|
"bytes": "^3.1.2",
|
|
39
41
|
"content-disposition": "^0.5.4",
|
|
40
42
|
"content-type": "^1.0.5",
|
|
@@ -42,15 +44,18 @@
|
|
|
42
44
|
"cookie-signature": "^1.2.1",
|
|
43
45
|
"cppzst": "^2.0.12",
|
|
44
46
|
"encodeurl": "^2.0.0",
|
|
45
|
-
"formidable": "^3.5.1",
|
|
46
47
|
"fresh": "^0.5.2",
|
|
48
|
+
"iconv-lite": "^0.6.3",
|
|
47
49
|
"mime-types": "^2.1.35",
|
|
48
|
-
"power-tasks": "^1.7.
|
|
50
|
+
"power-tasks": "^1.7.7",
|
|
49
51
|
"putil-isplainobject": "^1.1.5",
|
|
52
|
+
"putil-merge": "^3.13.0",
|
|
50
53
|
"putil-varhelpers": "^1.6.5",
|
|
51
54
|
"range-parser": "^1.2.1",
|
|
52
|
-
"raw-body": "^
|
|
53
|
-
"
|
|
55
|
+
"raw-body": "^3.0.0",
|
|
56
|
+
"reflect-metadata": "^0.2.2",
|
|
57
|
+
"strict-typed-events": "^2.4.0",
|
|
58
|
+
"tslib": "^2.6.3",
|
|
54
59
|
"vary": "^1.1.2"
|
|
55
60
|
},
|
|
56
61
|
"optionalDependencies": {
|
|
@@ -61,6 +66,7 @@
|
|
|
61
66
|
"@faker-js/faker": "^8.4.1",
|
|
62
67
|
"@types/accepts": "^1.3.7",
|
|
63
68
|
"@types/base64-stream": "^1.0.5",
|
|
69
|
+
"@types/busboy": "^1.5.4",
|
|
64
70
|
"@types/bytes": "^3.1.4",
|
|
65
71
|
"@types/content-disposition": "^0.5.8",
|
|
66
72
|
"@types/content-type": "^1.1.8",
|
|
@@ -76,8 +82,9 @@
|
|
|
76
82
|
"cookie-parser": "^1.4.6",
|
|
77
83
|
"crypto-browserify": "^3.12.0",
|
|
78
84
|
"express": "^4.19.2",
|
|
79
|
-
"fastify": "^4.28.
|
|
85
|
+
"fastify": "^4.28.1",
|
|
80
86
|
"path-browserify": "^1.0.1",
|
|
87
|
+
"supertest": "^7.0.0",
|
|
81
88
|
"ts-gems": "^3.4.0"
|
|
82
89
|
},
|
|
83
90
|
"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 {};
|
package/types/constants.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { ApiDocument, OpraHttpError, OpraSchema } from '@opra/common';
|
|
1
2
|
import { AsyncEventEmitter } from 'strict-typed-events';
|
|
2
|
-
import { ApiDocument, OpraSchema } from '@opra/common';
|
|
3
3
|
/**
|
|
4
4
|
* @namespace ExecutionContext
|
|
5
5
|
*/
|
|
@@ -8,7 +8,6 @@ export declare namespace ExecutionContext {
|
|
|
8
8
|
document: ApiDocument;
|
|
9
9
|
protocol: OpraSchema.Protocol;
|
|
10
10
|
platform: string;
|
|
11
|
-
platformArgs: any;
|
|
12
11
|
}
|
|
13
12
|
type OnFinishListener = (error: Error | undefined, context: ExecutionContext) => void | Promise<void>;
|
|
14
13
|
}
|
|
@@ -19,7 +18,7 @@ export declare abstract class ExecutionContext extends AsyncEventEmitter {
|
|
|
19
18
|
readonly document: ApiDocument;
|
|
20
19
|
readonly protocol: OpraSchema.Protocol;
|
|
21
20
|
readonly platform: string;
|
|
22
|
-
|
|
21
|
+
errors: OpraHttpError[];
|
|
23
22
|
protected constructor(init: ExecutionContext.Initiator);
|
|
24
23
|
addListener(event: 'finish', listener: ExecutionContext.OnFinishListener): this;
|
|
25
24
|
removeListener(event: 'finish', listener: ExecutionContext.OnFinishListener): this;
|
|
@@ -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;
|
|
@@ -1,27 +1,54 @@
|
|
|
1
1
|
import { ApiDocument, HttpApi, OpraSchema } from '@opra/common';
|
|
2
|
-
import { kHandler } from '../constants.js';
|
|
3
2
|
import { PlatformAdapter } from '../platform-adapter.js';
|
|
4
3
|
import { HttpContext } from './http-context.js';
|
|
5
|
-
import { HttpHandler } from './
|
|
4
|
+
import { HttpHandler } from './http-handler.js';
|
|
6
5
|
export declare namespace HttpAdapter {
|
|
6
|
+
type NextCallback = () => Promise<void>;
|
|
7
7
|
/**
|
|
8
|
-
* @type
|
|
8
|
+
* @type InterceptorFunction
|
|
9
9
|
*/
|
|
10
|
-
type
|
|
10
|
+
type InterceptorFunction = IHttpInterceptor['intercept'];
|
|
11
|
+
/**
|
|
12
|
+
* @interface IHttpInterceptor
|
|
13
|
+
*/
|
|
14
|
+
type IHttpInterceptor = {
|
|
15
|
+
intercept(context: HttpContext, next: NextCallback): Promise<void>;
|
|
16
|
+
};
|
|
11
17
|
interface Options extends PlatformAdapter.Options {
|
|
12
18
|
basePath?: string;
|
|
13
|
-
interceptors?:
|
|
14
|
-
onRequest?: (ctx: HttpContext) => void | Promise<void>;
|
|
19
|
+
interceptors?: (InterceptorFunction | IHttpInterceptor)[];
|
|
15
20
|
}
|
|
21
|
+
type EventFunction = (context: HttpContext) => void | Promise<void>;
|
|
22
|
+
interface Events {
|
|
23
|
+
createContext: EventFunction;
|
|
24
|
+
error: EventFunction;
|
|
25
|
+
request: EventFunction;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export interface HttpAdapter {
|
|
29
|
+
addListener<Event extends keyof HttpAdapter.Events>(event: Event, listener: HttpAdapter.Events[Event]): this;
|
|
30
|
+
addListener(event: string | symbol, listener: (...args: any[]) => void): this;
|
|
31
|
+
on<Event extends keyof HttpAdapter.Events>(event: Event, listener: HttpAdapter.Events[Event]): this;
|
|
32
|
+
on(event: string | symbol, listener: (...args: any[]) => void): this;
|
|
33
|
+
once<Event extends keyof HttpAdapter.Events>(event: Event, listener: HttpAdapter.Events[Event]): this;
|
|
34
|
+
once(event: string | symbol, listener: (...args: any[]) => void): this;
|
|
35
|
+
removeListener<Event extends keyof HttpAdapter.Events>(event: Event, listener: HttpAdapter.Events[Event]): this;
|
|
36
|
+
removeListener(event: string | symbol, listener: (...args: any[]) => void): this;
|
|
37
|
+
off<Event extends keyof HttpAdapter.Events>(event: Event, listener: HttpAdapter.Events[Event]): this;
|
|
38
|
+
off(event: string | symbol, listener: (...args: any[]) => void): this;
|
|
39
|
+
prependListener<Event extends keyof HttpAdapter.Events>(event: Event, listener: HttpAdapter.Events[Event]): this;
|
|
40
|
+
prependListener(event: string | symbol, listener: (...args: any[]) => void): this;
|
|
41
|
+
prependOnceListener<Event extends keyof HttpAdapter.Events>(event: Event, listener: HttpAdapter.Events[Event]): this;
|
|
42
|
+
prependOnceListener(event: string | symbol, listener: (...args: any[]) => void): this;
|
|
16
43
|
}
|
|
17
44
|
/**
|
|
18
45
|
*
|
|
19
46
|
* @class HttpAdapter
|
|
20
47
|
*/
|
|
21
48
|
export declare abstract class HttpAdapter extends PlatformAdapter {
|
|
22
|
-
|
|
49
|
+
readonly handler: HttpHandler;
|
|
23
50
|
readonly protocol: OpraSchema.Protocol;
|
|
24
|
-
interceptors: HttpAdapter.
|
|
51
|
+
interceptors: (HttpAdapter.InterceptorFunction | HttpAdapter.IHttpInterceptor)[];
|
|
25
52
|
protected constructor(document: ApiDocument, options?: HttpAdapter.Options);
|
|
26
53
|
get api(): HttpApi;
|
|
27
54
|
}
|
|
@@ -2,8 +2,8 @@ import { HttpController, HttpMediaType, HttpOperation, OpraSchema } from '@opra/
|
|
|
2
2
|
import { ExecutionContext } from '../execution-context.js';
|
|
3
3
|
import type { HttpAdapter } from './http-adapter';
|
|
4
4
|
import { MultipartReader } from './impl/multipart-reader.js';
|
|
5
|
-
import type { HttpIncoming } from './interfaces/http-incoming.interface';
|
|
6
|
-
import type { HttpOutgoing } from './interfaces/http-outgoing.interface';
|
|
5
|
+
import type { HttpIncoming } from './interfaces/http-incoming.interface.js';
|
|
6
|
+
import type { HttpOutgoing } from './interfaces/http-outgoing.interface.js';
|
|
7
7
|
export declare namespace HttpContext {
|
|
8
8
|
interface Initiator extends Omit<ExecutionContext.Initiator, 'document' | 'protocol'> {
|
|
9
9
|
adapter: HttpAdapter;
|
|
@@ -26,9 +26,9 @@ export declare class HttpContext extends ExecutionContext {
|
|
|
26
26
|
protected _multipartReader?: MultipartReader;
|
|
27
27
|
readonly protocol: OpraSchema.Protocol;
|
|
28
28
|
readonly adapter: HttpAdapter;
|
|
29
|
-
readonly controller
|
|
29
|
+
readonly controller?: HttpController;
|
|
30
30
|
readonly controllerInstance?: any;
|
|
31
|
-
readonly operation
|
|
31
|
+
readonly operation?: HttpOperation;
|
|
32
32
|
readonly operationHandler?: Function;
|
|
33
33
|
readonly request: HttpIncoming;
|
|
34
34
|
readonly response: HttpOutgoing;
|