@h4ad/serverless-adapter 2.0.1 → 2.1.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.
package/README.md CHANGED
@@ -19,7 +19,7 @@
19
19
  [![Semantic Release][semantic-release-img]][semantic-release-url]
20
20
 
21
21
  Run REST APIs and other web applications using your existing Node.js application framework (Express, Koa, Hapi and
22
- Fastify), on top of AWS Lambda, Amazon API Gateway and many other event sources.
22
+ Fastify), on top of AWS Lambda, Huawei and many other clouds.
23
23
 
24
24
  This library was a refactored version of [@vendia/serverless-express](https://github.com/vendia/serverless-express), I
25
25
  created a new way to interact and extend event sources by creating contracts to abstract the integrations between each
@@ -56,7 +56,7 @@ You can quickly use this library as follows:
56
56
 
57
57
  ```ts
58
58
  import { ServerlessAdapter } from '@h4ad/serverless-adapter';
59
- import { ApiGatewayV2Adapter } from '@h4ad/serverless-adapter/lib/adapters/aws';
59
+ import { ApiGatewayV2Adapter, AlbAdapter, SQSAdapter, SNSAdapter } from '@h4ad/serverless-adapter/lib/adapters/aws';
60
60
  import { ExpressFramework } from '@h4ad/serverless-adapter/lib/frameworks/express';
61
61
  import { DefaultHandler } from '@h4ad/serverless-adapter/lib/handlers/default';
62
62
  import { PromiseResolver } from '@h4ad/serverless-adapter/lib/resolvers/promise';
@@ -70,8 +70,6 @@ export const handler = ServerlessAdapter.new(app)
70
70
  .addAdapter(new SQSAdapter())
71
71
  .addAdapter(new SNSAdapter())
72
72
  .build();
73
-
74
- export { handler };
75
73
  ```
76
74
 
77
75
  Too fast? Ok, I can explain.
@@ -140,9 +138,14 @@ We support these event sources:
140
138
  using ([SNSAdapter](./src/adapters/aws/sns.adapter.ts))
141
139
  - [AWS SQS](https://docs.aws.amazon.com/pt_br/lambda/latest/dg/with-sqs.html) by
142
140
  using ([SQSAdapter](./src/adapters/aws/sqs.adapter.ts))
141
+ - Huawei
142
+ - [Http Function](https://support.huaweicloud.com/intl/en-us/usermanual-functiongraph/functiongraph_01_1442.html): Look [this section](#huawei-http-function) about Huawei support for Http Function.
143
+ - [Event Function](https://support.huaweicloud.com/intl/en-us/usermanual-functiongraph/functiongraph_01_1441.html): Work in progress.
143
144
  - Azure
145
+ - [The support is coming soon.](https://github.com/H4ad/serverless-adapter/issues/3)
146
+ - Firebase
144
147
  - The support is coming soon.
145
- - Huawei
148
+ - GCP
146
149
  - The support is coming soon.
147
150
 
148
151
  We support these resolvers:
@@ -154,6 +157,66 @@ We support these resolvers:
154
157
  We support these handlers:
155
158
 
156
159
  - Default by using ([DefaultHandler](src/handlers/default/default.handler.ts))
160
+ - Huawei by using ([HttpHuaweiHandler](src/handlers/huawei/http-huawei.handler.ts)) and ([EventHuaweiHandler](#huawei-event-function))
161
+
162
+ ## Huawei
163
+
164
+ In Huawei, currently we only added support to FunctionGraphV2 with Http Function type.
165
+
166
+ The difference between Http Function and Event Function is that in Http Function you must expose port 8000 and Huawei will proxy Api Gateway requests to your application.
167
+ So, on implementation, this library will create an http server to listen on port 8000 and forward the request to your framework.
168
+
169
+ In Event Function, you will receive the event from event source in the same way you receive in AWS, an object with some structure, you can see the supported event sources [here](https://support.huaweicloud.com/intl/en-us/devg-functiongraph/functiongraph_02_0102.html).
170
+
171
+ ### Huawei Http Function
172
+
173
+ To integrate your app with Huawei FunctionGrapth with the Http Function type, you must do the following:
174
+
175
+ ```ts
176
+ import { ServerlessAdapter } from '@h4ad/serverless-adapter';
177
+ import { ExpressFramework } from '@h4ad/serverless-adapter/lib/frameworks/express';
178
+ import { HttpHuaweiHandler } from '@h4ad/serverless-adapter/lib/handlers/huawei';
179
+ import { DummyResolver } from '@h4ad/serverless-adapter/lib/resolvers/dummy';
180
+ import { DummyAdapter } from '@h4ad/serverless-adapter/lib/adapters/dummy';
181
+ import app from './app';
182
+
183
+ // instead exposing handler, you have the dispose function
184
+ // this dispose function is never called
185
+ // but you can to close the http server created with him
186
+ const dispose = ServerlessAdapter.new(app)
187
+ .setHandler(new HttpHuaweiHandler())
188
+ .setFramework(new ExpressFramework())
189
+ // dummy resolver and adapter is used because
190
+ // they are necessary in the core of the library to build
191
+ // but is optional to make huawei http function works.
192
+ .setResolver(new DummyResolver())
193
+ .addAdapter(new DummyAdapter())
194
+ .build();
195
+ ```
196
+
197
+ > You don't need to expose a variable called `handler` when you choose Http Function, you just need to call build to the library create a http server.
198
+
199
+ By the way of Huawei architecture in Http Function, they have no use for Resolvers and Adapters, so you need to use the dummy versions because the library requires it.
200
+
201
+ #### ONE IMPORTANT THING
202
+
203
+ You need to configure a file called `bootstrap` in the root of folder that you upload to Huawei, is like the file `Procfile` but for Huawei.
204
+
205
+ In my setup, I configure like:
206
+ ```
207
+ node /opt/function/code/index.js
208
+ ```
209
+
210
+ The path `/opt/function/code` is where your code is uploaded when you deploy something and `index.js` is the file that contains the `ServerlessAdapter`.
211
+
212
+ In the end, the structure of the zip file you upload looks like this:
213
+
214
+ - `bootstrap`
215
+ - `index.js`
216
+
217
+ ### Huawei Event Function
218
+
219
+ Work in progress.
157
220
 
158
221
  # Architecture
159
222
 
@@ -207,6 +270,12 @@ PR just to extend the library's functionality :)
207
270
  Honestly, I just refactored all the code that the @vendia team and many other contributors wrote, thanks so much to them
208
271
  for existing and giving us a brilliant library that is the core of my current company.
209
272
 
273
+ # Sponsors
274
+
275
+ | <a href="https://liga.facens.br/"><img height="50" src="https://mlogu6g7z5ex.i.optimole.com/yEwfkqo-4R0ttNtd/w:auto/h:auto/q:mauto/f:avif/http://liga.facens.br/wp-content/uploads/2020/03/logo-1.png" title="The LIGA logo" width="100"/></a> |
276
+ |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
277
+
278
+
210
279
  [build-img]:https://github.com/H4ad/serverless-adapter/actions/workflows/release.yml/badge.svg
211
280
 
212
281
  [build-url]:https://github.com/H4ad/serverless-adapter/actions/workflows/release.yml
@@ -69,6 +69,7 @@ class ApiGatewayV2Adapter {
69
69
  */
70
70
  getResponse({ headers: responseHeaders, body, isBase64Encoded, statusCode, response, }) {
71
71
  const headers = (0, core_1.getFlattenedHeadersMap)(responseHeaders);
72
+ const multiValueHeaders = (0, core_1.getMultiValueHeadersMap)(responseHeaders);
72
73
  const transferEncodingHeader = headers['transfer-encoding'];
73
74
  const hasTransferEncodingChunked = transferEncodingHeader && transferEncodingHeader.includes('chunked');
74
75
  if (hasTransferEncodingChunked) {
@@ -77,16 +78,15 @@ class ApiGatewayV2Adapter {
77
78
  if (response === null || response === void 0 ? void 0 : response.chunkedEncoding) {
78
79
  throw new Error('chunked encoding in response is not supported by API Gateway V1');
79
80
  }
80
- const cookies = headers['set-cookie'];
81
- if (cookies) {
82
- headers.cookies = cookies;
81
+ const cookies = multiValueHeaders['set-cookie'];
82
+ if (headers)
83
83
  delete headers['set-cookie'];
84
- }
85
84
  return {
86
85
  statusCode,
87
86
  body,
88
87
  headers,
89
88
  isBase64Encoded,
89
+ cookies,
90
90
  };
91
91
  }
92
92
  /**
@@ -0,0 +1,27 @@
1
+ import { AdapterContract, AdapterRequest, OnErrorProps } from '../../contracts';
2
+ import { IEmptyResponse } from '../../core';
3
+ /**
4
+ * The class that represents a dummy adapter that does nothing and can be used by the cloud that doesn't use adapters.
5
+ */
6
+ export declare class DummyAdapter implements AdapterContract<any, any, void> {
7
+ /**
8
+ * @inheritDoc
9
+ */
10
+ canHandle(): boolean;
11
+ /**
12
+ * @inheritDoc
13
+ */
14
+ getAdapterName(): string;
15
+ /**
16
+ * @inheritDoc
17
+ */
18
+ getRequest(): AdapterRequest;
19
+ /**
20
+ * @inheritDoc
21
+ */
22
+ getResponse(): IEmptyResponse;
23
+ /**
24
+ * @inheritDoc
25
+ */
26
+ onErrorWhileForwarding(props: OnErrorProps<any, void>): void;
27
+ }
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ //#region Imports
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.DummyAdapter = void 0;
5
+ const core_1 = require("../../core");
6
+ //#endregion
7
+ /**
8
+ * The class that represents a dummy adapter that does nothing and can be used by the cloud that doesn't use adapters.
9
+ */
10
+ class DummyAdapter {
11
+ /**
12
+ * @inheritDoc
13
+ */
14
+ canHandle() {
15
+ return true;
16
+ }
17
+ /**
18
+ * @inheritDoc
19
+ */
20
+ getAdapterName() {
21
+ return DummyAdapter.name;
22
+ }
23
+ /**
24
+ * @inheritDoc
25
+ */
26
+ getRequest() {
27
+ return {
28
+ method: 'POST',
29
+ body: void 0,
30
+ path: '/dummy',
31
+ headers: {},
32
+ };
33
+ }
34
+ /**
35
+ * @inheritDoc
36
+ */
37
+ getResponse() {
38
+ return core_1.EmptyResponse;
39
+ }
40
+ /**
41
+ * @inheritDoc
42
+ */
43
+ onErrorWhileForwarding(props) {
44
+ props.delegatedResolver.succeed();
45
+ }
46
+ }
47
+ exports.DummyAdapter = DummyAdapter;
@@ -0,0 +1 @@
1
+ export * from './dummy.adapter';
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5
+ }) : (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ o[k2] = m[k];
8
+ }));
9
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
10
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
11
+ };
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ __exportStar(require("./dummy.adapter"), exports);
@@ -1,7 +1,6 @@
1
1
  import { BinarySettings, SingleValueHeaders } from '../../@types';
2
2
  import { AdapterContract, AdapterRequest, FrameworkContract, ResolverContract, ServerlessHandler } from '../../contracts';
3
- import { ILogger } from '../../core';
4
- import { BaseHandler } from '../../core/base-handler';
3
+ import { BaseHandler, ILogger } from '../../core';
5
4
  import { ServerlessResponse } from '../../network';
6
5
  /**
7
6
  * The class that implements a default serverless handler consisting of a function with event, context and callback parameters respectively
@@ -16,13 +16,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
16
16
  exports.DefaultHandler = void 0;
17
17
  const util_1 = __importDefault(require("util"));
18
18
  const core_1 = require("../../core");
19
- const base_handler_1 = require("../../core/base-handler");
20
19
  const network_1 = require("../../network");
21
20
  //#endregion
22
21
  /**
23
22
  * The class that implements a default serverless handler consisting of a function with event, context and callback parameters respectively
24
23
  */
25
- class DefaultHandler extends base_handler_1.BaseHandler {
24
+ class DefaultHandler extends core_1.BaseHandler {
26
25
  //#region Public Methods
27
26
  /**
28
27
  * @inheritDoc
@@ -0,0 +1,49 @@
1
+ /// <reference types="node" />
2
+ import * as http from 'http';
3
+ import { RequestListener } from 'http';
4
+ import { BinarySettings } from '../../@types';
5
+ import { AdapterContract, FrameworkContract, HandlerContract, ResolverContract, ServerlessHandler } from '../../contracts';
6
+ import { ILogger } from '../../core';
7
+ /**
8
+ * The default port that huawei will proxy the request to your framework
9
+ *
10
+ * {@link https://support.huaweicloud.com/intl/en-us/ae-ad-1-usermanual-functiongraph/functiongraph_01_1442.html#functiongraph_01_1442__li194597302096 Reference}
11
+ */
12
+ export declare const DEFAULT_HUAWEI_LISTEN_PORT: number;
13
+ /**
14
+ * The options to customize {@link HttpHuaweiHandler}
15
+ */
16
+ export declare type HttpHuaweiHandlerOptions = {
17
+ /**
18
+ * @default {@link DEFAULT_HUAWEI_LISTEN_PORT}
19
+ */
20
+ port?: number;
21
+ /**
22
+ * The factory to create a http server to use to listen huawei requests
23
+ */
24
+ httpServerFactory?: (requestListener: RequestListener) => http.Server;
25
+ };
26
+ /**
27
+ * The class that implements a huawei serverless handler with http function that exposes a http server in specific port.
28
+ *
29
+ * In this Handler, you don't need to specific resolver and adapter, so you can use DummyAdapter and DummyResolver instead.
30
+ *
31
+ * @see https://support.huaweicloud.com/intl/en-us/ae-ad-1-usermanual-functiongraph/functiongraph_01_1442.html#functiongraph_01_1442__li194597302096
32
+ */
33
+ export declare class HttpHuaweiHandler<TApp> implements HandlerContract<TApp, void, void, void, void, Promise<void>> {
34
+ protected readonly options?: HttpHuaweiHandlerOptions | undefined;
35
+ /**
36
+ * Construtor padrão
37
+ */
38
+ constructor(options?: HttpHuaweiHandlerOptions | undefined);
39
+ /**
40
+ * @inheritDoc
41
+ */
42
+ getHandler(app: TApp, framework: FrameworkContract<TApp>, _: AdapterContract<void, void, void>[], __: ResolverContract<void, void, void, void, void>, binarySettings: BinarySettings, respondWithErrors: boolean, log: ILogger): ServerlessHandler<Promise<void>>;
43
+ /**
44
+ * The method that creates the instance of http server
45
+ *
46
+ * @param requestListener O método que lidará com as requisições recebidas
47
+ */
48
+ protected createHttpServer(requestListener: RequestListener): http.Server;
49
+ }
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ //#region Imports
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
6
+ }) : (function(o, m, k, k2) {
7
+ if (k2 === undefined) k2 = k;
8
+ o[k2] = m[k];
9
+ }));
10
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
11
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
12
+ }) : function(o, v) {
13
+ o["default"] = v;
14
+ });
15
+ var __importStar = (this && this.__importStar) || function (mod) {
16
+ if (mod && mod.__esModule) return mod;
17
+ var result = {};
18
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
19
+ __setModuleDefault(result, mod);
20
+ return result;
21
+ };
22
+ Object.defineProperty(exports, "__esModule", { value: true });
23
+ exports.HttpHuaweiHandler = exports.DEFAULT_HUAWEI_LISTEN_PORT = void 0;
24
+ const http = __importStar(require("http"));
25
+ const core_1 = require("../../core");
26
+ //#endregion
27
+ /**
28
+ * The default port that huawei will proxy the request to your framework
29
+ *
30
+ * {@link https://support.huaweicloud.com/intl/en-us/ae-ad-1-usermanual-functiongraph/functiongraph_01_1442.html#functiongraph_01_1442__li194597302096 Reference}
31
+ */
32
+ exports.DEFAULT_HUAWEI_LISTEN_PORT = 8000;
33
+ /**
34
+ * The class that implements a huawei serverless handler with http function that exposes a http server in specific port.
35
+ *
36
+ * In this Handler, you don't need to specific resolver and adapter, so you can use DummyAdapter and DummyResolver instead.
37
+ *
38
+ * @see https://support.huaweicloud.com/intl/en-us/ae-ad-1-usermanual-functiongraph/functiongraph_01_1442.html#functiongraph_01_1442__li194597302096
39
+ */
40
+ class HttpHuaweiHandler {
41
+ //#region Constructor
42
+ /**
43
+ * Construtor padrão
44
+ */
45
+ constructor(options) {
46
+ this.options = options;
47
+ }
48
+ //#endregion
49
+ //#region Public Methods
50
+ /**
51
+ * @inheritDoc
52
+ */
53
+ getHandler(app, framework, _, __, binarySettings, respondWithErrors, log) {
54
+ var _a, _b;
55
+ const requestListener = (req, res) => {
56
+ framework.sendRequest(app, req, res);
57
+ };
58
+ const server = (0, core_1.getDefaultIfUndefined)((_a = this.options) === null || _a === void 0 ? void 0 : _a.httpServerFactory, this.createHttpServer.bind(this))(requestListener);
59
+ const port = (0, core_1.getDefaultIfUndefined)((_b = this.options) === null || _b === void 0 ? void 0 : _b.port, exports.DEFAULT_HUAWEI_LISTEN_PORT);
60
+ server.listen(port, () => {
61
+ log.debug('SERVERLESS_ADAPTER:PROXY: Server started.');
62
+ log.debug(`SERVERLESS_ADAPTER:PROXY: Listening the Huawei Requests in port ${port}`);
63
+ });
64
+ return () => {
65
+ log.debug('SERVERLESS_ADAPTER:PROXY: Disposing the server');
66
+ return new Promise((resolve, reject) => {
67
+ server.close(err => (err ? reject(err) : resolve()));
68
+ });
69
+ };
70
+ }
71
+ //#endregion
72
+ //#region Protected Methods
73
+ /**
74
+ * The method that creates the instance of http server
75
+ *
76
+ * @param requestListener O método que lidará com as requisições recebidas
77
+ */
78
+ createHttpServer(requestListener) {
79
+ return http.createServer(requestListener);
80
+ }
81
+ }
82
+ exports.HttpHuaweiHandler = HttpHuaweiHandler;
@@ -0,0 +1 @@
1
+ export * from './http-huawei.handler';
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5
+ }) : (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ o[k2] = m[k];
8
+ }));
9
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
10
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
11
+ };
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ __exportStar(require("./http-huawei.handler"), exports);
@@ -0,0 +1,10 @@
1
+ import { Resolver, ResolverContract } from '../../contracts';
2
+ /**
3
+ * The class that represents a dummy resolver that does nothing and can be used by the cloud that doesn't use resolvers.
4
+ */
5
+ export declare class DummyResolver implements ResolverContract<any, any, any, any, any> {
6
+ /**
7
+ * @inheritDoc
8
+ */
9
+ createResolver(): Resolver<any, void>;
10
+ }
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ //#region Imports
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.DummyResolver = void 0;
5
+ //#endregion
6
+ /**
7
+ * The class that represents a dummy resolver that does nothing and can be used by the cloud that doesn't use resolvers.
8
+ */
9
+ class DummyResolver {
10
+ /**
11
+ * @inheritDoc
12
+ */
13
+ createResolver() {
14
+ return {
15
+ run: () => Promise.resolve(),
16
+ };
17
+ }
18
+ }
19
+ exports.DummyResolver = DummyResolver;
@@ -29,7 +29,7 @@ export declare class ServerlessAdapter<TApp, TEvent, TContext, TCallback, TRespo
29
29
  /**
30
30
  * Settings for whether the response should be treated as binary or not
31
31
  *
32
- * @default `contentEncodings` and `contentTypes` are set with {@link DEFAULT_BINARY_ENCODINGS} and {@link DEFAULT_BINARY_ENCODINGS}, respectively.
32
+ * @default `contentEncodings` and `contentTypes` are set with {@link DEFAULT_BINARY_ENCODINGS} and {@link DEFAULT_BINARY_CONTENT_TYPES}, respectively.
33
33
  */
34
34
  protected binarySettings: BinarySettings;
35
35
  /**
@@ -31,11 +31,11 @@ class ServerlessAdapter {
31
31
  /**
32
32
  * Settings for whether the response should be treated as binary or not
33
33
  *
34
- * @default `contentEncodings` and `contentTypes` are set with {@link DEFAULT_BINARY_ENCODINGS} and {@link DEFAULT_BINARY_ENCODINGS}, respectively.
34
+ * @default `contentEncodings` and `contentTypes` are set with {@link DEFAULT_BINARY_ENCODINGS} and {@link DEFAULT_BINARY_CONTENT_TYPES}, respectively.
35
35
  */
36
36
  this.binarySettings = {
37
37
  contentEncodings: core_1.DEFAULT_BINARY_ENCODINGS,
38
- contentTypes: core_1.DEFAULT_BINARY_ENCODINGS,
38
+ contentTypes: core_1.DEFAULT_BINARY_CONTENT_TYPES,
39
39
  };
40
40
  /**
41
41
  * Indicates whether the error stack should be included in the response or not
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@h4ad/serverless-adapter",
3
- "version": "2.0.1",
3
+ "version": "2.1.0",
4
4
  "description": "This library enables you to utilize any Cloud or Event Source to respond to web and API requests using your existing Node.js application framework.",
5
5
  "main": "./lib/index.js",
6
6
  "files": [
@@ -46,7 +46,9 @@
46
46
  "hapi",
47
47
  "fastify",
48
48
  "node.js",
49
- "http"
49
+ "http",
50
+ "huawei",
51
+ "functiongraph"
50
52
  ],
51
53
  "bugs": {
52
54
  "url": "https://github.com/H4ad/serverless-adapter/issues"
@@ -57,6 +59,7 @@
57
59
  "@types/body-parser": "^1.19.2",
58
60
  "@types/jest": "^27.4.0",
59
61
  "@types/node": "12.20.43",
62
+ "@types/supertest": "^2.0.12",
60
63
  "@typescript-eslint/eslint-plugin": "^5.12.1",
61
64
  "@typescript-eslint/parser": "^5.12.1",
62
65
  "body-parser": "^1.19.1",
@@ -75,6 +78,7 @@
75
78
  "prettier": "^2.5.1",
76
79
  "semantic-release": "^19.0.2",
77
80
  "stream-mock": "^2.0.5",
81
+ "supertest": "^6.2.3",
78
82
  "ts-jest": "^27.1.3",
79
83
  "ts-node": "^10.4.0",
80
84
  "typescript": "^4.5.5"