@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.
Files changed (63) hide show
  1. package/cjs/augmentation/18n.augmentation.js +1 -1
  2. package/cjs/constants.js +1 -2
  3. package/cjs/execution-context.js +1 -1
  4. package/cjs/http/express-adapter.js +25 -34
  5. package/cjs/http/http-adapter.js +2 -5
  6. package/cjs/http/http-context.js +20 -32
  7. package/cjs/http/{impl/http-handler.js → http-handler.js} +249 -213
  8. package/cjs/http/impl/http-incoming.host.js +3 -3
  9. package/cjs/http/impl/http-outgoing.host.js +2 -2
  10. package/cjs/http/impl/multipart-reader.js +141 -50
  11. package/cjs/http/impl/node-incoming-message.host.js +5 -3
  12. package/cjs/http/interfaces/node-incoming-message.interface.js +3 -2
  13. package/cjs/http/utils/body-reader.js +6 -5
  14. package/cjs/http/utils/common.js +6 -5
  15. package/cjs/http/utils/concat-readable.js +1 -2
  16. package/cjs/http/utils/convert-to-headers.js +2 -3
  17. package/cjs/http/utils/convert-to-raw-headers.js +1 -2
  18. package/cjs/http/utils/match-known-fields.js +2 -2
  19. package/cjs/http/utils/wrap-exception.js +1 -2
  20. package/cjs/index.js +4 -4
  21. package/cjs/platform-adapter.js +1 -4
  22. package/cjs/type-guards.js +4 -5
  23. package/esm/augmentation/18n.augmentation.js +1 -1
  24. package/esm/constants.js +0 -1
  25. package/esm/execution-context.js +1 -1
  26. package/esm/http/express-adapter.js +25 -34
  27. package/esm/http/http-adapter.js +2 -5
  28. package/esm/http/http-context.js +21 -33
  29. package/esm/http/{impl/http-handler.js → http-handler.js} +243 -207
  30. package/esm/http/impl/http-incoming.host.js +3 -3
  31. package/esm/http/impl/http-outgoing.host.js +2 -2
  32. package/esm/http/impl/multipart-reader.js +142 -51
  33. package/esm/http/impl/node-incoming-message.host.js +5 -3
  34. package/esm/http/interfaces/node-incoming-message.interface.js +3 -2
  35. package/esm/http/utils/body-reader.js +6 -5
  36. package/esm/http/utils/common.js +2 -1
  37. package/esm/index.js +4 -4
  38. package/esm/platform-adapter.js +1 -4
  39. package/package.json +21 -14
  40. package/types/augmentation/18n.augmentation.d.ts +1 -1
  41. package/types/constants.d.ts +0 -1
  42. package/types/execution-context.d.ts +2 -3
  43. package/types/http/express-adapter.d.ts +1 -1
  44. package/types/http/http-adapter.d.ts +35 -8
  45. package/types/http/http-context.d.ts +4 -4
  46. package/types/http/{impl/http-handler.d.ts → http-handler.d.ts} +11 -9
  47. package/types/http/impl/http-incoming.host.d.ts +1 -2
  48. package/types/http/impl/http-outgoing.host.d.ts +1 -1
  49. package/types/http/impl/multipart-reader.d.ts +38 -20
  50. package/types/http/impl/node-incoming-message.host.d.ts +2 -6
  51. package/types/http/impl/node-outgoing-message.host.d.ts +4 -7
  52. package/types/http/interfaces/http-incoming.interface.d.ts +1 -2
  53. package/types/http/interfaces/http-outgoing.interface.d.ts +1 -1
  54. package/types/http/interfaces/node-incoming-message.interface.d.ts +0 -2
  55. package/types/http/interfaces/node-outgoing-message.interface.d.ts +0 -2
  56. package/types/http/utils/body-reader.d.ts +2 -5
  57. package/types/http/utils/concat-readable.d.ts +0 -1
  58. package/types/http/utils/convert-to-raw-headers.d.ts +0 -1
  59. package/types/index.d.ts +4 -4
  60. package/types/platform-adapter.d.ts +1 -5
  61. package/cjs/helpers/logger.js +0 -35
  62. package/esm/helpers/logger.js +0 -31
  63. 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' !== method && 'HEAD' !== method)
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) || 304 === status) {
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 (204 === this.statusCode || 304 === this.statusCode) {
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 formidable from 'formidable';
3
- import fs from 'fs/promises';
9
+ import fsPromise from 'fs/promises';
10
+ import { isNotNullish } from 'valgen';
4
11
  export class MultipartReader extends EventEmitter {
5
- constructor(incoming, options) {
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._incoming = incoming;
13
- const form = (this._form = formidable({
14
- ...options,
15
- filter: (part) => {
16
- return !this._cancelled && (!options?.filter || options.filter(part));
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('field', (fieldName, value) => {
25
- const item = { fieldName, type: 'field', value };
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', (fieldName, file) => {
32
- const item = { fieldName, type: 'file', file };
33
- this._items.push(item);
34
- this._stack.push(item);
35
- this.emit('file', item);
36
- this.emit('item', item);
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
- if (!this._form.ended)
70
+ async getNext() {
71
+ let item = this._stack.shift();
72
+ if (!item && !this._finished) {
44
73
  this.resume();
45
- return new Promise((resolve, reject) => {
46
- if (this._stack.length)
47
- return resolve(this._stack.shift());
48
- if (this._form.ended)
49
- return resolve(undefined);
50
- this.once('item', () => resolve(this._stack.shift()));
51
- this.once('error', e => reject(e));
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
- getAll() {
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._form.parse(this._incoming, () => void 0);
73
- if (this._form.req)
74
- this._form.resume();
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
- if (this._form.req)
78
- this._form.pause();
176
+ this.context.request.pause();
79
177
  }
80
- async deleteTempFiles() {
178
+ async purge() {
81
179
  const promises = [];
82
180
  this._items.forEach(item => {
83
- if (!item.file)
181
+ if (item.kind !== 'file')
84
182
  return;
85
- const file = item.file;
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
- if (init.trailers)
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
  }));
@@ -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;
@@ -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",
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 _copyi18n && npm run _copy_pkg_files",
19
- "_copy_pkg_files": "cp README.md package.json ../../LICENSE ../../build/core && cp ../../package.cjs.json ../../build/core/cjs/package.json",
20
- "_copyi18n": "cp -R i18n ../../build/core/i18n",
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
- "check": "madge --circular src/**",
22
+ "lint:fix": "eslint . --max-warnings=0 --fix",
23
23
  "format": "prettier . --write --log-level=warn",
24
- "test": "jest",
25
- "cover": "jest --collect-coverage",
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.3",
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.3",
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": "^2.5.2",
53
- "strict-typed-events": "^2.3.3",
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.0",
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 {};
@@ -1,2 +1 @@
1
- export declare const kHandler: unique symbol;
2
1
  export declare const kAssetCache: unique symbol;
@@ -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
- readonly platformArgs: any;
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 './impl/http-handler.js';
4
+ import { HttpHandler } from './http-handler.js';
6
5
  export declare namespace HttpAdapter {
6
+ type NextCallback = () => Promise<void>;
7
7
  /**
8
- * @type Interceptor
8
+ * @type InterceptorFunction
9
9
  */
10
- type Interceptor = (context: HttpContext, next: () => Promise<void>) => Promise<void>;
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?: HttpAdapter.Interceptor[];
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
- protected [kHandler]: HttpHandler;
49
+ readonly handler: HttpHandler;
23
50
  readonly protocol: OpraSchema.Protocol;
24
- interceptors: HttpAdapter.Interceptor[];
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: HttpController;
29
+ readonly controller?: HttpController;
30
30
  readonly controllerInstance?: any;
31
- readonly operation: HttpOperation;
31
+ readonly operation?: HttpOperation;
32
32
  readonly operationHandler?: Function;
33
33
  readonly request: HttpIncoming;
34
34
  readonly response: HttpOutgoing;