@opra/core 1.0.0-alpha.9 → 1.0.0-beta.2

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 (60) hide show
  1. package/cjs/augmentation/18n.augmentation.js +13 -13
  2. package/cjs/constants.js +1 -2
  3. package/cjs/execution-context.js +1 -0
  4. package/cjs/http/express-adapter.js +22 -20
  5. package/cjs/http/http-adapter.js +2 -5
  6. package/cjs/http/http-context.js +16 -31
  7. package/cjs/http/{impl/http-handler.js → http-handler.js} +194 -169
  8. package/cjs/http/impl/http-outgoing.host.js +2 -2
  9. package/cjs/http/impl/multipart-reader.js +141 -44
  10. package/cjs/http/utils/body-reader.js +0 -1
  11. package/cjs/http/utils/common.js +4 -4
  12. package/cjs/http/utils/concat-readable.js +1 -2
  13. package/cjs/http/utils/convert-to-headers.js +2 -3
  14. package/cjs/http/utils/convert-to-raw-headers.js +1 -2
  15. package/cjs/http/utils/match-known-fields.js +2 -2
  16. package/cjs/http/utils/wrap-exception.js +1 -2
  17. package/cjs/index.js +1 -1
  18. package/cjs/platform-adapter.js +0 -3
  19. package/cjs/type-guards.js +4 -5
  20. package/esm/augmentation/18n.augmentation.js +2 -2
  21. package/esm/constants.js +0 -1
  22. package/esm/execution-context.js +1 -0
  23. package/esm/http/express-adapter.js +22 -20
  24. package/esm/http/http-adapter.js +2 -5
  25. package/esm/http/http-context.js +17 -32
  26. package/esm/http/{impl/http-handler.js → http-handler.js} +195 -170
  27. package/esm/http/impl/http-outgoing.host.js +1 -1
  28. package/esm/http/impl/multipart-reader.js +142 -45
  29. package/esm/http/utils/body-reader.js +0 -1
  30. package/esm/index.js +1 -1
  31. package/esm/package.json +3 -0
  32. package/esm/platform-adapter.js +0 -3
  33. package/package.json +35 -63
  34. package/types/augmentation/http-controller.augmentation.d.ts +1 -2
  35. package/types/constants.d.ts +0 -1
  36. package/types/execution-context.d.ts +2 -1
  37. package/types/helpers/service-base.d.ts +1 -1
  38. package/types/http/express-adapter.d.ts +1 -1
  39. package/types/http/http-adapter.d.ts +35 -8
  40. package/types/http/http-context.d.ts +4 -4
  41. package/types/http/{impl/http-handler.d.ts → http-handler.d.ts} +11 -9
  42. package/types/http/impl/http-incoming.host.d.ts +1 -2
  43. package/types/http/impl/http-outgoing.host.d.ts +1 -1
  44. package/types/http/impl/multipart-reader.d.ts +38 -20
  45. package/types/http/impl/node-incoming-message.host.d.ts +3 -7
  46. package/types/http/impl/node-outgoing-message.host.d.ts +5 -8
  47. package/types/http/interfaces/http-incoming.interface.d.ts +2 -3
  48. package/types/http/interfaces/http-outgoing.interface.d.ts +2 -2
  49. package/types/http/interfaces/node-incoming-message.interface.d.ts +0 -2
  50. package/types/http/interfaces/node-outgoing-message.interface.d.ts +1 -3
  51. package/types/http/utils/body-reader.d.ts +1 -4
  52. package/types/http/utils/concat-readable.d.ts +0 -1
  53. package/types/http/utils/convert-to-raw-headers.d.ts +1 -2
  54. package/types/index.d.cts +28 -0
  55. package/types/index.d.ts +1 -1
  56. package/types/platform-adapter.d.ts +0 -4
  57. package/cjs/helpers/logger.js +0 -35
  58. package/esm/helpers/logger.js +0 -31
  59. package/i18n/i18n/en/error.json +0 -21
  60. package/types/helpers/logger.d.ts +0 -14
@@ -1,55 +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) => !this._cancelled && (!options?.filter || options.filter(part)),
16
- }));
17
- 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) => {
18
27
  this._cancelled = true;
28
+ this._finished = true;
19
29
  if (this.listenerCount('error') > 0)
20
- this.emit('error');
30
+ this.emit('error', e);
21
31
  });
22
- form.on('field', (fieldName, value) => {
23
- 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
+ };
24
43
  this._items.push(item);
25
44
  this._stack.push(item);
26
45
  this.emit('field', item);
27
46
  this.emit('item', item);
28
47
  });
29
- form.on('file', (fieldName, file) => {
30
- const item = { fieldName, type: 'file', file };
31
- this._items.push(item);
32
- this._stack.push(item);
33
- this.emit('file', item);
34
- 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
+ });
35
65
  });
36
66
  }
37
67
  get items() {
38
68
  return this._items;
39
69
  }
40
- getNext() {
41
- if (!this._form.ended)
70
+ async getNext() {
71
+ let item = this._stack.shift();
72
+ if (!item && !this._finished) {
42
73
  this.resume();
43
- return new Promise((resolve, reject) => {
44
- if (this._stack.length)
45
- return resolve(this._stack.shift());
46
- if (this._form.ended)
47
- return resolve(undefined);
48
- this.once('item', () => resolve(this._stack.shift()));
49
- this.once('error', e => reject(e));
50
- });
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;
51
151
  }
52
- getAll() {
152
+ getAll_() {
53
153
  if (this._form.ended)
54
154
  return Promise.resolve([...this._items]);
55
155
  this.resume();
@@ -66,29 +166,26 @@ export class MultipartReader extends EventEmitter {
66
166
  this.resume();
67
167
  }
68
168
  resume() {
69
- if (!this._started)
70
- this._form.parse(this._incoming, () => undefined);
71
- if (this._form.req)
72
- this._form.resume();
169
+ if (!this._started) {
170
+ this._started = true;
171
+ this.context.request.pipe(this._form);
172
+ }
173
+ this.context.request.resume();
73
174
  }
74
175
  pause() {
75
- if (this._form.req)
76
- this._form.pause();
176
+ this.context.request.pause();
77
177
  }
78
- async deleteTempFiles() {
178
+ async purge() {
79
179
  const promises = [];
80
180
  this._items.forEach(item => {
81
- if (!item.file)
181
+ if (item.kind !== 'file')
82
182
  return;
83
- const file = item.file;
84
- promises.push(new Promise(resolve => {
85
- if (file._writeStream.closed)
86
- return resolve();
87
- file._writeStream.once('close', resolve);
88
- })
89
- .then(() => fs.unlink(file.filepath))
90
- .then(() => 0));
183
+ promises.push(fsPromise.unlink(item.storedPath));
91
184
  });
92
185
  return Promise.allSettled(promises);
93
186
  }
94
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
+ }
@@ -145,7 +145,6 @@ export class BodyReader extends EventEmitter {
145
145
  message: 'request aborted',
146
146
  code: 'ECONNABORTED',
147
147
  details: {
148
- length,
149
148
  received: this._receivedSize,
150
149
  },
151
150
  }));
package/esm/index.js CHANGED
@@ -6,11 +6,11 @@ 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 './helpers/logger.js';
10
9
  export * from './helpers/service-base.js';
11
10
  export * from './http/express-adapter.js';
12
11
  export * from './http/http-adapter.js';
13
12
  export * from './http/http-context.js';
13
+ export * from './http/http-handler.js';
14
14
  export * from './http/impl/multipart-reader.js';
15
15
  export * from './http/interfaces/http-incoming.interface.js';
16
16
  export * from './http/interfaces/http-outgoing.interface.js';
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "module"
3
+ }
@@ -2,7 +2,6 @@ import './augmentation/18n.augmentation.js';
2
2
  import { I18n } from '@opra/common';
3
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,42 +1,17 @@
1
1
  {
2
2
  "name": "@opra/core",
3
- "version": "1.0.0-alpha.9",
3
+ "version": "1.0.0-beta.2",
4
4
  "description": "Opra schema package",
5
5
  "author": "Panates",
6
6
  "license": "MIT",
7
- "repository": {
8
- "type": "git",
9
- "url": "https://github.com/panates/opra.git",
10
- "directory": "packages/core"
11
- },
12
- "scripts": {
13
- "compile": "tsc",
14
- "prebuild": "npm run lint && npm run clean",
15
- "build": "npm run build:cjs && npm run build:esm",
16
- "build:cjs": "tsc -b tsconfig-build-cjs.json",
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",
21
- "lint": "eslint . --max-warnings=0",
22
- "lint:fix": "eslint . --max-warnings=0 --fix",
23
- "format": "prettier . --write --log-level=warn",
24
- "check": "madge --circular src/**",
25
- "test": "jest --passWithNoTests",
26
- "cover": "jest --passWithNoTests --collect-coverage",
27
- "clean": "npm run clean:src && npm run clean:test && npm run clean:dist && npm run clean:cover",
28
- "clean:src": "ts-cleanup -s src --all",
29
- "clean:test": "ts-cleanup -s test --all",
30
- "clean:dist": "rimraf ../../build/client",
31
- "clean:cover": "rimraf ../../coverage/client"
32
- },
33
7
  "dependencies": {
34
- "@browsery/http-parser": "^0.5.8",
35
- "@browsery/type-is": "^1.6.18-r2",
36
- "@opra/common": "^1.0.0-alpha.9",
37
- "@types/formidable": "^3.4.5",
8
+ "@browsery/antlr4": "^4.13.3-r1",
9
+ "@browsery/http-parser": "^0.5.9-r1",
10
+ "@browsery/type-is": "^1.6.18-r5",
11
+ "@opra/common": "^1.0.0-beta.2",
38
12
  "accepts": "^1.3.8",
39
13
  "base64-stream": "^1.0.0",
14
+ "busboy": "^1.6.0",
40
15
  "bytes": "^3.1.2",
41
16
  "content-disposition": "^0.5.4",
42
17
  "content-type": "^1.0.5",
@@ -44,58 +19,55 @@
44
19
  "cookie-signature": "^1.2.1",
45
20
  "cppzst": "^2.0.12",
46
21
  "encodeurl": "^2.0.0",
47
- "formidable": "^3.5.1",
22
+ "fast-tokenizer": "^1.7.0",
48
23
  "fresh": "^0.5.2",
49
24
  "iconv-lite": "^0.6.3",
50
25
  "mime-types": "^2.1.35",
51
- "power-tasks": "^1.7.3",
26
+ "power-tasks": "^1.11.0",
52
27
  "putil-isplainobject": "^1.1.5",
53
- "putil-merge": "^3.12.1",
28
+ "putil-merge": "^3.13.0",
54
29
  "putil-varhelpers": "^1.6.5",
55
30
  "range-parser": "^1.2.1",
56
- "raw-body": "^2.5.2",
31
+ "raw-body": "^3.0.0",
57
32
  "reflect-metadata": "^0.2.2",
58
- "strict-typed-events": "^2.3.3",
33
+ "strict-typed-events": "^2.8.0",
34
+ "super-fast-md5": "^1.0.3",
35
+ "tslib": "^2.7.0",
36
+ "valgen": "^5.9.0",
59
37
  "vary": "^1.1.2"
60
38
  },
61
39
  "optionalDependencies": {
62
40
  "express": "^4.x.x || ^5.x.x",
63
41
  "fastify": "^4.x.x"
64
42
  },
65
- "devDependencies": {
66
- "@faker-js/faker": "^8.4.1",
67
- "@types/accepts": "^1.3.7",
68
- "@types/base64-stream": "^1.0.5",
69
- "@types/bytes": "^3.1.4",
70
- "@types/content-disposition": "^0.5.8",
71
- "@types/content-type": "^1.1.8",
72
- "@types/cookie": "^0.6.0",
73
- "@types/cookie-parser": "^1.4.7",
74
- "@types/cookie-signature": "^1.1.2",
75
- "@types/encodeurl": "^1.0.2",
76
- "@types/express": "^4.17.21",
77
- "@types/fresh": "^0.5.2",
78
- "@types/mime-types": "^2.1.4",
79
- "@types/range-parser": "^1.2.7",
80
- "@types/vary": "^1.1.3",
81
- "cookie-parser": "^1.4.6",
82
- "crypto-browserify": "^3.12.0",
83
- "express": "^4.19.2",
84
- "fastify": "^4.28.1",
85
- "path-browserify": "^1.0.1",
86
- "supertest": "^7.0.0",
87
- "ts-gems": "^3.4.0"
88
- },
89
43
  "type": "module",
90
- "module": "./esm/index.js",
44
+ "exports": {
45
+ ".": {
46
+ "import": {
47
+ "types": "./types/index.d.ts",
48
+ "default": "./esm/index.js"
49
+ },
50
+ "require": {
51
+ "types": "./types/index.d.cts",
52
+ "default": "./cjs/index.js"
53
+ },
54
+ "default": "./esm/index.js"
55
+ },
56
+ "./package.json": "./package.json"
57
+ },
91
58
  "main": "./cjs/index.js",
59
+ "module": "./esm/index.js",
92
60
  "types": "./types/index.d.ts",
61
+ "repository": {
62
+ "type": "git",
63
+ "url": "https://github.com/panates/opra.git",
64
+ "directory": "packages/core"
65
+ },
93
66
  "engines": {
94
67
  "node": ">=16.0",
95
68
  "npm": ">=7.0.0"
96
69
  },
97
70
  "files": [
98
- "bin/",
99
71
  "cjs/",
100
72
  "esm/",
101
73
  "i18n/",
@@ -113,4 +85,4 @@
113
85
  "swagger",
114
86
  "raml"
115
87
  ]
116
- }
88
+ }
@@ -15,7 +15,6 @@ declare module '@opra/common' {
15
15
  }
16
16
  }
17
17
  namespace HttpOperation {
18
- interface Context extends HttpContext {
19
- }
18
+ type Context = HttpContext;
20
19
  }
21
20
  }
@@ -1,2 +1 @@
1
- export declare const kHandler: unique symbol;
2
1
  export declare const kAssetCache: unique symbol;
@@ -1,4 +1,4 @@
1
- import { ApiDocument, OpraSchema } from '@opra/common';
1
+ import { ApiDocument, OpraHttpError, OpraSchema } from '@opra/common';
2
2
  import { AsyncEventEmitter } from 'strict-typed-events';
3
3
  /**
4
4
  * @namespace ExecutionContext
@@ -18,6 +18,7 @@ export declare abstract class ExecutionContext extends AsyncEventEmitter {
18
18
  readonly document: ApiDocument;
19
19
  readonly protocol: OpraSchema.Protocol;
20
20
  readonly platform: string;
21
+ errors: OpraHttpError[];
21
22
  protected constructor(init: ExecutionContext.Initiator);
22
23
  addListener(event: 'finish', listener: ExecutionContext.OnFinishListener): this;
23
24
  removeListener(event: 'finish', listener: ExecutionContext.OnFinishListener): this;
@@ -1,4 +1,4 @@
1
- import { Nullish } from 'ts-gems';
1
+ import type { Nullish } from 'ts-gems';
2
2
  import type { HttpContext } from '../http/http-context';
3
3
  export declare abstract class ServiceBase {
4
4
  protected _context: HttpContext;
@@ -1,5 +1,5 @@
1
1
  import { ApiDocument, HttpController } from '@opra/common';
2
- import { Application } from 'express';
2
+ import { type 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;
@@ -1,9 +1,8 @@
1
- import { HttpOperationResponse } from '@opra/common';
2
- import { kAssetCache } from '../../constants.js';
3
- import type { HttpAdapter } from '../http-adapter.js';
4
- import { HttpContext } from '../http-context.js';
5
- import { HttpOutgoing } from '../interfaces/http-outgoing.interface.js';
6
- import { AssetCache } from './asset-cache.js';
1
+ import { HttpOperationResponse, OpraException, OpraHttpError } from '@opra/common';
2
+ import { kAssetCache } from '../constants.js';
3
+ import type { HttpAdapter } from './http-adapter.js';
4
+ import { HttpContext } from './http-context.js';
5
+ import { AssetCache } from './impl/asset-cache.js';
7
6
  /**
8
7
  * @namespace
9
8
  */
@@ -16,6 +15,7 @@ export declare namespace HttpHandler {
16
15
  contentType?: string;
17
16
  operationResponse?: HttpOperationResponse;
18
17
  body?: any;
18
+ projection?: string[] | '*';
19
19
  }
20
20
  }
21
21
  /**
@@ -24,6 +24,7 @@ export declare namespace HttpHandler {
24
24
  export declare class HttpHandler {
25
25
  readonly adapter: HttpAdapter;
26
26
  protected [kAssetCache]: AssetCache;
27
+ onError?: (context: HttpContext, error: OpraException) => void | Promise<void>;
27
28
  constructor(adapter: HttpAdapter);
28
29
  /**
29
30
  * Main http request handler
@@ -60,7 +61,9 @@ export declare class HttpHandler {
60
61
  * @param responseValue
61
62
  * @protected
62
63
  */
63
- protected _sendResponse(context: HttpContext, responseValue: any): Promise<void>;
64
+ sendResponse(context: HttpContext, responseValue?: any): Promise<void>;
65
+ protected _sendErrorResponse(context: HttpContext): Promise<void>;
66
+ sendDocumentSchema(context: HttpContext): Promise<void>;
64
67
  /**
65
68
  *
66
69
  * @param context
@@ -68,6 +71,5 @@ export declare class HttpHandler {
68
71
  * @protected
69
72
  */
70
73
  protected _determineResponseArgs(context: HttpContext, body: any): HttpHandler.ResponseArgs;
71
- sendDocumentSchema(context: HttpContext): Promise<void>;
72
- sendErrorResponse(response: HttpOutgoing, errors: any[]): Promise<void>;
74
+ protected _wrapExceptions(exceptions: any[]): OpraHttpError[];
73
75
  }
@@ -1,6 +1,5 @@
1
- /// <reference types="node" />
2
1
  import parseRange from 'range-parser';
3
- import type { HttpIncoming } from '../interfaces/http-incoming.interface';
2
+ import type { HttpIncoming } from '../interfaces/http-incoming.interface.js';
4
3
  import { BodyReader } from '../utils/body-reader.js';
5
4
  export interface HttpIncomingHost extends HttpIncoming {
6
5
  }
@@ -1,4 +1,4 @@
1
- import type { CookieOptions, HttpOutgoing } from '../interfaces/http-outgoing.interface';
1
+ import type { CookieOptions, HttpOutgoing } from '../interfaces/http-outgoing.interface.js';
2
2
  export interface HttpOutgoingHost extends HttpOutgoing {
3
3
  }
4
4
  export declare class HttpOutgoingHost {