@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
@@ -1,11 +1,11 @@
1
+ import { HttpApi, NotFoundError } from '@opra/common';
1
2
  import { Router } from 'express';
2
3
  import * as nodePath from 'path';
3
- import { HttpApi, NotFoundError } from '@opra/common';
4
- import { kHandler } from '../constants.js';
5
4
  import { HttpAdapter } from './http-adapter.js';
6
5
  import { HttpContext } from './http-context.js';
7
6
  import { HttpIncoming } from './interfaces/http-incoming.interface.js';
8
7
  import { HttpOutgoing } from './interfaces/http-outgoing.interface.js';
8
+ import { wrapException } from './utils/wrap-exception';
9
9
  export class ExpressAdapter extends HttpAdapter {
10
10
  constructor(app, document, options) {
11
11
  super(document, options);
@@ -31,13 +31,15 @@ export class ExpressAdapter extends HttpAdapter {
31
31
  }
32
32
  if (resource.onShutdown) {
33
33
  const instance = this._controllerInstances.get(resource) || resource.instance;
34
- if (instance)
34
+ if (instance) {
35
35
  try {
36
36
  await resource.onShutdown.call(instance, resource);
37
37
  }
38
38
  catch (e) {
39
- this.logger.error(e);
39
+ if (this.listenerCount('error'))
40
+ this.emit('error', wrapException(e));
40
41
  }
42
+ }
41
43
  }
42
44
  };
43
45
  for (const c of this.api.controllers.values())
@@ -58,17 +60,12 @@ export class ExpressAdapter extends HttpAdapter {
58
60
  }
59
61
  else
60
62
  this.app.use(router);
61
- const createContext = (_req, _res, args) => {
63
+ const createContext = async (_req, _res, args) => {
62
64
  const request = HttpIncoming.from(_req);
63
65
  const response = HttpOutgoing.from(_res);
64
- const platformArgs = {
65
- request: _req,
66
- response: _res,
67
- };
68
- return new HttpContext({
66
+ const ctx = new HttpContext({
69
67
  adapter: this,
70
68
  platform: this.platform,
71
- platformArgs,
72
69
  request,
73
70
  response,
74
71
  controller: args?.controller,
@@ -76,18 +73,14 @@ export class ExpressAdapter extends HttpAdapter {
76
73
  operation: args?.operation,
77
74
  operationHandler: args?.operationHandler,
78
75
  });
76
+ await this.emitAsync('createContext', ctx);
77
+ return ctx;
79
78
  };
80
79
  /** Add an endpoint that returns document schema */
81
- router.get('*', (_req, _res, next) => {
82
- if (_req.url.includes('/$schema')) {
83
- const url = (_req.url.includes('?') ? _req.url.substring(0, _req.url.indexOf('?')) : _req.url).toLowerCase();
84
- if (url === '/$schema') {
85
- const context = createContext(_req, _res);
86
- this[kHandler].sendDocumentSchema(context).catch(next);
87
- return;
88
- }
89
- }
90
- next();
80
+ router.get('/\\$schema', (_req, _res, next) => {
81
+ createContext(_req, _res)
82
+ .then(ctx => this.handler.sendDocumentSchema(ctx).catch(next))
83
+ .catch(next);
91
84
  });
92
85
  /** Add operation endpoints */
93
86
  if (this.api.controllers.size) {
@@ -101,19 +94,18 @@ export class ExpressAdapter extends HttpAdapter {
101
94
  continue;
102
95
  /** Define router callback */
103
96
  router[operation.method.toLowerCase()](routePath, (_req, _res, _next) => {
104
- const context = createContext(_req, _res, {
97
+ createContext(_req, _res, {
105
98
  controller,
106
99
  controllerInstance,
107
100
  operation,
108
101
  operationHandler,
109
- });
110
- this[kHandler]
111
- .handleRequest(context)
102
+ })
103
+ .then(ctx => this.handler.handleRequest(ctx))
112
104
  .then(() => {
113
105
  if (!_res.headersSent)
114
106
  _next();
115
107
  })
116
- .catch((e) => this.logger.fatal(e));
108
+ .catch((e) => this.emit('error', e));
117
109
  });
118
110
  }
119
111
  if (controller.controllers.size) {
@@ -126,18 +118,17 @@ export class ExpressAdapter extends HttpAdapter {
126
118
  }
127
119
  /** Add an endpoint that returns 404 error at last */
128
120
  router.use('*', (_req, _res, next) => {
129
- const res = HttpOutgoing.from(_res);
130
- // const url = new URL(_req.originalUrl, '')
131
- this[kHandler]
132
- .sendErrorResponse(res, [
133
- new NotFoundError({
134
- message: `No endpoint found for [${_req.method}]${_req.baseUrl}`,
121
+ createContext(_req, _res)
122
+ .then(ctx => {
123
+ ctx.errors.push(new NotFoundError({
124
+ message: `No endpoint found at [${_req.method}]${_req.baseUrl}`,
135
125
  details: {
136
126
  path: _req.baseUrl,
137
127
  method: _req.method,
138
128
  },
139
- }),
140
- ])
129
+ }));
130
+ this.handler.sendResponse(ctx).catch(next);
131
+ })
141
132
  .catch(next);
142
133
  });
143
134
  }
@@ -1,7 +1,6 @@
1
1
  import { HttpApi } from '@opra/common';
2
- import { kHandler } from '../constants.js';
3
2
  import { PlatformAdapter } from '../platform-adapter.js';
4
- import { HttpHandler } from './impl/http-handler.js';
3
+ import { HttpHandler } from './http-handler.js';
5
4
  /**
6
5
  *
7
6
  * @class HttpAdapter
@@ -12,10 +11,8 @@ export class HttpAdapter extends PlatformAdapter {
12
11
  this.protocol = 'http';
13
12
  if (!(document.api instanceof HttpApi))
14
13
  throw new TypeError(`The document does not expose an HTTP Api`);
15
- this[kHandler] = new HttpHandler(this);
14
+ this.handler = new HttpHandler(this);
16
15
  this.interceptors = [...(options?.interceptors || [])];
17
- if (options?.onRequest)
18
- this.on('request', options.onRequest);
19
16
  }
20
17
  get api() {
21
18
  return this.document.api;
@@ -1,6 +1,6 @@
1
- import { vg } from 'valgen';
2
1
  import typeIs from '@browsery/type-is';
3
- import { BadRequestError, InternalServerError, NotAcceptableError, } from '@opra/common';
2
+ import { 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';
@@ -25,6 +25,10 @@ export class HttpContext extends ExecutionContext {
25
25
  this.pathParams = init.pathParams || {};
26
26
  this.queryParams = init.queryParams || {};
27
27
  this._body = init.body;
28
+ this.on('finish', () => {
29
+ if (this._multipartReader)
30
+ this._multipartReader.purge().catch(() => undefined);
31
+ });
28
32
  }
29
33
  get isMultipart() {
30
34
  return !!this.request.is('multipart');
@@ -34,21 +38,21 @@ export class HttpContext extends ExecutionContext {
34
38
  throw new InternalServerError('Request content is not a multipart content');
35
39
  if (this._multipartReader)
36
40
  return this._multipartReader;
37
- const { request, mediaType } = this;
41
+ const { mediaType } = this;
38
42
  if (mediaType?.contentType) {
39
43
  const arr = Array.isArray(mediaType.contentType) ? mediaType.contentType : [mediaType.contentType];
40
44
  const contentType = arr.find(ct => typeIs.is(ct, ['multipart']));
41
45
  if (!contentType)
42
46
  throw new NotAcceptableError('This endpoint does not accept multipart requests');
43
47
  }
44
- const reader = new MultipartReader(request, {
45
- maxFields: mediaType?.maxFields,
46
- maxFieldsSize: mediaType?.maxFieldsSize,
47
- maxFiles: mediaType?.maxFiles,
48
- maxFileSize: mediaType?.maxFileSize,
49
- maxTotalFileSize: mediaType?.maxTotalFileSize,
50
- minFileSize: mediaType?.minFileSize,
51
- });
48
+ const reader = new MultipartReader(this, {
49
+ limits: {
50
+ fields: mediaType?.maxFields,
51
+ fieldSize: mediaType?.maxFieldsSize,
52
+ files: mediaType?.maxFiles,
53
+ fileSize: mediaType?.maxFileSize,
54
+ },
55
+ }, mediaType);
52
56
  this._multipartReader = reader;
53
57
  return reader;
54
58
  }
@@ -61,32 +65,15 @@ export class HttpContext extends ExecutionContext {
61
65
  /** Retrieve all fields */
62
66
  const parts = await reader.getAll();
63
67
  /** Filter fields according to configuration */
64
- this._body = [];
65
- const multipartFields = mediaType?.multipartFields;
66
- if (mediaType && multipartFields?.length) {
67
- const fieldsFound = new Map();
68
- for (const item of parts) {
69
- const field = mediaType.findMultipartField(item.fieldName, item.type);
70
- if (field) {
71
- fieldsFound.set(field, true);
72
- this._body.push(item);
73
- }
74
- }
75
- /** Check required fields */
76
- for (const field of multipartFields) {
77
- if (field.required && !fieldsFound.get(field))
78
- throw new BadRequestError({
79
- message: `Multipart field (${field.fieldName}) is required`,
80
- });
81
- }
82
- }
68
+ this._body = [...parts];
83
69
  return this._body;
84
70
  }
85
- this._body = await this.request.readBody({ limit: operation.requestBody?.maxContentSize });
71
+ this._body = await this.request.readBody({ limit: operation?.requestBody?.maxContentSize });
86
72
  if (this._body != null) {
87
73
  // Convert Buffer to string if media is text
88
- if (Buffer.isBuffer(this._body) && request.is(['json', 'xml', 'txt', 'text']))
74
+ if (Buffer.isBuffer(this._body) && request.is(['json', 'xml', 'txt', 'text'])) {
89
75
  this._body = this._body.toString('utf-8');
76
+ }
90
77
  // Transform text to Object if media is JSON
91
78
  if (typeof this._body === 'string' && request.is(['json']))
92
79
  this._body = JSON.parse(this._body);
@@ -98,8 +85,9 @@ export class HttpContext extends ExecutionContext {
98
85
  if (!decode) {
99
86
  decode =
100
87
  mediaType.type?.generateCodec('decode', {
101
- partial: operation.requestBody?.partial,
88
+ partial: operation?.requestBody?.partial,
102
89
  projection: '*',
90
+ ignoreReadonlyFields: true,
103
91
  }) || vg.isAny();
104
92
  this.adapter[kAssetCache].set(mediaType, 'decode', decode);
105
93
  }