@tsed/platform-serverless 7.0.0-alpha.1 → 7.0.0-beta.3

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 (50) hide show
  1. package/jest.config.js +4 -4
  2. package/lib/cjs/builder/PlatformServerless.js +156 -0
  3. package/lib/cjs/builder/PlatformServerless.js.map +1 -0
  4. package/lib/cjs/builder/PlatformServerlessHandler.js +93 -0
  5. package/lib/cjs/builder/PlatformServerlessHandler.js.map +1 -0
  6. package/lib/cjs/domain/ServerlessContext.js +26 -0
  7. package/lib/cjs/domain/ServerlessContext.js.map +1 -0
  8. package/lib/cjs/domain/ServerlessRequest.js +80 -0
  9. package/lib/cjs/domain/ServerlessRequest.js.map +1 -0
  10. package/lib/cjs/domain/ServerlessResponse.js +193 -0
  11. package/lib/cjs/domain/ServerlessResponse.js.map +1 -0
  12. package/lib/cjs/index.js +22 -0
  13. package/lib/cjs/index.js.map +1 -0
  14. package/lib/cjs/utils/getRequestId.js +13 -0
  15. package/lib/cjs/utils/getRequestId.js.map +1 -0
  16. package/lib/cjs/utils/setResponseHeaders.js +35 -0
  17. package/lib/cjs/utils/setResponseHeaders.js.map +1 -0
  18. package/lib/esm/builder/PlatformServerless.js +133 -0
  19. package/lib/esm/builder/PlatformServerless.js.map +1 -0
  20. package/lib/esm/builder/PlatformServerlessHandler.js +90 -0
  21. package/lib/esm/builder/PlatformServerlessHandler.js.map +1 -0
  22. package/lib/esm/domain/ServerlessContext.js +22 -0
  23. package/lib/esm/domain/ServerlessContext.js.map +1 -0
  24. package/lib/esm/domain/ServerlessRequest.js +76 -0
  25. package/lib/esm/domain/ServerlessRequest.js.map +1 -0
  26. package/lib/esm/domain/ServerlessResponse.js +189 -0
  27. package/lib/esm/domain/ServerlessResponse.js.map +1 -0
  28. package/lib/esm/index.js +10 -0
  29. package/lib/esm/index.js.map +1 -0
  30. package/lib/esm/utils/getRequestId.js +9 -0
  31. package/lib/esm/utils/getRequestId.js.map +1 -0
  32. package/lib/esm/utils/setResponseHeaders.js +31 -0
  33. package/lib/esm/utils/setResponseHeaders.js.map +1 -0
  34. package/lib/{builder → types/builder}/PlatformServerless.d.ts +14 -6
  35. package/lib/{builder → types/builder}/PlatformServerlessHandler.d.ts +2 -2
  36. package/lib/{domain → types/domain}/ServerlessContext.d.ts +0 -0
  37. package/lib/{domain → types/domain}/ServerlessRequest.d.ts +0 -0
  38. package/lib/{domain → types/domain}/ServerlessResponse.d.ts +1 -1
  39. package/lib/{index.d.ts → types/index.d.ts} +1 -2
  40. package/lib/types/utils/getRequestId.d.ts +2 -0
  41. package/lib/{utils → types/utils}/setResponseHeaders.d.ts +0 -0
  42. package/package.json +20 -15
  43. package/readme.md +271 -54
  44. package/tsconfig.compile.esm.json +23 -0
  45. package/lib/decorators/lambda.d.ts +0 -58
  46. package/lib/index.js +0 -1020
  47. package/lib/index.js.map +0 -1
  48. package/lib/index.modern.js +0 -978
  49. package/lib/index.modern.js.map +0 -1
  50. package/lib/services/PlatformServerlessTest.d.ts +0 -40
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ServerlessResponse.js","sourceRoot":"","sources":["../../../src/domain/ServerlessResponse.ts"],"names":[],"mappings":"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","sourcesContent":["import {getValue} from \"@tsed/core\";\nimport {getStatusMessage} from \"@tsed/schema\";\nimport type {APIGatewayEventDefaultAuthorizerContext, APIGatewayProxyEventBase, Context} from \"aws-lambda\";\nimport encodeUrl from \"encodeurl\";\nimport * as mime from \"mime\";\nimport type {ServerlessRequest} from \"./ServerlessRequest\";\n\nexport interface ServerlessResponseOptions {\n event: APIGatewayProxyEventBase<APIGatewayEventDefaultAuthorizerContext>;\n context: Context;\n request: ServerlessRequest;\n}\n\nexport type HeaderValue = boolean | number | string;\n\n/**\n * @platform\n */\nexport class ServerlessResponse {\n readonly event: APIGatewayProxyEventBase<APIGatewayEventDefaultAuthorizerContext>;\n readonly request: ServerlessRequest;\n\n #status: number = 200;\n #body: any = undefined;\n #headers: Record<string, HeaderValue> = {};\n #locals: Record<string, any> = {};\n #isHeadersSent = false;\n #endListeners: Function[] = [];\n\n constructor({event, request}: ServerlessResponseOptions) {\n this.event = event;\n this.request = request;\n }\n\n /**\n * Get the current statusCode\n */\n get statusCode() {\n return this.#status;\n }\n\n set statusCode(status) {\n this.#status = status;\n }\n\n /**\n * An object that contains response local variables scoped to the request, and therefore available only to the view(s) rendered during that request / response cycle (if any). Otherwise, this property is identical to app.locals.\n *\n * This property is useful for exposing request-level information such as the request path name, authenticated user, user settings, and so on.\n */\n get locals() {\n return this.#locals;\n }\n\n get isHeadersSent() {\n return this.#isHeadersSent;\n }\n\n getStatus() {\n return this.#status;\n }\n\n /**\n * Sets the HTTP status for the response.\n *\n * @param status\n */\n status(status: number) {\n this.#status = status;\n return this;\n }\n\n set(name: string, value: any) {\n return this.setHeader(name, value);\n }\n\n /**\n * Returns the HTTP response header specified by field. The match is case-insensitive.\n *\n * ```typescript\n * response.get('Content-Type') // => \"text/plain\"\n * ```\n *\n * @param name\n */\n get(name: string): string | number | boolean | undefined {\n return getValue(this.#headers, name.toLowerCase());\n }\n\n getHeaders(): Record<string, HeaderValue> {\n return {\n ...this.#headers\n };\n }\n\n hasStatus() {\n return this.#status !== 200;\n }\n\n /**\n * Set header `field` to `val`, or pass\n * an object of header fields.\n *\n * Examples:\n * ```typescript\n * response.setHeaders({ Accept: 'text/plain', 'X-API-Key': 'tobi' });\n * ```\n *\n * Aliased as `res.header()`.\n */\n setHeaders(headers: Record<string, HeaderValue>) {\n // apply headers\n Object.entries(headers).forEach(([key, item]) => {\n this.setHeader(key, item);\n });\n\n return this;\n }\n\n setHeader(key: string, item: HeaderValue | null | undefined) {\n if (item !== null && item !== undefined) {\n key = key.toLowerCase();\n\n if (key === \"location\") {\n // \"back\" is an alias for the referrer\n if (item === \"back\") {\n item = this.request.get(\"Referrer\") || \"/\";\n }\n\n item = encodeUrl(String(item));\n }\n\n this.#headers = {\n ...this.#headers,\n [key.toLowerCase()]: item as any\n };\n }\n\n return this;\n }\n\n /**\n * Set `Content-Type` response header with `type` through `mime.lookup()`\n * when it does not contain \"/\", or set the Content-Type to `type` otherwise.\n *\n * Examples:\n *\n * res.type('.html');\n * res.type('html');\n * res.type('json');\n * res.type('application/json');\n * res.type('png');\n */\n contentType(contentType: string) {\n this.setHeader(\"Content-Type\", mime.getType(contentType));\n\n return this;\n }\n\n contentLength(length: number) {\n this.setHeader(\"Content-Length\", length);\n return this;\n }\n\n getContentLength() {\n if (this.get(\"Content-Length\")) {\n return parseInt(this.get(\"Content-Length\") as any, 10) || 0;\n }\n }\n\n getContentType() {\n return String(this.get(\"Content-Type\") || \"\").split(\";\")[0];\n }\n\n /**\n * Redirects to the URL derived from the specified path, with specified status, a positive integer that corresponds to an [HTTP status code](http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html).\n * If not specified, status defaults to `302 Found`.\n *\n * @param status\n * @param url\n */\n redirect(status: number, url: string) {\n // Set location header\n const address = this.location(url).get(\"Location\");\n const body = `${getStatusMessage(status)}. Redirecting to ${address}`;\n\n this.status(status).set(\"Content-Length\", Buffer.byteLength(body)).body(body);\n\n return this;\n }\n\n /**\n * Sets the response Location HTTP header to the specified path parameter.\n *\n * @param url\n */\n location(url: string) {\n return this.set(\"Location\", url);\n }\n\n /**\n * Send any data to your consumer.\n *\n * This method accept a ReadableStream, a plain object, boolean, string, number, null and undefined data.\n * It choose the better way to send the data.\n *\n * @param body\n */\n body(body: any) {\n this.#body = body;\n\n return this;\n }\n\n getBody() {\n return this.#body;\n }\n\n isDone(): boolean {\n return this.#isHeadersSent;\n }\n\n onEnd(cb: Function) {\n this.#endListeners.push(cb);\n\n return this;\n }\n\n destroy() {\n this.#endListeners.forEach((cb: Function) => cb());\n this.#isHeadersSent = true;\n }\n}\n"]}
@@ -0,0 +1,10 @@
1
+ export * from "@tsed/di";
2
+ export { Get, Post, Put, Delete, Head, Patch, Options, Route as Lambda } from "@tsed/schema";
3
+ export * from "@tsed/platform-params";
4
+ export * from "./builder/PlatformServerlessHandler";
5
+ export * from "./builder/PlatformServerless";
6
+ // decorators
7
+ export * from "./domain/ServerlessContext";
8
+ export * from "./domain/ServerlessResponse";
9
+ export * from "./domain/ServerlessResponse";
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,OAAO,EAAC,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,IAAI,MAAM,EAAC,MAAM,cAAc,CAAC;AAC3F,cAAc,uBAAuB,CAAC;AAEtC,cAAc,qCAAqC,CAAC;AACpD,cAAc,8BAA8B,CAAC;AAE7C,aAAa;AACb,cAAc,4BAA4B,CAAC;AAC3C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,6BAA6B,CAAC","sourcesContent":["export * from \"@tsed/di\";\nexport {Get, Post, Put, Delete, Head, Patch, Options, Route as Lambda} from \"@tsed/schema\";\nexport * from \"@tsed/platform-params\";\n\nexport * from \"./builder/PlatformServerlessHandler\";\nexport * from \"./builder/PlatformServerless\";\n\n// decorators\nexport * from \"./domain/ServerlessContext\";\nexport * from \"./domain/ServerlessResponse\";\nexport * from \"./domain/ServerlessResponse\";\n"]}
@@ -0,0 +1,9 @@
1
+ import { v4 } from "uuid";
2
+ export function getRequestId(event, context) {
3
+ var _a;
4
+ if ((event === null || event === void 0 ? void 0 : event.headers) && event.headers["x-request-id"]) {
5
+ return event.headers["x-request-id"];
6
+ }
7
+ return ((_a = event === null || event === void 0 ? void 0 : event.requestContext) === null || _a === void 0 ? void 0 : _a.requestId) || (context === null || context === void 0 ? void 0 : context.awsRequestId) || v4().replace(/-/gi, "");
8
+ }
9
+ //# sourceMappingURL=getRequestId.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getRequestId.js","sourceRoot":"","sources":["../../../src/utils/getRequestId.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,EAAE,EAAC,MAAM,MAAM,CAAC;AAExB,MAAM,UAAU,YAAY,CAAC,KAAwE,EAAE,OAAgB;;IACrH,IAAI,CAAA,KAAK,aAAL,KAAK,uBAAL,KAAK,CAAE,OAAO,KAAI,KAAK,CAAC,OAAO,CAAC,cAAc,CAAC,EAAE;QACnD,OAAO,KAAK,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;KACtC;IAED,OAAO,CAAA,MAAA,KAAK,aAAL,KAAK,uBAAL,KAAK,CAAE,cAAc,0CAAE,SAAS,MAAI,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,YAAY,CAAA,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AAC9F,CAAC","sourcesContent":["import {APIGatewayEventDefaultAuthorizerContext, APIGatewayProxyEventBase, Context} from \"aws-lambda\";\nimport {v4} from \"uuid\";\n\nexport function getRequestId(event: APIGatewayProxyEventBase<APIGatewayEventDefaultAuthorizerContext>, context: Context) {\n if (event?.headers && event.headers[\"x-request-id\"]) {\n return event.headers[\"x-request-id\"];\n }\n\n return event?.requestContext?.requestId || context?.awsRequestId || v4().replace(/-/gi, \"\");\n}\n"]}
@@ -0,0 +1,31 @@
1
+ function mergeHeaders(specHeaders, headers) {
2
+ return Object.entries(specHeaders).reduce((headers, [key, item]) => {
3
+ key = key.toLowerCase();
4
+ return {
5
+ ...headers,
6
+ [key]: headers[key] === undefined ? String(item.example) : headers[key]
7
+ };
8
+ }, headers);
9
+ }
10
+ /**
11
+ * @ignore
12
+ */
13
+ export function setResponseHeaders(ctx) {
14
+ const { response, endpoint } = ctx;
15
+ const operation = endpoint.operation;
16
+ if (response.isDone()) {
17
+ return;
18
+ }
19
+ if (!response.hasStatus()) {
20
+ // apply status only if the isn't already modified
21
+ response.status(operation.getStatus());
22
+ }
23
+ const statusCode = response.statusCode;
24
+ const headers = operation.getHeadersOf(response.statusCode);
25
+ const mergedHeaders = mergeHeaders(headers, response.getHeaders());
26
+ response.setHeaders(mergedHeaders);
27
+ if (operation.isRedirection(statusCode)) {
28
+ response.redirect(statusCode, String(mergedHeaders["location"]));
29
+ }
30
+ }
31
+ //# sourceMappingURL=setResponseHeaders.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setResponseHeaders.js","sourceRoot":"","sources":["../../../src/utils/setResponseHeaders.ts"],"names":[],"mappings":"AAIA,SAAS,YAAY,CAAC,WAA2D,EAAE,OAAoC;IACrH,OAAO,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,EAAE;QACjE,GAAG,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC;QACxB,OAAO;YACL,GAAG,OAAO;YACV,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,GAAG,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC;SACxE,CAAC;IACJ,CAAC,EAAE,OAAO,CAAC,CAAC;AACd,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAAC,GAAsB;IACvD,MAAM,EAAC,QAAQ,EAAE,QAAQ,EAAC,GAAG,GAAG,CAAC;IACjC,MAAM,SAAS,GAAG,QAAQ,CAAC,SAAU,CAAC;IAEtC,IAAI,QAAQ,CAAC,MAAM,EAAE,EAAE;QACrB,OAAO;KACR;IAED,IAAI,CAAC,QAAQ,CAAC,SAAS,EAAE,EAAE;QACzB,kDAAkD;QAClD,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,SAAS,EAAE,CAAC,CAAC;KACxC;IAED,MAAM,UAAU,GAAG,QAAQ,CAAC,UAAU,CAAC;IACvC,MAAM,OAAO,GAAG,SAAS,CAAC,YAAY,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;IAC5D,MAAM,aAAa,GAAG,YAAY,CAAC,OAAO,EAAE,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;IAEnE,QAAQ,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;IAEnC,IAAI,SAAS,CAAC,aAAa,CAAC,UAAU,CAAC,EAAE;QACvC,QAAQ,CAAC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;KAClE;AACH,CAAC","sourcesContent":["import {JsonHeader} from \"@tsed/schema\";\nimport {ServerlessContext} from \"../domain/ServerlessContext\";\nimport {HeaderValue} from \"../domain/ServerlessResponse\";\n\nfunction mergeHeaders(specHeaders: Record<string, JsonHeader & {example: string}>, headers: Record<string, HeaderValue>) {\n return Object.entries(specHeaders).reduce((headers, [key, item]) => {\n key = key.toLowerCase();\n return {\n ...headers,\n [key]: headers[key] === undefined ? String(item.example) : headers[key]\n };\n }, headers);\n}\n\n/**\n * @ignore\n */\nexport function setResponseHeaders(ctx: ServerlessContext) {\n const {response, endpoint} = ctx;\n const operation = endpoint.operation!;\n\n if (response.isDone()) {\n return;\n }\n\n if (!response.hasStatus()) {\n // apply status only if the isn't already modified\n response.status(operation.getStatus());\n }\n\n const statusCode = response.statusCode;\n const headers = operation.getHeadersOf(response.statusCode);\n const mergedHeaders = mergeHeaders(headers, response.getHeaders());\n\n response.setHeaders(mergedHeaders);\n\n if (operation.isRedirection(statusCode)) {\n response.redirect(statusCode, String(mergedHeaders[\"location\"]));\n }\n}\n"]}
@@ -1,23 +1,31 @@
1
1
  import { Type } from "@tsed/core";
2
2
  import { InjectorService } from "@tsed/di";
3
- import type { APIGatewayProxyHandler } from "aws-lambda";
3
+ import type { Instance } from "find-my-way";
4
+ import type { Handler } from "aws-lambda";
5
+ export interface PlatformServerlessSettings extends Partial<TsED.Configuration> {
6
+ lambda?: Type[];
7
+ }
4
8
  /**
5
9
  * @platform
6
10
  */
7
11
  export declare class PlatformServerless {
8
- #private;
9
12
  readonly name: string;
13
+ private _injector;
14
+ private _router;
15
+ private _promise;
10
16
  get injector(): InjectorService;
11
17
  get settings(): TsED.Configuration & import("@tsed/di").DIConfiguration;
12
- get promise(): Promise<void>;
18
+ get promise(): Promise<any>;
13
19
  static bootstrap(settings?: Partial<TsED.Configuration> & {
14
20
  lambda?: Type[];
15
21
  }): PlatformServerless;
16
- callbacks(tokens?: Type | Type[], exports?: {}): Record<string, APIGatewayProxyHandler>;
17
- callback(token: Type<any>, propertyKey: string): APIGatewayProxyHandler;
22
+ handler(): Handler;
23
+ callbacks(tokens?: Type | Type[], callbacks?: any): Record<string, Handler>;
18
24
  ready(): Promise<void>;
19
25
  stop(): Promise<void>;
20
- protected bootstrap(settings?: Partial<TsED.Configuration>): this;
26
+ init(): Promise<any>;
27
+ protected callback(token: Type<any>, propertyKey: string): Handler;
28
+ protected initRouter(): Promise<Instance<any>>;
21
29
  protected createInjector(settings: any): this;
22
30
  protected loadInjector(): Promise<void>;
23
31
  }
@@ -1,11 +1,11 @@
1
1
  import { InjectorService, TokenProvider } from "@tsed/di";
2
- import { PlatformExceptions } from "@tsed/platform-exceptions";
3
2
  import { PlatformParams } from "@tsed/platform-params";
3
+ import type { PlatformExceptions } from "@tsed/platform-exceptions";
4
4
  import { ServerlessContext } from "../domain/ServerlessContext";
5
5
  export declare class PlatformServerlessHandler {
6
6
  protected injector: InjectorService;
7
7
  protected params: PlatformParams;
8
- protected exceptions: PlatformExceptions;
8
+ protected exceptionsManager: Promise<PlatformExceptions>;
9
9
  createHandler(token: TokenProvider, propertyKey: string | symbol): ($ctx: ServerlessContext) => Promise<{
10
10
  statusCode: number;
11
11
  body: any;
@@ -25,6 +25,7 @@ export declare class ServerlessResponse {
25
25
  * This property is useful for exposing request-level information such as the request path name, authenticated user, user settings, and so on.
26
26
  */
27
27
  get locals(): Record<string, any>;
28
+ get isHeadersSent(): boolean;
28
29
  getStatus(): number;
29
30
  /**
30
31
  * Sets the HTTP status for the response.
@@ -101,5 +102,4 @@ export declare class ServerlessResponse {
101
102
  isDone(): boolean;
102
103
  onEnd(cb: Function): this;
103
104
  destroy(): void;
104
- isHeadersSent(): boolean;
105
105
  }
@@ -1,9 +1,8 @@
1
1
  export * from "@tsed/di";
2
+ export { Get, Post, Put, Delete, Head, Patch, Options, Route as Lambda } from "@tsed/schema";
2
3
  export * from "@tsed/platform-params";
3
4
  export * from "./builder/PlatformServerlessHandler";
4
5
  export * from "./builder/PlatformServerless";
5
- export * from "./services/PlatformServerlessTest";
6
- export * from "./decorators/lambda";
7
6
  export * from "./domain/ServerlessContext";
8
7
  export * from "./domain/ServerlessResponse";
9
8
  export * from "./domain/ServerlessResponse";
@@ -0,0 +1,2 @@
1
+ import { APIGatewayEventDefaultAuthorizerContext, APIGatewayProxyEventBase, Context } from "aws-lambda";
2
+ export declare function getRequestId(event: APIGatewayProxyEventBase<APIGatewayEventDefaultAuthorizerContext>, context: Context): string;
package/package.json CHANGED
@@ -1,34 +1,39 @@
1
1
  {
2
2
  "name": "@tsed/platform-serverless",
3
- "version": "7.0.0-alpha.1",
3
+ "version": "7.0.0-beta.3",
4
4
  "description": "Platform serverless module for Ts.ED Framework",
5
5
  "private": false,
6
6
  "source": "./src/index.ts",
7
- "main": "./lib/index.js",
8
- "typings": "./lib/index.d.ts",
7
+ "main": "./lib/cjs/index.js",
8
+ "typings": "./lib/types/index.d.ts",
9
9
  "exports": {
10
- "require": "./lib/index.js",
11
- "default": "./lib/index.modern.js"
10
+ "types": "./lib/types/index.d.ts",
11
+ "import": "./lib/esm/index.js",
12
+ "require": "./lib/cjs/index.js",
13
+ "default": "./lib/esm/index.js"
12
14
  },
13
15
  "scripts": {
14
- "build": "microbundle --target node --no-compress --format modern,cjs --tsconfig ./tsconfig.compile.json",
16
+ "build": "yarn run build:esm && yarn run build:cjs",
17
+ "build:cjs": "tsc --build tsconfig.compile.json",
18
+ "build:esm": "tsc --build tsconfig.compile.esm.json",
15
19
  "test": "cross-env NODE_ENV=test jest"
16
20
  },
17
21
  "dependencies": {
18
- "@tsed/core": "7.0.0-alpha.1",
19
- "@tsed/di": "7.0.0-alpha.1",
20
- "@tsed/exceptions": "7.0.0-alpha.1",
21
- "@tsed/json-mapper": "7.0.0-alpha.1",
22
- "@tsed/logger": "^5.17.0",
23
- "@tsed/platform-exceptions": "7.0.0-alpha.1",
24
- "@tsed/platform-params": "7.0.0-alpha.1",
25
- "@tsed/schema": "7.0.0-alpha.1",
22
+ "@tsed/core": "7.0.0-beta.3",
23
+ "@tsed/di": "7.0.0-beta.3",
24
+ "@tsed/exceptions": "7.0.0-beta.3",
25
+ "@tsed/json-mapper": "7.0.0-beta.3",
26
+ "@tsed/logger": "^6.0.0",
27
+ "@tsed/platform-exceptions": "7.0.0-beta.3",
28
+ "@tsed/platform-params": "7.0.0-beta.3",
29
+ "@tsed/schema": "7.0.0-beta.3",
26
30
  "encodeurl": "^1.0.2",
27
31
  "mime": "^2.5.2",
28
32
  "tslib": "2.2.0",
29
33
  "uuid": "^8.3.2"
30
34
  },
31
35
  "devDependencies": {
36
+ "@tsed/platform-serverless-testing": "7.0.0-beta.3",
32
37
  "@types/aws-lambda": "^8.10.83",
33
38
  "@types/encodeurl": "^1.0.0",
34
39
  "@types/mime": "^2.0.3"
@@ -38,7 +43,7 @@
38
43
  "bugs": {
39
44
  "url": "https://github.com/tsedio/tsed/issues"
40
45
  },
41
- "homepage": "https://github.com/tsedio/tsed/tree/alpha/packages/platform/platform-serverless",
46
+ "homepage": "https://github.com/tsedio/tsed/tree/beta/packages/platform/platform-serverless",
42
47
  "author": "Romain Lenzotti",
43
48
  "license": "MIT"
44
49
  }
package/readme.md CHANGED
@@ -36,16 +36,94 @@ This package allows the creation of pure Lambda AWS without mounting an Express.
36
36
  It supports:
37
37
 
38
38
  - Creating multiple lambda in one file,
39
+ - Support routing,
39
40
  - DI injection with `@tsed/di`,
40
41
  - Models mapping using `@tsed/schema` and `@tsed/json-mapper`,
41
42
  - Params decorators can be used from `@tsed/platform-params` to get Query, Body, etc...
43
+ - Response can be modified by using `@tsed/platform-response-filter`,
44
+ - All error can be handled by using `@tsed/platform-exceptions`,
42
45
  - Operation descriptions like `@Returns`,
43
46
  - `@tsed/async-hook-context` to inject Context anywhere in your class!
47
+ - All ORM already available for other Ts.ED platform.
48
+
49
+ ## Unsupported features
50
+
51
+ Some features that you can use with the Express.js or Koa.js platforms are not available with the Serverless
52
+ platform.
53
+
54
+ (See also our [table feature capabilities](https://tsed.io/getting-started/#platform-features-support)).
55
+
56
+ ### Middlewares
57
+
58
+ This mechanism is specific to Koa.js / Express.js and doesn't exist with the Serverless approach.
59
+
60
+ Serverless provides a number of features such as Cors management via the configuration of `serverless.yml` and doesn't
61
+ need to use middleware. In addition, serverless [plugins](https://www.serverless.com/plugins/) are also available and
62
+ allow you to manage advanced scenarios without having to develop anything!
63
+
64
+ If you can't find what you are looking for on the Serverless side, you can use `@tsed/di`'
65
+ s [Interceptors](/docs/interceptors.md) to decorate the methods and add shareable features.
66
+
67
+ ### Passport/OIDC/GraphQL etc...
68
+
69
+ Because middlewares aren't supported Passport.js, OIDC, GraphQL won't be usable in this platform either.
70
+
71
+ ### Upload files
72
+
73
+ File upload isn't covered at this time. Any help is welcome to improve this platform :).
74
+
75
+ ### Statics files
76
+
77
+ The goal of lambda isn't to expose static files. We do not plan to support this use in the near future.
78
+
79
+ ## Rule
80
+
81
+ ::: warning By convention, try to not import something from `@tsed/common`. `@tsed/common` embed a lot of codes designed
82
+ for the Full server platform which are not necessary in the Serverless context and aren't optimized for it.
83
+
84
+ The recent version of Ts.ED expose all necessary decorators from `@tsed/schema`, `@tsed/platform-params` or `@tsed/di`.
85
+ For example, @@Get@@ or @@Post@@ are commonly imported like this:
86
+
87
+ ```typescript
88
+ import {Get} from "@tsed/common";
89
+ ```
90
+
91
+ Now, you have to import the decorator from `@tsed/schema`.
92
+
93
+ ```typescript
94
+ import {Get} from "@tsed/schema";
95
+ ```
96
+
97
+ :::
44
98
 
45
99
  ## Installation
46
100
 
101
+ Generate a new project with the CLI (you can also start from an existing project):
102
+
103
+ ```bash
104
+ tsed init .
105
+ ? Choose the target platform: Express.js
106
+ ? Choose the architecture for your project: Ts.ED
107
+ ? Choose the convention file styling: Ts.ED
108
+ ? Check the features needed for your project Swagger, Testing, Linter
109
+ ? Choose unit framework Jest
110
+ ? Choose linter tools framework EsLint
111
+ ? Choose extra linter tools Prettier, Lint on commit
112
+ ? Choose the package manager: Yarn
113
+ ```
114
+
115
+ This tutorial works also with NPM package manager!
116
+
117
+ Yarn:
118
+ ```bash
119
+ yarn add @tsed/platform-serverless serverless serverless-offline
120
+ yarn add -D @types/aws-lambda
121
+ ```
122
+
123
+ Npm:
47
124
  ```bash
48
- npm install --save @tsed/platform-serverless
125
+ npm install --save @tsed/platform-serverless serverless serverless-offline
126
+ npm install --save-dev @types/aws-lambda
49
127
  ```
50
128
 
51
129
  ## Configuration
@@ -53,33 +131,33 @@ npm install --save @tsed/platform-serverless
53
131
  In the `src/lambda` create a new Lambda class:
54
132
 
55
133
  ```typescript
56
- import {Injectable} from "@tsed/di";
57
- import {Returns, Summary} from "@tsed/schema";
58
- import {QueryParams} from "@tsed/platform-serverless"; // /!\ don't import decorators from @tsed/common
134
+ import {Controller, Inject} from "@tsed/di";
135
+ import {Get, Returns, Summary} from "@tsed/schema";
136
+ import {QueryParams} from "@tsed/platform-params"; // /!\ don't import decorators from @tsed/common
59
137
  import {TimeslotsService} from "../services/TimeslotsService";
60
138
  import {TimeslotModel} from "../models/TimeslotModel";
61
139
 
62
- @Injectable()
140
+ @Controller("/timeslots")
63
141
  export class TimeslotsLambda {
64
142
  @Inject()
65
143
  protected timeslotsService: TimeslotsService;
66
144
 
67
- @Lambda()
145
+ @Get("/")
68
146
  @Summary("Return a list of timeslots")
69
147
  @Returns(200, Array).Of(TimeslotModel)
70
148
  get(@QueryParams("date_start") dateStart: Date, @QueryParams("date_end") dateEnd: Date) {
71
149
  return this.timeslotsService.find({
72
150
  dateStart,
73
151
  dateEnd
74
- })
152
+ });
75
153
  }
76
154
  }
77
155
  ```
78
156
 
79
- Create new handler.ts to expose your lambda:
157
+ Create new `handler.ts` to expose your lambda:
80
158
 
81
159
  ```typescript
82
- import {PlatformServerless} from "@tsed/serverless";
160
+ import {PlatformServerless} from "@tsed/platform-serverless";
83
161
  import {TimeslotsLambda} from "./lambda/TimeslotsLambda";
84
162
 
85
163
  const platform = PlatformServerless.bootstrap({
@@ -93,70 +171,124 @@ const platform = PlatformServerless.bootstrap({
93
171
  export = platform.callbacks();
94
172
  ```
95
173
 
96
- Finally, create the serverless.yml:
174
+ Finally, create the `serverless.yml`:
97
175
 
98
176
  ```yml
99
- # Welcome to Serverless!
100
- #
101
- # This file is the main config file for your service.
102
- # It's very minimal at this point and uses default values.
103
- # You can always add more config options for more control.
104
- # We've included some commented out config examples here.
105
- # Just uncomment any of them to get that config option.
106
- #
107
- # For full config options, check the docs:
108
- # docs.serverless.com
109
- #
110
- # Happy Coding!
177
+ service: timeslots
178
+
179
+ frameworkVersion: '2'
180
+
181
+ provider:
182
+ name: aws
183
+ runtime: nodejs14.x
184
+ lambdaHashingVersion: '20201221'
185
+
186
+ plugins:
187
+ - serverless-offline
188
+
189
+ functions:
190
+ timeslots:
191
+ handler: dist/handler.getTimeslots
192
+ events:
193
+ - http:
194
+ path: /timeslots
195
+ method: get
196
+ ```
197
+
198
+ ## Invoke a lambda with serverless
199
+
200
+ Serverless provide a plugin named `serverless-offline`. This Serverless plugin emulates AWS λ and API Gateway on your
201
+ local machine to speed up your development cycles. To do so, it starts an HTTP server that handles the request's
202
+ lifecycle like API does and invokes your handlers.
203
+
204
+ So, by using the `serverless offline` command, we'll be able to invoke our function. For that, we need also to build our
205
+ code before invoke the lambda.
206
+
207
+ To simplify our workflow, we can add the following npm script command in our `package.json`:
208
+
209
+ ```json
210
+ {
211
+ "scripts": {
212
+ "invoke:timeslots": "yarn build && serverless invoke local -f timeslots"
213
+ }
214
+ }
215
+ ```
216
+
217
+ Now, we can run the following command to invoke our lambda:
218
+
219
+ ```
220
+ yarn invoke:timeslots
221
+ // OR
222
+ npm run invoke:timeslots
223
+ ```
224
+
225
+ You should see in the terminal the following result:
226
+
227
+ ```json
228
+ {
229
+ "statusCode": 200,
230
+ "body": "[{\"id\":\"b6de4fc7-faaa-4cd7-a144-42f6af0dec6b\",\"title\":\"title\",\"description\":\"description\",\"start_date\":\"2021-10-29T10:40:57.019Z\",\"end_date\":\"2021-10-29T10:40:57.019Z\",\"created_at\":\"2021-10-29T10:40:57.019Z\",\"update_at\":\"2021-10-29T10:40:57.019Z\"}]",
231
+ "headers": {
232
+ "content-type": "application/json",
233
+ "x-request-id": "ebb52d5e-113b-40da-b34e-c14811df596b"
234
+ },
235
+ "isBase64Encoded": false
236
+ }
237
+ ```
238
+
239
+ ## Manage routes from code
240
+
241
+ Declaring all routes in the `serverless.yml` file can be a source of error. `@tsed/platform-serverless` can handle all
242
+ routes and call the right lambda based on the decorators like `@Get`, `@Post`, etc...
111
243
 
244
+ To use the embed router, change the `serverless.yml` declaration by this example:
245
+
246
+ ```yml
112
247
  service: timeslots
113
- # app and org for use with dashboard.serverless.com
114
- #app: your-app-name
115
- #org: your-org-name
116
248
 
117
- # You can pin your service to only deploy with a specific Serverless version
118
- # Check out our docs for more details
119
249
  frameworkVersion: '2'
120
250
 
121
251
  provider:
122
252
  name: aws
123
- runtime: nodejs12.x
124
- lambdaHashingVersion: 20201221
253
+ runtime: nodejs14.x
254
+ lambdaHashingVersion: '20201221'
255
+
256
+ plugins:
257
+ - serverless-offline
125
258
 
126
259
  functions:
127
- timeslots:
128
- handler: handler.getTimeslots
260
+ any:
261
+ handler: dist/handler.handler
129
262
  events:
130
263
  - http:
131
- path: /timeslots
132
- method: get
133
- cors: true
264
+ method: ANY
265
+ path: /
266
+ - http:
267
+ method: ANY
268
+ path: '{proxy+}'
134
269
  ```
135
270
 
136
- ## Multiple lambda
137
-
138
- In you lambda controller use Lambda decorators:
271
+ Then, edit the `handler.ts` and change the exported functions:
139
272
 
140
273
  ```typescript
141
- import {Injectable} from "@tsed/di";
142
- import {Returns, Summary} from "@tsed/schema";
143
- import {QueryParams} from "@tsed/platform-serverless"; // /!\ don't import decorators from @tsed/common
144
- import {TimeslotsService} from "../services/TimeslotsService";
145
- import {TimeslotModel} from "../models/TimeslotModel";
274
+ import {PlatformServerless} from "@tsed/platform-serverless";
275
+ import {TimeslotsLambda} from "./TimeslotsLambda";
146
276
 
147
- @Injectable()
148
- export class TimeslotsLambda {
149
- @Inject()
150
- protected timeslotsService: TimeslotsService;
277
+ const platform = PlatformServerless.bootstrap({
278
+ lambda: [TimeslotsLambda]
279
+ });
151
280
 
152
- @Lambda()
153
- @Summary("Return a list of timeslots")
154
- @Returns(200, Array).Of(TimeslotModel)
155
- get(@QueryParams("date_start") dateStart: Date, @QueryParams("date_end") dateEnd: Date) {
156
- return this.timeslotsService.find({
157
- dateStart,
158
- dateEnd
159
- })
281
+ export const handler = platform.handler();
282
+ ```
283
+
284
+ Now, Ts.ED will handle request and call the expected lambda in your controllers.
285
+
286
+ To simplify our workflow, we can add the following npm script command in our `package.json`:
287
+
288
+ ```json
289
+ {
290
+ "scripts": {
291
+ "invoke:any": "yarn serverless invoke local -f any --data '{\"path\":\"/timeslots\", \"httpMethod\": \"GET\"}'"
160
292
  }
161
293
  }
162
294
  ```
@@ -184,6 +316,91 @@ export class TimeslotsLambda {
184
316
  }
185
317
  ```
186
318
 
319
+ ## Testing
320
+
321
+ Ts.ED provide a way to test you lambda with mocked Aws event and context by using the `PlatformServerlessTest` util.
322
+
323
+ Here an example to test a Lambda controller:
324
+
325
+ ```typescript
326
+ import {PlatformServerlessTest} from "@tsed/platform-serverless-testing";
327
+ import {PlatformServerless} from "@tsed/platform-serverless";
328
+
329
+ @Controller("/")
330
+ class TimeslotsLambdaController {
331
+ @Get("/")
332
+ getAll() {
333
+ return [];
334
+ }
335
+
336
+ @Get("/:id")
337
+ getById(@PathParams("id") id: string, @QueryParams("start_date") startDate: Date, @QueryParams("end_date") endDate: Date) {
338
+ return {
339
+ id,
340
+ startDate,
341
+ endDate
342
+ };
343
+ }
344
+ }
345
+
346
+ describe("TimeslotsLambdaController", () => {
347
+ beforeEach(
348
+ PlatformServerlessTest.bootstrap(PlatformServerless, {
349
+ lambda: [TimeslotsLambdaController]
350
+ })
351
+ );
352
+ afterEach(() => PlatformServerlessTest.reset());
353
+
354
+ describe("Invoke by lambda name", () => {
355
+ it("should call getAll Lambda", async () => {
356
+ const response = await PlatformServerlessTest.request.call("getAll");
357
+
358
+ expect(response.statusCode).toEqual(200);
359
+ expect(response.headers).toEqual({
360
+ "x-request-id": "requestId",
361
+ "content-type": "application/json"
362
+ });
363
+ expect(JSON.parse(response.body)).toEqual([]);
364
+ });
365
+
366
+ it("should call getAll Lambda", async () => {
367
+ const response = await PlatformServerlessTest.request.call("getById")
368
+ .params({
369
+ id: "1"
370
+ })
371
+ .query({
372
+ start_date: new Date("2020-01-01"),
373
+ end_date: new Date("2020-01-10")
374
+ });
375
+
376
+ expect(response.statusCode).toEqual(200);
377
+ expect(response.headers).toEqual({
378
+ "x-request-id": "requestId",
379
+ "content-type": "application/json"
380
+ });
381
+ expect(JSON.parse(response.body)).toEqual({
382
+ id: "1",
383
+ endDate: "2020-01-10T00:00:00.000Z",
384
+ startDate: "2020-01-01T00:00:00.000Z"
385
+ });
386
+ });
387
+ });
388
+
389
+ describe("invoke using the router", () => {
390
+ it("should call getAll Lambda", async () => {
391
+ const response = await PlatformServerlessTest.request.get("/");
392
+
393
+ expect(response.statusCode).toEqual(200);
394
+ expect(response.headers).toEqual({
395
+ "x-request-id": "requestId",
396
+ "content-type": "application/json"
397
+ });
398
+ expect(JSON.parse(response.body)).toEqual([]);
399
+ });
400
+ });
401
+ });
402
+ ```
403
+
187
404
  ## Contributors
188
405
  Please read [contributing guidelines here](https://tsed.io/CONTRIBUTING.html).
189
406