@opra/core 0.21.0 → 0.23.0

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 (118) hide show
  1. package/cjs/adapter/execution-context.host.js +48 -0
  2. package/cjs/adapter/http/express-adapter.host.js +24 -0
  3. package/cjs/adapter/http/express-adapter.js +12 -45
  4. package/cjs/adapter/http/helpers/concat-readable.js +20 -0
  5. package/cjs/adapter/http/helpers/multipart-helper.js +96 -0
  6. package/cjs/adapter/http/helpers/query-parsers.js +16 -0
  7. package/cjs/adapter/http/http-adapter-base.js +127 -0
  8. package/cjs/adapter/http/http-adapter.host.js +57 -0
  9. package/cjs/adapter/http/http-adapter.js +11 -129
  10. package/cjs/adapter/http/{impl/http-server-request.js → http-server-request.js} +11 -5
  11. package/cjs/adapter/http/{impl/http-server-response.js → http-server-response.js} +22 -22
  12. package/cjs/adapter/http/impl/http-incoming-message.host.js +148 -0
  13. package/cjs/adapter/http/impl/{http-outgoing-message-host.js → http-outgoing-message.host.js} +26 -38
  14. package/cjs/adapter/http/request-handlers/entity-request-handler.js +378 -0
  15. package/cjs/adapter/http/request-handlers/request-handler-base.js +27 -0
  16. package/cjs/adapter/http/request-handlers/storage-request-handler.js +134 -0
  17. package/cjs/adapter/operation-context.js +16 -0
  18. package/cjs/adapter/platform-adapter.host.js +107 -0
  19. package/cjs/adapter/request.host.js +1 -2
  20. package/cjs/adapter/request.js +2 -0
  21. package/cjs/adapter/response.js +2 -0
  22. package/cjs/adapter/services/logger.js +36 -0
  23. package/cjs/augmentation/collection.augmentation.js +2 -0
  24. package/cjs/augmentation/singleton.augmentation.js +2 -0
  25. package/cjs/augmentation/storage.augmentation.js +2 -0
  26. package/cjs/index.js +15 -9
  27. package/esm/adapter/execution-context.host.js +44 -0
  28. package/esm/adapter/http/express-adapter.host.js +20 -0
  29. package/esm/adapter/http/express-adapter.js +11 -20
  30. package/esm/adapter/http/helpers/concat-readable.js +16 -0
  31. package/esm/adapter/http/helpers/multipart-helper.js +91 -0
  32. package/esm/adapter/http/helpers/query-parsers.js +12 -0
  33. package/esm/adapter/http/http-adapter-base.js +123 -0
  34. package/esm/adapter/http/http-adapter.host.js +52 -0
  35. package/esm/adapter/http/http-adapter.js +11 -128
  36. package/esm/adapter/http/{impl/http-server-request.js → http-server-request.js} +12 -6
  37. package/esm/adapter/http/{impl/http-server-response.js → http-server-response.js} +22 -22
  38. package/esm/adapter/http/impl/http-incoming-message.host.js +144 -0
  39. package/esm/adapter/http/impl/{http-outgoing-message-host.js → http-outgoing-message.host.js} +25 -36
  40. package/esm/adapter/http/request-handlers/entity-request-handler.js +373 -0
  41. package/esm/adapter/http/request-handlers/request-handler-base.js +23 -0
  42. package/esm/adapter/http/request-handlers/storage-request-handler.js +129 -0
  43. package/esm/adapter/operation-context.js +13 -0
  44. package/esm/adapter/platform-adapter.host.js +102 -0
  45. package/esm/adapter/request.host.js +1 -2
  46. package/esm/adapter/request.js +1 -0
  47. package/esm/adapter/response.js +1 -0
  48. package/esm/adapter/services/logger.js +32 -0
  49. package/esm/augmentation/collection.augmentation.js +1 -0
  50. package/esm/augmentation/singleton.augmentation.js +1 -0
  51. package/esm/augmentation/storage.augmentation.js +1 -0
  52. package/esm/index.js +15 -9
  53. package/i18n/en/error.json +5 -2
  54. package/package.json +8 -7
  55. package/types/adapter/execution-context.d.ts +31 -0
  56. package/types/adapter/execution-context.host.d.ts +27 -0
  57. package/types/adapter/http/express-adapter.d.ts +12 -8
  58. package/types/adapter/http/express-adapter.host.d.ts +11 -0
  59. package/types/adapter/http/helpers/concat-readable.d.ts +3 -0
  60. package/types/adapter/http/helpers/multipart-helper.d.ts +25 -0
  61. package/types/adapter/http/helpers/query-parsers.d.ts +1 -0
  62. package/types/adapter/http/http-adapter-base.d.ts +23 -0
  63. package/types/adapter/http/http-adapter.d.ts +13 -29
  64. package/types/adapter/http/http-adapter.host.d.ts +18 -0
  65. package/types/adapter/http/{impl/http-server-request.d.ts → http-server-request.d.ts} +7 -6
  66. package/types/adapter/http/{impl/http-server-response.d.ts → http-server-response.d.ts} +2 -2
  67. package/types/adapter/http/impl/{http-incoming-message-host.d.ts → http-incoming-message.host.d.ts} +16 -12
  68. package/types/adapter/http/impl/{http-outgoing-message-host.d.ts → http-outgoing-message.host.d.ts} +12 -16
  69. package/types/adapter/http/request-handlers/entity-request-handler.d.ts +24 -0
  70. package/types/adapter/http/request-handlers/request-handler-base.d.ts +15 -0
  71. package/types/adapter/http/request-handlers/storage-request-handler.d.ts +23 -0
  72. package/types/adapter/interfaces/logger.interface.d.ts +7 -6
  73. package/types/adapter/interfaces/request-handler.interface.d.ts +4 -0
  74. package/types/adapter/operation-context.d.ts +11 -0
  75. package/types/adapter/{adapter.d.ts → platform-adapter.d.ts} +18 -28
  76. package/types/adapter/platform-adapter.host.d.ts +31 -0
  77. package/types/adapter/request.d.ts +11 -0
  78. package/types/adapter/request.host.d.ts +12 -21
  79. package/types/adapter/{interfaces/response.interface.d.ts → response.d.ts} +2 -2
  80. package/types/adapter/response.host.d.ts +2 -2
  81. package/types/adapter/services/logger.d.ts +14 -0
  82. package/types/augmentation/collection.augmentation.d.ts +112 -0
  83. package/types/augmentation/singleton.augmentation.d.ts +64 -0
  84. package/types/augmentation/storage.augmentation.d.ts +39 -0
  85. package/types/index.d.ts +15 -9
  86. package/cjs/adapter/adapter.js +0 -118
  87. package/cjs/adapter/http/impl/http-incoming-message-host.js +0 -127
  88. package/cjs/adapter/http/request-parsers/parse-collection-request.js +0 -165
  89. package/cjs/adapter/http/request-parsers/parse-request.js +0 -24
  90. package/cjs/adapter/http/request-parsers/parse-singleton-request.js +0 -96
  91. package/cjs/adapter/internal/metadata.resource.js +0 -26
  92. package/cjs/adapter/request-context.host.js +0 -44
  93. package/cjs/shared/collection-resource-base.js +0 -20
  94. package/esm/adapter/adapter.js +0 -113
  95. package/esm/adapter/http/impl/http-incoming-message-host.js +0 -122
  96. package/esm/adapter/http/request-parsers/parse-collection-request.js +0 -161
  97. package/esm/adapter/http/request-parsers/parse-request.js +0 -20
  98. package/esm/adapter/http/request-parsers/parse-singleton-request.js +0 -92
  99. package/esm/adapter/internal/metadata.resource.js +0 -23
  100. package/esm/adapter/request-context.host.js +0 -40
  101. package/esm/shared/collection-resource-base.js +0 -16
  102. package/types/adapter/http/request-parsers/parse-collection-request.d.ts +0 -4
  103. package/types/adapter/http/request-parsers/parse-request.d.ts +0 -4
  104. package/types/adapter/http/request-parsers/parse-singleton-request.d.ts +0 -4
  105. package/types/adapter/interfaces/request-context.interface.d.ts +0 -31
  106. package/types/adapter/interfaces/request.interface.d.ts +0 -15
  107. package/types/adapter/internal/metadata.resource.d.ts +0 -7
  108. package/types/adapter/request-context.host.d.ts +0 -22
  109. package/types/shared/collection-resource-base.d.ts +0 -11
  110. /package/cjs/adapter/{interfaces/request-context.interface.js → execution-context.js} +0 -0
  111. /package/cjs/adapter/http/{request-parsers/batch-request-parser.js → request-handlers/parse-batch-request.js} +0 -0
  112. /package/cjs/adapter/interfaces/{request.interface.js → request-handler.interface.js} +0 -0
  113. /package/cjs/adapter/{interfaces/response.interface.js → platform-adapter.js} +0 -0
  114. /package/esm/adapter/{interfaces/request-context.interface.js → execution-context.js} +0 -0
  115. /package/esm/adapter/http/{request-parsers/batch-request-parser.js → request-handlers/parse-batch-request.js} +0 -0
  116. /package/esm/adapter/interfaces/{request.interface.js → request-handler.interface.js} +0 -0
  117. /package/esm/adapter/{interfaces/response.interface.js → platform-adapter.js} +0 -0
  118. /package/types/adapter/http/{request-parsers/batch-request-parser.d.ts → request-handlers/parse-batch-request.d.ts} +0 -0
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Logger = void 0;
4
+ class Logger {
5
+ constructor(options = {}) {
6
+ this._instance = options.instance ||
7
+ (!(process.env.NODE_ENV || '').includes('test') ? globalThis.console : {});
8
+ }
9
+ info(message, ...optionalParams) {
10
+ (this._instance.info || this._instance.log)?.(message, ...optionalParams);
11
+ }
12
+ error(message, ...optionalParams) {
13
+ if (this._instance.error) {
14
+ this._instance.error(message, ...optionalParams);
15
+ return;
16
+ }
17
+ this.info(message, ...optionalParams);
18
+ }
19
+ fatal(message, ...optionalParams) {
20
+ if (this._instance.fatal) {
21
+ this._instance.fatal(message, ...optionalParams);
22
+ return;
23
+ }
24
+ this.error(message, ...optionalParams);
25
+ }
26
+ warn(message, ...optionalParams) {
27
+ this._instance.warn?.(message, ...optionalParams);
28
+ }
29
+ debug(message, ...optionalParams) {
30
+ this._instance.debug?.(message, ...optionalParams);
31
+ }
32
+ verbose(message, ...optionalParams) {
33
+ this._instance.verbose?.(message, ...optionalParams);
34
+ }
35
+ }
36
+ exports.Logger = Logger;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/cjs/index.js CHANGED
@@ -3,16 +3,22 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const tslib_1 = require("tslib");
4
4
  require("reflect-metadata");
5
5
  require("./augmentation/resource.augmentation.js");
6
+ require("./augmentation/collection.augmentation.js");
7
+ require("./augmentation/singleton.augmentation.js");
8
+ require("./augmentation/storage.augmentation.js");
6
9
  tslib_1.__exportStar(require("./types.js"), exports);
7
- tslib_1.__exportStar(require("./adapter/adapter.js"), exports);
10
+ tslib_1.__exportStar(require("./adapter/execution-context.js"), exports);
11
+ tslib_1.__exportStar(require("./adapter/operation-context.js"), exports);
12
+ tslib_1.__exportStar(require("./adapter/platform-adapter.js"), exports);
13
+ tslib_1.__exportStar(require("./adapter/request.js"), exports);
14
+ tslib_1.__exportStar(require("./adapter/response.js"), exports);
8
15
  tslib_1.__exportStar(require("./adapter/http/express-adapter.js"), exports);
9
16
  tslib_1.__exportStar(require("./adapter/http/http-adapter.js"), exports);
10
- tslib_1.__exportStar(require("./adapter/http/impl/http-server-request.js"), exports);
11
- tslib_1.__exportStar(require("./adapter/http/impl/http-server-response.js"), exports);
12
- tslib_1.__exportStar(require("./adapter/http/impl/http-incoming-message-host.js"), exports);
13
- tslib_1.__exportStar(require("./adapter/http/impl/http-outgoing-message-host.js"), exports);
14
- tslib_1.__exportStar(require("./adapter/interfaces/request-context.interface.js"), exports);
17
+ tslib_1.__exportStar(require("./adapter/http/impl/http-incoming-message.host.js"), exports);
18
+ tslib_1.__exportStar(require("./adapter/http/impl/http-outgoing-message.host.js"), exports);
19
+ tslib_1.__exportStar(require("./adapter/http/http-server-request.js"), exports);
20
+ tslib_1.__exportStar(require("./adapter/http/http-server-response.js"), exports);
15
21
  tslib_1.__exportStar(require("./adapter/interfaces/logger.interface.js"), exports);
16
- tslib_1.__exportStar(require("./adapter/interfaces/request.interface.js"), exports);
17
- tslib_1.__exportStar(require("./adapter/interfaces/response.interface.js"), exports);
18
- tslib_1.__exportStar(require("./shared/collection-resource-base.js"), exports);
22
+ tslib_1.__exportStar(require("./adapter/interfaces/request-handler.interface.js"), exports);
23
+ tslib_1.__exportStar(require("./adapter/services/logger.js"), exports);
24
+ tslib_1.__exportStar(require("./adapter/http/helpers/multipart-helper.js"), exports);
@@ -0,0 +1,44 @@
1
+ import { AsyncEventEmitter } from 'strict-typed-events';
2
+ export class ExecutionContextHost extends AsyncEventEmitter {
3
+ constructor(api, platform, protocol) {
4
+ super();
5
+ this.api = api;
6
+ this.platform = platform;
7
+ this.errors = [];
8
+ this.executionScope = {};
9
+ this.ws = protocol.ws;
10
+ this.rpc = protocol.rpc;
11
+ if (protocol.http) {
12
+ this.protocol = 'http';
13
+ this.http = {
14
+ platform,
15
+ incoming: protocol.http.incoming,
16
+ outgoing: protocol.http.outgoing,
17
+ switchToContext: () => this
18
+ };
19
+ }
20
+ else if (protocol.ws) {
21
+ this.protocol = 'ws';
22
+ this.ws = protocol.ws;
23
+ }
24
+ else if (protocol.rpc) {
25
+ this.protocol = 'rpc';
26
+ this.rpc = protocol.rpc;
27
+ }
28
+ }
29
+ switchToHttp() {
30
+ if (this.http)
31
+ return this.http;
32
+ throw new TypeError('Not executing in an "Http" context');
33
+ }
34
+ switchToWs() {
35
+ if (this.ws)
36
+ return this.ws;
37
+ throw new TypeError('Not executing in an "WebSocket" context');
38
+ }
39
+ switchToRpc() {
40
+ if (this.rpc)
41
+ return this.rpc;
42
+ throw new TypeError('Not executing in an "RPC" context');
43
+ }
44
+ }
@@ -0,0 +1,20 @@
1
+ import { OpraURLPath } from '@opra/common';
2
+ import { HttpAdapterBase } from './http-adapter-base.js';
3
+ import { HttpServerRequest } from './http-server-request.js';
4
+ import { HttpServerResponse } from './http-server-response.js';
5
+ export class ExpressAdapterHost extends HttpAdapterBase {
6
+ constructor(app, api, options) {
7
+ super(api, options);
8
+ this._platform = 'express';
9
+ const basePath = new OpraURLPath(options?.basePath);
10
+ app.use(basePath.toString(), (_req, _res) => {
11
+ const req = HttpServerRequest.from(_req);
12
+ const res = HttpServerResponse.from(_res);
13
+ this.handleIncoming(req, res)
14
+ .catch(() => void 0);
15
+ });
16
+ }
17
+ get app() {
18
+ return this._app;
19
+ }
20
+ }
@@ -1,22 +1,13 @@
1
- import { normalizePath } from '@opra/common';
2
- import { OpraHttpAdapter } from './http-adapter.js';
3
- import { HttpServerRequest } from './impl/http-server-request.js';
4
- import { HttpServerResponse } from './impl/http-server-response.js';
5
- export class OpraExpressAdapter extends OpraHttpAdapter {
6
- constructor() {
7
- super(...arguments);
8
- this.platform = 'express';
9
- }
10
- static async create(app, document, options) {
11
- const express = await import('express');
12
- const adapter = new OpraExpressAdapter(document);
13
- await adapter.init(options);
14
- const prefix = '/' + normalizePath(options?.prefix, true);
15
- app.use(prefix, express.json());
16
- app.use(prefix, (req, res, next) => {
17
- adapter.handler(HttpServerRequest.create(req), HttpServerResponse.create(res))
18
- .catch(e => next(e));
19
- });
1
+ import { ExpressAdapterHost } from './express-adapter.host.js';
2
+ /**
3
+ * @namespace
4
+ */
5
+ export var ExpressAdapter;
6
+ (function (ExpressAdapter) {
7
+ async function create(app, api, options) {
8
+ const adapter = new ExpressAdapterHost(app, api, options);
9
+ await adapter.init();
20
10
  return adapter;
21
11
  }
22
- }
12
+ ExpressAdapter.create = create;
13
+ })(ExpressAdapter || (ExpressAdapter = {}));
@@ -0,0 +1,16 @@
1
+ import { PassThrough } from 'stream';
2
+ export function concatReadable(...streams) {
3
+ const out = new PassThrough();
4
+ const pipeNext = () => {
5
+ const nextStream = streams.shift();
6
+ if (nextStream) {
7
+ nextStream.pipe(out, { end: false });
8
+ nextStream.once('end', () => pipeNext());
9
+ }
10
+ else {
11
+ out.end();
12
+ }
13
+ };
14
+ pipeNext();
15
+ return out;
16
+ }
@@ -0,0 +1,91 @@
1
+ import { EventEmitter } from 'events';
2
+ import formidable from 'formidable';
3
+ import fs from 'fs/promises';
4
+ const noOp = () => void 0;
5
+ export class MultipartIterator extends EventEmitter {
6
+ constructor(incoming, options) {
7
+ super();
8
+ this._cancelled = false;
9
+ this._items = [];
10
+ this._stack = [];
11
+ this.setMaxListeners(1000);
12
+ const form = this._form = formidable({
13
+ ...options,
14
+ filter: (part) => {
15
+ return !this._cancelled && (!options?.filter || options.filter(part));
16
+ }
17
+ });
18
+ form.once('error', () => {
19
+ this._cancelled = true;
20
+ if (this.listenerCount('error') > 0)
21
+ this.emit('error');
22
+ });
23
+ form.on('field', (field, value) => {
24
+ const item = { field, value };
25
+ this._items.push(item);
26
+ this._stack.push(item);
27
+ this.emit('item', item);
28
+ });
29
+ form.on('file', (field, file) => {
30
+ const item = { field, file };
31
+ this._items.push(item);
32
+ this._stack.push(item);
33
+ this.emit('item', item);
34
+ });
35
+ form.parse(incoming).catch(noOp);
36
+ }
37
+ get items() {
38
+ return this._items;
39
+ }
40
+ getNext() {
41
+ if (this._form.ended)
42
+ return Promise.resolve(undefined);
43
+ this.resume();
44
+ return new Promise((resolve, reject) => {
45
+ if (this._stack.length)
46
+ return resolve(this._stack.shift());
47
+ this.once('item', () => resolve(this._stack.shift()));
48
+ this.once('error', (e) => reject(e));
49
+ });
50
+ }
51
+ getAll() {
52
+ if (this._form.ended)
53
+ return Promise.resolve([...this._items]);
54
+ this.resume();
55
+ return new Promise((resolve, reject) => {
56
+ this._form.once('error', reject);
57
+ this._form.once('end', () => {
58
+ resolve([...this._items]);
59
+ });
60
+ });
61
+ }
62
+ cancel() {
63
+ this._cancelled = true;
64
+ this.resume();
65
+ }
66
+ resume() {
67
+ this._form.resume();
68
+ }
69
+ pause() {
70
+ this._form.pause();
71
+ }
72
+ async deleteFiles() {
73
+ const promises = [];
74
+ this._items
75
+ .forEach(item => {
76
+ if (!item.file)
77
+ return;
78
+ const file = item.file;
79
+ promises.push(new Promise(resolve => {
80
+ if (file._writeStream.closed)
81
+ return resolve();
82
+ file._writeStream.once('close', resolve);
83
+ }).then(() => {
84
+ return fs.unlink(file.filepath);
85
+ }).then(() => {
86
+ return 0;
87
+ }));
88
+ });
89
+ return Promise.allSettled(promises);
90
+ }
91
+ }
@@ -0,0 +1,12 @@
1
+ import { splitString } from 'fast-tokenizer';
2
+ export function parseArrayParam(v) {
3
+ if (!v)
4
+ return;
5
+ return splitString(v, {
6
+ delimiters: ',',
7
+ quotes: true,
8
+ brackets: true,
9
+ keepBrackets: true,
10
+ keepQuotes: true
11
+ }).map(x => x.trim());
12
+ }
@@ -0,0 +1,123 @@
1
+ import { BadRequestError, HttpHeaderCodes, HttpStatusCodes, InternalServerError, IssueSeverity, OpraException, OpraSchema, wrapException } from '@opra/common';
2
+ import { ExecutionContextHost } from '../execution-context.host.js';
3
+ import { PlatformAdapterHost } from '../platform-adapter.host.js';
4
+ import { EntityRequestHandler } from './request-handlers/entity-request-handler.js';
5
+ import { StorageRequestHandler } from './request-handlers/storage-request-handler.js';
6
+ /**
7
+ *
8
+ * @class HttpAdapterBase
9
+ */
10
+ export class HttpAdapterBase extends PlatformAdapterHost {
11
+ constructor() {
12
+ super(...arguments);
13
+ this._protocol = 'http';
14
+ this._requestHandlers = [
15
+ new EntityRequestHandler(this),
16
+ new StorageRequestHandler(this)
17
+ ];
18
+ }
19
+ /**
20
+ * Main http request handler
21
+ * @param incoming
22
+ * @param outgoing
23
+ * @protected
24
+ */
25
+ async handleIncoming(incoming, outgoing) {
26
+ const context = new ExecutionContextHost(this.api, this.platform, { http: { incoming, outgoing } });
27
+ try {
28
+ /* istanbul ignore next */
29
+ if (!this._initialized)
30
+ throw new InternalServerError(`${Object.getPrototypeOf(this).constructor.name} has not been initialized yet`);
31
+ outgoing.setHeader(HttpHeaderCodes.X_Opra_Version, OpraSchema.SpecVersion);
32
+ // Expose headers if cors enabled
33
+ if (outgoing.getHeader(HttpHeaderCodes.Access_Control_Allow_Origin)) {
34
+ // Expose X-Opra-* headers
35
+ outgoing.appendHeader(HttpHeaderCodes.Access_Control_Expose_Headers, Object.values(HttpHeaderCodes)
36
+ .filter(k => k.toLowerCase().startsWith('x-opra-')));
37
+ }
38
+ await this.processRequest(context);
39
+ }
40
+ catch (error) {
41
+ context.errors.push(wrapException(error));
42
+ }
43
+ // If no response returned to the client we send an error
44
+ if (!outgoing.writableEnded) {
45
+ if (!context.errors.length)
46
+ context.errors.push(new BadRequestError(`Server can not process this request`));
47
+ await this.handleError(context);
48
+ }
49
+ }
50
+ async processRequest(context) {
51
+ try {
52
+ const { incoming, outgoing } = context.switchToHttp();
53
+ if (incoming.method === 'GET' &&
54
+ incoming.parsedUrl.path.length &&
55
+ incoming.parsedUrl.path[0].resource === '$metadata') {
56
+ outgoing.setHeader('content-type', 'application/json');
57
+ outgoing.end(JSON.stringify(this.api.exportSchema({ webSafe: true })));
58
+ return;
59
+ }
60
+ const { parsedUrl } = incoming;
61
+ if (!parsedUrl.path.length) {
62
+ // Batch
63
+ if (incoming.headers['content-type'] === 'multipart/mixed') {
64
+ // todo
65
+ }
66
+ throw new BadRequestError();
67
+ }
68
+ // Iterate through request handlers until one of them sends response (end outgoing stream)
69
+ for (const requestHandler of this._requestHandlers) {
70
+ await requestHandler.processRequest(context);
71
+ if (outgoing.writableEnded)
72
+ return;
73
+ if (context.errors.length) {
74
+ await this.handleError(context);
75
+ return;
76
+ }
77
+ }
78
+ }
79
+ finally {
80
+ await context.emitAsync('finish');
81
+ }
82
+ }
83
+ async handleError(context) {
84
+ const { errors } = context;
85
+ const { outgoing } = context.switchToHttp();
86
+ if (outgoing.headersSent) {
87
+ outgoing.end();
88
+ return;
89
+ }
90
+ errors.forEach(x => {
91
+ // todo. implement a better log mechanism
92
+ if (x instanceof OpraException)
93
+ this._logger.fatal(x);
94
+ else
95
+ this._logger.error(x);
96
+ });
97
+ const wrappedErrors = errors.map(wrapException);
98
+ // Sort errors from fatal to info
99
+ wrappedErrors.sort((a, b) => {
100
+ const i = IssueSeverity.Keys.indexOf(a.issue.severity) - IssueSeverity.Keys.indexOf(b.issue.severity);
101
+ if (i === 0)
102
+ return b.status - a.status;
103
+ return i;
104
+ });
105
+ let status = outgoing.statusCode || 0;
106
+ if (!status || status < Number(HttpStatusCodes.BAD_REQUEST)) {
107
+ status = wrappedErrors[0].status;
108
+ if (status < Number(HttpStatusCodes.BAD_REQUEST))
109
+ status = HttpStatusCodes.INTERNAL_SERVER_ERROR;
110
+ }
111
+ outgoing.statusCode = status;
112
+ const body = this._i18n.deep({
113
+ errors: wrappedErrors.map(x => x.issue)
114
+ });
115
+ outgoing.setHeader(HttpHeaderCodes.Content_Type, 'application/json; charset=utf-8');
116
+ outgoing.setHeader(HttpHeaderCodes.Cache_Control, 'no-cache');
117
+ outgoing.setHeader(HttpHeaderCodes.Pragma, 'no-cache');
118
+ outgoing.setHeader(HttpHeaderCodes.Expires, '-1');
119
+ outgoing.setHeader(HttpHeaderCodes.X_Opra_Version, OpraSchema.SpecVersion);
120
+ outgoing.send(JSON.stringify(body));
121
+ outgoing.end();
122
+ }
123
+ }
@@ -0,0 +1,52 @@
1
+ import http from 'http';
2
+ import { HttpStatusCodes, HttpStatusMessages, OpraURL, OpraURLPath, } from '@opra/common';
3
+ import { HttpAdapterBase } from './http-adapter-base.js';
4
+ import { HttpServerRequest } from './http-server-request.js';
5
+ import { HttpServerResponse } from './http-server-response.js';
6
+ /**
7
+ * @class HttpAdapterHost
8
+ */
9
+ export class HttpAdapterHost extends HttpAdapterBase {
10
+ constructor(api, options) {
11
+ super(api, options);
12
+ this._platform = 'http';
13
+ this._basePath = new OpraURLPath(options?.basePath);
14
+ this._server = http.createServer((incomingMessage, serverResponse) => this._serverListener(incomingMessage, serverResponse));
15
+ }
16
+ get basePath() {
17
+ return this._basePath;
18
+ }
19
+ get server() {
20
+ return this._server;
21
+ }
22
+ _serverListener(incomingMessage, serverResponse) {
23
+ const originalUrl = incomingMessage.url;
24
+ const parsedUrl = new OpraURL(originalUrl);
25
+ const relativePath = OpraURLPath.relative(parsedUrl.path, this.basePath);
26
+ if (!relativePath) {
27
+ serverResponse.statusCode = HttpStatusCodes.NOT_FOUND;
28
+ serverResponse.statusMessage = HttpStatusMessages[HttpStatusCodes.NOT_FOUND];
29
+ serverResponse.end();
30
+ return;
31
+ }
32
+ parsedUrl.path = relativePath;
33
+ incomingMessage.originalUrl = originalUrl;
34
+ incomingMessage.baseUrl = this.basePath.toString();
35
+ incomingMessage.parsedUrl = parsedUrl;
36
+ const req = HttpServerRequest.from(incomingMessage);
37
+ const res = HttpServerResponse.from(serverResponse);
38
+ this.handleIncoming(req, res)
39
+ .catch((e) => this._logger.fatal(e));
40
+ }
41
+ async close() {
42
+ await super.close();
43
+ if (this.server.listening)
44
+ await new Promise((resolve, reject) => {
45
+ this.server.close((err) => {
46
+ if (err)
47
+ return reject(err);
48
+ resolve();
49
+ });
50
+ });
51
+ }
52
+ }
@@ -1,130 +1,13 @@
1
- import { Task } from 'power-tasks';
2
- import { BadRequestError, Collection, HttpHeaderCodes, HttpStatusCodes, InternalServerError, isReadable, IssueSeverity, OpraException, OpraSchema, Singleton, wrapException } from '@opra/common';
3
- import { OpraAdapter } from '../adapter.js';
4
- import { RequestContextHost } from '../request-context.host.js';
5
- import { ResponseHost } from '../response.host.js';
6
- import { parseRequest } from './request-parsers/parse-request.js';
1
+ import { HttpAdapterHost } from './http-adapter.host.js';
7
2
  /**
8
- *
9
- * @class OpraHttpAdapter
3
+ * @namespace HttpAdapter
10
4
  */
11
- export class OpraHttpAdapter extends OpraAdapter {
12
- /**
13
- * Main http request handler
14
- * @param incoming
15
- * @param outgoing
16
- * @protected
17
- */
18
- async handler(incoming, outgoing) {
19
- try {
20
- try {
21
- const request = await parseRequest(this._apiRoot, incoming);
22
- const task = this.createRequestTask(request, outgoing);
23
- await task.toPromise();
24
- }
25
- catch (e) {
26
- if (e instanceof OpraException)
27
- throw e;
28
- throw new BadRequestError(e);
29
- }
30
- }
31
- catch (error) {
32
- await this.handleError(incoming, outgoing, [error]);
33
- }
34
- }
35
- createRequestTask(request, outgoing) {
36
- return new Task(async () => {
37
- const response = new ResponseHost({ http: outgoing });
38
- const context = new RequestContextHost('http', this.platform, this.api, request, response);
39
- return this.executeRequest(context);
40
- }, {
41
- id: request.contentId,
42
- exclusive: request.crud !== 'read'
43
- });
44
- }
45
- async executeRequest(context) {
46
- const { request, response } = context;
47
- await super.executeRequest(context)
48
- .catch(e => {
49
- response.errors = response.errors || [];
50
- response.errors.push(e);
51
- });
52
- const outgoing = response.switchToHttp();
53
- if (response.errors?.length) {
54
- const errors = response.errors.map(e => wrapException(e));
55
- await this.handleError(request.switchToHttp(), outgoing, errors);
56
- return;
57
- }
58
- if (request.resource instanceof Singleton || request.resource instanceof Collection) {
59
- outgoing.setHeader(HttpHeaderCodes.X_Opra_Data_Type, String(request.resource.type.name));
60
- outgoing.setHeader(HttpHeaderCodes.X_Opra_Operation, request.operation);
61
- }
62
- if (request.crud === 'create') {
63
- if (!response.value)
64
- throw new InternalServerError();
65
- // todo validate
66
- outgoing.statusCode = 201;
67
- }
68
- if (request.resource instanceof Collection &&
69
- request.crud === 'read' && request.many && request.args.count >= 0) {
70
- outgoing.setHeader(HttpHeaderCodes.X_Opra_Total_Matches, String(response.count));
71
- }
72
- outgoing.statusCode = outgoing.statusCode || HttpStatusCodes.OK;
73
- outgoing.setHeader(HttpHeaderCodes.Cache_Control, 'no-cache');
74
- outgoing.setHeader(HttpHeaderCodes.Pragma, 'no-cache');
75
- outgoing.setHeader(HttpHeaderCodes.Expires, '-1');
76
- outgoing.setHeader(HttpHeaderCodes.X_Opra_Version, OpraSchema.SpecVersion);
77
- if (response.value) {
78
- if (typeof response.value === 'object') {
79
- if (isReadable(response.value) || Buffer.isBuffer(response.value))
80
- outgoing.send(response.value);
81
- else {
82
- const body = this.i18n.deep(response.value);
83
- outgoing.setHeader(HttpHeaderCodes.Content_Type, 'application/json; charset=utf-8');
84
- outgoing.send(JSON.stringify(body));
85
- }
86
- }
87
- else
88
- outgoing.send(JSON.stringify(response.value));
89
- }
90
- outgoing.end();
91
- }
92
- async handleError(incoming, outgoing, errors) {
93
- errors.forEach(e => {
94
- this.log((e instanceof OpraException) ? 'error' : 'fatal', incoming, e); // todo. implement a better logger
95
- });
96
- errors = errors.map(wrapException);
97
- let status = outgoing.statusCode || 0;
98
- // Sort errors from fatal to info
99
- errors.sort((a, b) => {
100
- const i = IssueSeverity.Keys.indexOf(a.issue.severity) - IssueSeverity.Keys.indexOf(b.issue.severity);
101
- if (i === 0)
102
- return b.status - a.status;
103
- return i;
104
- });
105
- if (!status || status < HttpStatusCodes.BAD_REQUEST) {
106
- status = errors[0].status;
107
- if (status < HttpStatusCodes.BAD_REQUEST)
108
- status = HttpStatusCodes.INTERNAL_SERVER_ERROR;
109
- }
110
- const body = this.i18n.deep({
111
- errors: errors.map(e => e.issue)
112
- });
113
- outgoing.statusCode = status;
114
- outgoing.setHeader(HttpHeaderCodes.Content_Type, 'application/json; charset=utf-8');
115
- outgoing.setHeader(HttpHeaderCodes.Cache_Control, 'no-cache');
116
- outgoing.setHeader(HttpHeaderCodes.Pragma, 'no-cache');
117
- outgoing.setHeader(HttpHeaderCodes.Expires, '-1');
118
- outgoing.setHeader(HttpHeaderCodes.X_Opra_Version, OpraSchema.SpecVersion);
119
- outgoing.send(JSON.stringify(body));
120
- outgoing.end();
121
- }
122
- log(logType, incoming, message, ...optionalParams) {
123
- const logFn = logType === 'fatal'
124
- ? this.logger?.fatal || this.logger?.error
125
- : this.logger?.[logType];
126
- if (!logFn)
127
- return;
128
- logFn.apply(this.logger, [String(message), ...optionalParams]);
129
- }
130
- }
5
+ export var HttpAdapter;
6
+ (function (HttpAdapter) {
7
+ async function create(api, options) {
8
+ const adapter = new HttpAdapterHost(api, options);
9
+ await adapter.init();
10
+ return adapter;
11
+ }
12
+ HttpAdapter.create = create;
13
+ })(HttpAdapter || (HttpAdapter = {}));
@@ -6,22 +6,28 @@ import accepts from 'accepts';
6
6
  import fresh from 'fresh';
7
7
  import parseRange from 'range-parser';
8
8
  import typeIs from 'type-is';
9
- import { isReadable, mergePrototype } from '@opra/common';
10
- import { HttpIncomingMessageHost } from './http-incoming-message-host.js';
9
+ import { isReadable, mergePrototype, OpraURL } from '@opra/common';
10
+ import { HttpIncomingMessageHost } from './impl/http-incoming-message.host.js';
11
11
  function isHttpIncomingMessage(v) {
12
12
  return v && Array.isArray(v.rawHeaders) && isReadable(v);
13
13
  }
14
14
  export var HttpServerRequest;
15
15
  (function (HttpServerRequest) {
16
- function create(instance) {
16
+ function from(instance) {
17
17
  if (!isHttpIncomingMessage(instance))
18
- instance = HttpIncomingMessageHost.create(instance);
18
+ instance = HttpIncomingMessageHost.from(instance);
19
19
  mergePrototype(instance, HttpServerRequestHost.prototype);
20
- return instance;
20
+ const req = instance;
21
+ req.baseUrl = req.baseUrl || '';
22
+ req.parsedUrl = req.parsedUrl || new OpraURL(req.url);
23
+ return req;
21
24
  }
22
- HttpServerRequest.create = create;
25
+ HttpServerRequest.from = from;
23
26
  })(HttpServerRequest || (HttpServerRequest = {}));
24
27
  class HttpServerRequestHost {
28
+ constructor() {
29
+ this.basePath = '/';
30
+ }
25
31
  get protocol() {
26
32
  const proto = this.header('X-Forwarded-Proto') || 'http';
27
33
  const index = proto.indexOf(',');