diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 219f880..6ff0f39 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -63,6 +63,6 @@ jobs: env: NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} - name: Bump versions - run: node ./common/scripts/install-run-rush.js rush version --bump --target-branch main + run: node ./common/scripts/install-run-rush.js version --bump --target-branch main - name: publish run: node ./common/scripts/install-run-rush.js publish -a -b main -p --set-access-level public --include-all diff --git a/.gitignore b/.gitignore index 498da96..e834342 100644 --- a/.gitignore +++ b/.gitignore @@ -61,3 +61,6 @@ jspm_packages/ # Rush temporary files common/temp/ **/.rush/temp/ + +# JetBrains IDEs +.idea diff --git a/README.md b/README.md index 525d605..c9465b9 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,24 @@ export const handler = compose( }) ``` +There's a [known issue with TypeScript](https://github.com/microsoft/TypeScript/issues/29904) that pipe and compose functions cannot +infer types correctly if the innermost function is generic (in this case the last argument to `compose`). +If you use TypeScript in strict mode, you can instead use the `composeHandler` function exported from `@lambda-middleware/compose`: + +```typescript +export const handler = composeHandler( + someMiddleware(), + someOtherMiddleware(), + aThirdMiddleware(), + () => { + return { + body: '', + statusCode: 200 + } + } +) +``` + Composing middleware is equivalent to calling it nested: ```typescript export const handler = diff --git a/common/changes/@lambda-middleware/class-validator/use_strict_types_2020-10-29-19-31.json b/common/changes/@lambda-middleware/class-validator/use_strict_types_2020-10-29-19-31.json new file mode 100644 index 0000000..f4cae18 --- /dev/null +++ b/common/changes/@lambda-middleware/class-validator/use_strict_types_2020-10-29-19-31.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "@lambda-middleware/class-validator", + "comment": "Make compatible with TypeScript strict mode", + "type": "patch" + } + ], + "packageName": "@lambda-middleware/class-validator", + "email": "daniel@bartholomae.name" +} \ No newline at end of file diff --git a/common/changes/@lambda-middleware/compose/use_strict_types_2020-10-29-19-31.json b/common/changes/@lambda-middleware/compose/use_strict_types_2020-10-29-19-31.json new file mode 100644 index 0000000..af87a08 --- /dev/null +++ b/common/changes/@lambda-middleware/compose/use_strict_types_2020-10-29-19-31.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "@lambda-middleware/compose", + "comment": "Add composeHandler function", + "type": "minor" + } + ], + "packageName": "@lambda-middleware/compose", + "email": "daniel@bartholomae.name" +} \ No newline at end of file diff --git a/common/changes/@lambda-middleware/http-header-normalizer/use_strict_types_2020-10-29-19-31.json b/common/changes/@lambda-middleware/http-header-normalizer/use_strict_types_2020-10-29-19-31.json new file mode 100644 index 0000000..a549a9b --- /dev/null +++ b/common/changes/@lambda-middleware/http-header-normalizer/use_strict_types_2020-10-29-19-31.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "@lambda-middleware/http-header-normalizer", + "comment": "Make compatible with TypeScript strict mode", + "type": "patch" + } + ], + "packageName": "@lambda-middleware/http-header-normalizer", + "email": "daniel@bartholomae.name" +} \ No newline at end of file diff --git a/common/changes/@lambda-middleware/middy-adaptor/use_strict_types_2020-10-29-19-31.json b/common/changes/@lambda-middleware/middy-adaptor/use_strict_types_2020-10-29-19-31.json new file mode 100644 index 0000000..c011205 --- /dev/null +++ b/common/changes/@lambda-middleware/middy-adaptor/use_strict_types_2020-10-29-19-31.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "@lambda-middleware/middy-adaptor", + "comment": "Make compatible with TypeScript strict mode", + "type": "patch" + } + ], + "packageName": "@lambda-middleware/middy-adaptor", + "email": "daniel@bartholomae.name" +} \ No newline at end of file diff --git a/common/changes/@lambda-middleware/utils/use_strict_types_2020-10-29-19-31.json b/common/changes/@lambda-middleware/utils/use_strict_types_2020-10-29-19-31.json new file mode 100644 index 0000000..c1741df --- /dev/null +++ b/common/changes/@lambda-middleware/utils/use_strict_types_2020-10-29-19-31.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "@lambda-middleware/utils", + "comment": "Make compatible with TypeScript strict mode", + "type": "patch" + } + ], + "packageName": "@lambda-middleware/utils", + "email": "daniel@bartholomae.name" +} \ No newline at end of file diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index 926693b..fa26dc3 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -56,6 +56,7 @@ dependencies: supertest: 4.0.2 ts-jest: 25.5.1_jest@25.5.4+typescript@4.0.3 ts-loader: 6.2.2_typescript@4.0.3 + ts-toolbelt: 8.0.7 tslib: 2.0.3 typescript: 4.0.3 wait-on: 5.2.0 @@ -10477,6 +10478,10 @@ packages: typescript: '>=2.7' resolution: integrity: sha512-/TqB4SnererCDR/vb4S/QvSZvzQMJN8daAslg7MeaiHvD8rDZsSfXmNeNumyZZzMned72Xoq/isQljYSt8Ynfg== + /ts-toolbelt/8.0.7: + dev: false + resolution: + integrity: sha512-KICHyKxc5Nu34kyoODrEe2+zvuQQaubTJz7pnC5RQ19TH/Jged1xv+h8LBrouaSD310m75oAljYs59LNHkLDkQ== /tslib/1.14.1: dev: false resolution: @@ -11343,7 +11348,7 @@ packages: dev: false name: '@rush-temp/class-validator' resolution: - integrity: sha512-UrbEJhMzF/lU/MKDmmy24qQSB+tERIsz5TZcZ7BITudaciOY5eHkdJz3pQarivNKUfKHayhRcQvGx77UD1NrFw== + integrity: sha512-gltpLhUXTG3ZkGUIvdkbWW2S/b4vMQ65rdAe+H13eYhtGnKp/d/zFXUgzDXAiFqWA+3rd6Y/8+MZ+is7//GLaw== tarball: 'file:projects/class-validator.tgz' version: 0.0.0 'file:projects/compose.tgz': @@ -11380,6 +11385,7 @@ packages: supertest: 4.0.2 ts-jest: 25.5.1_jest@25.5.4+typescript@4.0.3 ts-loader: 6.2.2_typescript@4.0.3 + ts-toolbelt: 8.0.7 tslib: 2.0.3 typescript: 4.0.3 wait-on: 5.2.0 @@ -11387,7 +11393,7 @@ packages: dev: false name: '@rush-temp/compose' resolution: - integrity: sha512-MzWBmHJnNTTVlkBBRcdjuEa5+kNf5DP8J8Tbjc1GGDODu45ZLPaUIxQlsZeenSI36SJrK+eoDh3iTC12B6o6JQ== + integrity: sha512-v36uFBM33IxOWBybWPQchv3ldLZC2MjdMam+fNN2xNT0+quSTn7zfUPyhzDnIj5wuTU40mAvXU9hmbLKdVuoiA== tarball: 'file:projects/compose.tgz' version: 0.0.0 'file:projects/cors.tgz': @@ -11424,7 +11430,7 @@ packages: dev: false name: '@rush-temp/cors' resolution: - integrity: sha512-tldEPneJvYT5Z636vUGq3rPB24FGojmhkzvO5KPK8TLPDH5xQht8eSjzOjx6WAl1VrnHf7QHJLdsi1XFYFIoZQ== + integrity: sha512-3ppPf378yauooMj4bT6QP5OrGUPLTcKEMt5hCXHiC9oAH5EpH8MH/w52PXHwPiuB3O9E75lyij6xU/WrArZH8A== tarball: 'file:projects/cors.tgz' version: 0.0.0 'file:projects/do-not-wait.tgz': @@ -11452,7 +11458,7 @@ packages: dev: false name: '@rush-temp/do-not-wait' resolution: - integrity: sha512-pbqTL339tb6SrqbCaOpeycGNbaaIDjgNmgAiye3Pa+7QLa1QWVupIOwM8c2wFVlYvK/UI+QZbeg0sbU0HvxwAw== + integrity: sha512-0jqH3GeGIyRei7EgwLuOxsCVp3jZzmV3lUKxDCTBbPqVzQroa3wsuqu0e84FntBdpK3VdWdqKRBKczN56pxzjg== tarball: 'file:projects/do-not-wait.tgz' version: 0.0.0 'file:projects/e2e-tests.tgz': @@ -11489,7 +11495,7 @@ packages: dev: false name: '@rush-temp/e2e-tests' resolution: - integrity: sha512-EBms1BPrr2ZtRs932MNahK5GG8+2nQ8o0m0Xw9yXrg/wUV3t/s71oOTi4KKlv7jLE3R+uPLN5uB3Qq7BCftTPQ== + integrity: sha512-sdc6Dzz0OhESmCcuMx74yRcAAdZAlDus2fdLSQlZ7yzZvNVdtqUHJwTtjOti6lhRMmUAmy2vEY+kwZNJtnUNZQ== tarball: 'file:projects/e2e-tests.tgz' version: 0.0.0 'file:projects/http-error-handler.tgz': @@ -11529,7 +11535,7 @@ packages: dev: false name: '@rush-temp/http-error-handler' resolution: - integrity: sha512-0Ph7LwrkBYkVQqRA4rt7hgi9dcJUOATu8BA4A5AETxtpDbibrNfIdHAbXMtAQnEqhUG9DYi9gnG0jZvAiiOLAw== + integrity: sha512-L9AoSpY+BnFyamYAsKwLiIjjgUY50FwAdHVMY+nV/5YMIoUgy2Nk6EVCQwBG3YXdBlBahlQUn+/hfs+VsHWGjg== tarball: 'file:projects/http-error-handler.tgz' version: 0.0.0 'file:projects/http-header-normalizer.tgz': @@ -11566,7 +11572,7 @@ packages: dev: false name: '@rush-temp/http-header-normalizer' resolution: - integrity: sha512-FYdHfwXlF2PBhfNysKtA+W3O0BiP79Xg5n0c5lxdKUznxGV+NtZxrWG1KhymwrSPId4gTQ1I5mQyjKTJSIkAJA== + integrity: sha512-QRnytbnd6HbJLqeIdY2LlRxDH/YTIBZ03jGzJ841ycanqNbkLOT4yzNbUjP2Yeu9JLkRRvUG9GRIXVK5C06B5Q== tarball: 'file:projects/http-header-normalizer.tgz' version: 0.0.0 'file:projects/ie-no-open.tgz': @@ -11603,7 +11609,7 @@ packages: dev: false name: '@rush-temp/ie-no-open' resolution: - integrity: sha512-3d9DnnEy5Z8EAPGT7TpYjtMmNAXuV5KygkNd+mXuTC4dj1c7NBqKojtDpiAZunlpUb/OnqNzo83Ti2ltvmXBbA== + integrity: sha512-vB7P2EeQlfZ0IfENI/J9Hwc3CWeiX/980h1NjbHJ4ml7uS8qgteO2TZFEqt4Lr0pJRe74Y0wKw/is5gwDiUuxA== tarball: 'file:projects/ie-no-open.tgz' version: 0.0.0 'file:projects/json-serializer.tgz': @@ -11640,7 +11646,7 @@ packages: dev: false name: '@rush-temp/json-serializer' resolution: - integrity: sha512-mzYsCB+2y1YIlzIeGLzy9UCFP1Q4csvFDVR+x262/7E9n2ld6jFOd387P4F1nJCa7iQiOXB7fOjAU2MvTr/96Q== + integrity: sha512-YgCmC/BZ6gfZRFGghdne5XIzkZBbssprZ82TI6K3E2xuj/YZvB5SCK9cUamGm+egCw9wY2myw81iOCIjL5YvYw== tarball: 'file:projects/json-serializer.tgz' version: 0.0.0 'file:projects/jwt-auth.tgz': @@ -11683,7 +11689,7 @@ packages: dev: false name: '@rush-temp/jwt-auth' resolution: - integrity: sha512-8hslbK1nAF1IV/fHQvAakC/eZriQbmnNGSbwDh8IbdguSuGrtxroDWriYVWaRHIDlXVEUhLhJkFhwh+yz1T8BQ== + integrity: sha512-us+NA7x1rRZXEXXWIJrBZTuvMBtbXUg7oG5IkoR0Pwvf/ZaSQd61mNb9dNX4WCace7ifGuSrk7ez4NPSqBtmvg== tarball: 'file:projects/jwt-auth.tgz' version: 0.0.0 'file:projects/middy-adaptor.tgz': @@ -11720,7 +11726,7 @@ packages: dev: false name: '@rush-temp/middy-adaptor' resolution: - integrity: sha512-/dd5rFOCO6f7IpHl9wYPv5VXwZRK2GwJ0WskxfHkeUSdKVnvnCLsv1U+GUkhqKZjAhCI5ANudv/b5blkA2BynQ== + integrity: sha512-3LysRSGlJOuUiAR3cVY0xXGEzrvKWFHiBh/7RKzZ/O/6o3DOA+JVmiJIHMZeeMhgwjgoYCN7LYeUYarUmmvE4Q== tarball: 'file:projects/middy-adaptor.tgz' version: 0.0.0 'file:projects/no-sniff.tgz': @@ -11757,7 +11763,7 @@ packages: dev: false name: '@rush-temp/no-sniff' resolution: - integrity: sha512-oUEiD+hl4WNZGwoSLM3HBQqbDApQgIvJM1BgvsABpSHcL0Tyr9DJZFrquvfVwXpyx6c32KvQptVtCBaD/i9IUg== + integrity: sha512-mB5jqsN8xFEPOWmK2dt4AcKFbqjTGVS0KegGchpNl+jDUsrvnfKA5ibo+0Vq+ejrOTF2siRl5PuH+awco6U1eQ== tarball: 'file:projects/no-sniff.tgz' version: 0.0.0 'file:projects/utils.tgz': @@ -11846,6 +11852,7 @@ specifiers: supertest: ^4.0.2 ts-jest: ^25.3.1 ts-loader: ^6.2.2 + ts-toolbelt: ^8.0.7 tslib: ^2.0.1 typescript: ^4.0.3 wait-on: ^5.2.0 diff --git a/internal/e2e-tests/examples/helloWorld.ts b/internal/e2e-tests/examples/helloWorld.ts index 6384367..025b10c 100644 --- a/internal/e2e-tests/examples/helloWorld.ts +++ b/internal/e2e-tests/examples/helloWorld.ts @@ -1,7 +1,7 @@ import "reflect-metadata"; import { classValidator } from "@lambda-middleware/class-validator"; -import { compose } from "@lambda-middleware/compose"; +import { composeHandler } from "@lambda-middleware/compose"; import { cors } from "@lambda-middleware/cors"; import { errorHandler } from "@lambda-middleware/http-error-handler"; import { jsonSerializer } from "@lambda-middleware/json-serializer"; @@ -11,6 +11,11 @@ import { IsString } from "class-validator"; // Define a validator for the body via class-validator class NameBody { + constructor(firstName: string, lastName: string) { + this.firstName = firstName; + this.lastName = lastName; + } + @IsString() public firstName: string; @@ -19,14 +24,16 @@ class NameBody { } // This is your AWS handler -async function helloWorld(event: { body: NameBody }) { +async function helloWorld(event: { + body: NameBody; +}): Promise<{ message: string }> { // Thanks to the validation middleware you can be sure body is typed correctly return { message: `Hello ${event.body.firstName} ${event.body.lastName}`, }; } -const wrapper = compose( +export const handler = composeHandler( // add cors headers last so even error responses from the // errorHandler middleware have cors headers applied cors(), @@ -48,7 +55,6 @@ const wrapper = compose( // and other middlewares might not be able to handle the modified event classValidator({ bodyType: NameBody, - }) -)(helloWorld); - -export const handler = wrapper; + }), + helloWorld +); diff --git a/packages/class-validator/README.md b/packages/class-validator/README.md index 7e4acbb..70f454e 100644 --- a/packages/class-validator/README.md +++ b/packages/class-validator/README.md @@ -22,12 +22,17 @@ This middleware is part of the [lambda middleware series](https://dbartholomae.g import "reflect-metadata"; import { classValidator } from '@lambda-middleware/class-validator' -import { compose } from "@lambda-middleware/compose"; +import { composeHandler } from "@lambda-middleware/compose"; import { errorHandler } from "@lambda-middleware/http-error-handler"; import { IsString } from "class-validator"; // Define a validator for the body via class-validator class NameBody { + constructor(firstName: string, lastName: string) { + this.firstName = firstName; + this.lastName = lastName; + } + @IsString() public firstName: string; @@ -48,7 +53,7 @@ async function helloWorld(event: { body: NameBody }) { } // Let's add middleware to our handler, then we will be able to attach middlewares to it -export const handler = compose( +export const handler = composeHandler( // The class validator throws validation errors from http-errors which are compatible with // the error handler middlewares for middy errorHandler(), @@ -63,6 +68,7 @@ export const handler = compose( // to set it to true manually as the default for class-validator would be // false validator: {}, - }) -)(helloWorld); + }), + helloWorld +); ``` diff --git a/packages/class-validator/examples/helloWorld.ts b/packages/class-validator/examples/helloWorld.ts index 9f10bf5..d0f1341 100644 --- a/packages/class-validator/examples/helloWorld.ts +++ b/packages/class-validator/examples/helloWorld.ts @@ -2,12 +2,18 @@ import "reflect-metadata"; import { classValidator } from "../"; -import { compose } from "@lambda-middleware/compose"; +import { composeHandler } from "@lambda-middleware/compose"; import { errorHandler } from "@lambda-middleware/http-error-handler"; import { IsString } from "class-validator"; +import { APIGatewayProxyResult } from "aws-lambda"; // Define a validator for the body via class-validator class NameBody { + constructor(firstName: string, lastName: string) { + this.firstName = firstName; + this.lastName = lastName; + } + @IsString() public firstName: string; @@ -16,7 +22,9 @@ class NameBody { } // This is your AWS handler -async function helloWorld(event: { body: NameBody }) { +async function helloWorld(event: { + body: NameBody; +}): Promise { // Thanks to the validation middleware you can be sure body is typed correctly return { body: `Hello ${event.body.firstName} ${event.body.lastName}`, @@ -28,7 +36,7 @@ async function helloWorld(event: { body: NameBody }) { } // Let's add middleware to our handler, then we will be able to attach middlewares to it -export const handler = compose( +export const handler = composeHandler( // The class validator throws validation errors from http-errors which are compatible with // the error handler middlewares for middy errorHandler(), @@ -43,5 +51,6 @@ export const handler = compose( // to set it to true manually as the default for class-validator would be // false validator: {}, - }) -)(helloWorld); + }), + helloWorld +); diff --git a/packages/class-validator/src/classValidator.test.ts b/packages/class-validator/src/classValidator.test.ts index 36387e6..ef70f66 100644 --- a/packages/class-validator/src/classValidator.test.ts +++ b/packages/class-validator/src/classValidator.test.ts @@ -4,6 +4,11 @@ import { IsOptional, IsString } from "class-validator"; import { createEvent } from "@lambda-middleware/utils"; class NameBody { + constructor(firstName: string, lastName: string) { + this.firstName = firstName; + this.lastName = lastName; + } + @IsString() public firstName: string; diff --git a/packages/compose/README.md b/packages/compose/README.md index 11a791f..1e5c011 100644 --- a/packages/compose/README.md +++ b/packages/compose/README.md @@ -56,3 +56,53 @@ export const handler: ProxyHandler = compose( stringifyToBody() )(helloWorld); ``` + +## Usage in TypeScript strict mode + +There's a [known issue with TypeScript](https://github.com/microsoft/TypeScript/issues/29904) that pipe and compose functions cannot +infer types correctly if the innermost function is generic (in this case the last argument to `compose`). +To get around it, this package also exports `composeHandler`: + +```ts +import "reflect-metadata"; + +import { classValidator } from "@lambda-middleware/class-validator"; +import { compose } from "@lambda-middleware/compose"; +import { errorHandler } from "@lambda-middleware/http-error-handler"; +import { IsString } from "class-validator"; +import { APIGatewayProxyResult } from "aws-lambda"; + +class NameBody { + constructor(firstName: string, lastName: string) { + this.firstName = firstName; + this.lastName = lastName; + } + + @IsString() + public firstName: string; + + @IsString() + public lastName: string; +} + +async function helloWorld(event: { + body: NameBody; +}): Promise { + return { + body: `Hello ${event.body.firstName} ${event.body.lastName}`, + headers: { + "content-type": "text", + }, + statusCode: 200, + }; +} + +export const handler = composeHandler( + errorHandler(), + classValidator({ + bodyType: NameBody, + }), + // The following function solves the type trouble: + helloWorld +); +``` diff --git a/packages/compose/examples/helloWorld.ts b/packages/compose/examples/helloWorld.ts index e49efea..14858ae 100644 --- a/packages/compose/examples/helloWorld.ts +++ b/packages/compose/examples/helloWorld.ts @@ -1,4 +1,4 @@ -import { compose } from "../"; +import { composeHandler } from "../"; import { PromiseHandler } from "@lambda-middleware/utils"; import { Context, ProxyHandler, APIGatewayEvent } from "aws-lambda"; @@ -31,7 +31,8 @@ const addStatusCode = (statusCode: number) => ( // Wrap the handler with the middleware. // With compose you can wrap multiple middlewares around one handler. -export const handler: ProxyHandler = compose( +export const handler: ProxyHandler = composeHandler( addStatusCode(200), - stringifyToBody() -)(helloWorld); + stringifyToBody(), + helloWorld +); diff --git a/packages/compose/src/compose-handler.test.ts b/packages/compose/src/compose-handler.test.ts new file mode 100644 index 0000000..3407c44 --- /dev/null +++ b/packages/compose/src/compose-handler.test.ts @@ -0,0 +1,40 @@ +import { composeHandler } from "./compose-handler"; + +describe("composeHandler", () => { + describe("with no arguments", () => { + it("throws a TypeError if called without any arguments", () => { + // @ts-expect-error + expect(() => composeHandler()).toThrow( + new TypeError("compose requires at least one argument") + ); + }); + }); + + describe("with one arguments", () => { + it("throws a TypeError if called with only one argument", () => { + // @ts-expect-error + expect(() => composeHandler(jest.fn())).toThrow( + new TypeError("compose requires at least one argument") + ); + }); + }); + + describe("with three arguments", () => { + it("calls the functions in order", () => { + const mockHandler = "mockHandler"; + const middlewareAResponse = "middleware-A-response"; + const mockMiddlewareA = jest.fn().mockReturnValue(middlewareAResponse); + const middlewareBResponse = "middleware-B-response"; + const mockMiddlewareB = jest.fn().mockReturnValue(middlewareBResponse); + const composeResponse = composeHandler( + mockMiddlewareB, + mockMiddlewareA, + mockHandler + ); + + expect(mockMiddlewareA).toHaveBeenCalledWith(mockHandler); + expect(mockMiddlewareB).toHaveBeenCalledWith(middlewareAResponse); + expect(composeResponse).toBe(middlewareBResponse); + }); + }); +}); diff --git a/packages/compose/src/compose-handler.ts b/packages/compose/src/compose-handler.ts new file mode 100644 index 0000000..2b89a97 --- /dev/null +++ b/packages/compose/src/compose-handler.ts @@ -0,0 +1,89 @@ +import { compose } from "./compose"; + +export function composeHandler(fn0: (x: V) => T1, handler: V): T1; +export function composeHandler( + fn1: (x: T1) => T2, + fn0: (x: V) => T1, + handler: V +): T2; +export function composeHandler( + fn2: (x: T2) => T3, + fn1: (x: T1) => T2, + fn0: (x: V) => T1, + handler: V +): T3; +export function composeHandler( + fn3: (x: T3) => T4, + fn2: (x: T2) => T3, + fn1: (x: T1) => T2, + fn0: (x: V) => T1, + handler: V +): T4; +export function composeHandler( + fn4: (x: T4) => T5, + fn3: (x: T3) => T4, + fn2: (x: T2) => T3, + fn1: (x: T1) => T2, + fn0: (x: V) => T1, + handler: V +): T5; +export function composeHandler( + fn5: (x: T5) => T6, + fn4: (x: T4) => T5, + fn3: (x: T3) => T4, + fn2: (x: T2) => T3, + fn1: (x: T1) => T2, + fn0: (x: V) => T1, + handler: V +): T6; +export function composeHandler( + fn6: (x: T6) => T7, + fn5: (x: T5) => T6, + fn4: (x: T4) => T5, + fn3: (x: T3) => T4, + fn2: (x: T2) => T3, + fn1: (x: T1) => T2, + fn0: (x: V) => T1, + handler: V +): T7; +export function composeHandler( + fn7: (x: T7) => T8, + fn6: (x: T6) => T7, + fn5: (x: T5) => T6, + fn4: (x: T4) => T5, + fn3: (x: T3) => T4, + fn2: (x: T2) => T3, + fn1: (x: T1) => T2, + fn0: (x: V) => T1, + handler: V +): T8; +export function composeHandler( + fn8: (x: T8) => T9, + fn7: (x: T7) => T8, + fn6: (x: T6) => T7, + fn5: (x: T5) => T6, + fn4: (x: T4) => T5, + fn3: (x: T3) => T4, + fn2: (x: T2) => T3, + fn1: (x: T1) => T2, + fn0: (x: V) => T1, + handler: V +): T9; +export function composeHandler( + fn9: (x: T9) => T10, + fn8: (x: T8) => T9, + fn7: (x: T7) => T8, + fn6: (x: T6) => T7, + fn5: (x: T5) => T6, + fn4: (x: T4) => T5, + fn3: (x: T3) => T4, + fn2: (x: T2) => T3, + fn1: (x: T1) => T2, + fn0: (x: V) => T1, + handler: V +): T10; +export function composeHandler(...fns: Function[]): any { + /* eslint-disable-next-line @typescript-eslint/ban-ts-ignore */ + // @ts-ignore + return compose(...fns.slice(0, fns.length - 1))(fns[fns.length - 1]); +} diff --git a/packages/compose/src/compose.ts b/packages/compose/src/compose.ts index d8b7968..3abaa61 100644 --- a/packages/compose/src/compose.ts +++ b/packages/compose/src/compose.ts @@ -1,4 +1,3 @@ -// tslint:disable-next-line:ban-types function composeTwo(f: Function, g: Function) { return function (this: any) { // eslint-disable-next-line prefer-rest-params @@ -93,7 +92,6 @@ export function compose< fn1: (x: T1) => T2, fn0: (...args: V) => T1 ): (...args: V) => T10; -// tslint:disable-next-line:ban-types export function compose(...fns: Function[]): any { if (fns.length === 0) { throw new TypeError("compose requires at least one argument"); diff --git a/packages/compose/src/index.ts b/packages/compose/src/index.ts index e5e632f..1af5c1e 100644 --- a/packages/compose/src/index.ts +++ b/packages/compose/src/index.ts @@ -1,2 +1,4 @@ /* istanbul ignore next */ export * from "./compose"; +/* istanbul ignore next */ +export * from "./compose-handler"; diff --git a/packages/http-header-normalizer/src/http-header-normalizer.ts b/packages/http-header-normalizer/src/http-header-normalizer.ts index eb1ed8c..9f0e8cf 100644 --- a/packages/http-header-normalizer/src/http-header-normalizer.ts +++ b/packages/http-header-normalizer/src/http-header-normalizer.ts @@ -3,17 +3,15 @@ import { PromiseHandler } from "@lambda-middleware/utils"; import { APIGatewayProxyEvent, Context } from "aws-lambda"; import { logger } from "./logger"; -type HashMap = { [key: string]: Value }; - -function lowercaseKeys(object: HashMap): HashMap { - const newObject = {}; +function lowercaseKeys(object: Record): Record { + const newObject: Record = {}; for (const key in object) { newObject[key.toLowerCase()] = object[key]; } return newObject; } -function addReferrer(headers: HashMap): HashMap { +function addReferrer(headers: Record): Record { return { ...headers, referrer: headers.referrer ?? headers.referer, @@ -21,12 +19,14 @@ function addReferrer(headers: HashMap): HashMap { }; } -function normalizeHeaders(headers: HashMap): HashMap { +function normalizeHeaders( + headers: Record +): Record { return compose(addReferrer, lowercaseKeys)(headers); } export const httpHeaderNormalizer = () => ( - handler: PromiseHandler }, R> + handler: PromiseHandler }, R> ) => async (event: E, context: Context): Promise => { logger("Running handler"); const normalizedEvent = { diff --git a/packages/middy-adaptor/src/MiddyMiddleware/MiddyMiddleware.ts b/packages/middy-adaptor/src/MiddyMiddleware/MiddyMiddleware.ts index f630b53..b40bda1 100644 --- a/packages/middy-adaptor/src/MiddyMiddleware/MiddyMiddleware.ts +++ b/packages/middy-adaptor/src/MiddyMiddleware/MiddyMiddleware.ts @@ -5,13 +5,13 @@ import { promisifyMiddyMiddleware } from "./promisifyMiddyMiddleware"; import { logger } from "../logger"; export class MiddyMiddleware { - private middlewareObject: PromisifiedMiddlewareObject; + private readonly middlewareObject: PromisifiedMiddlewareObject; constructor(middlewareObject: MiddlewareObject) { this.middlewareObject = promisifyMiddyMiddleware(middlewareObject); } public async onError( - instance: Instance, + instance: Instance, defaultReturn: any ): Promise { logger("Checking for onError middleware"); @@ -24,17 +24,17 @@ export class MiddyMiddleware { logger("onError middleware called"); } - public async before(instance: Instance): Promise { + public async before(instance: Instance): Promise { return this.run("before", instance); } - public async after(instance: Instance): Promise { + public async after(instance: Instance): Promise { return this.run("after", instance); } private async run( - middlewareType: keyof PromisifiedMiddlewareObject, - instance: Instance + middlewareType: keyof PromisifiedMiddlewareObject, + instance: Instance ): Promise { logger(`Checking for ${middlewareType} middleware`); diff --git a/packages/middy-adaptor/src/MiddyMiddleware/promisifyMiddyMiddleware.ts b/packages/middy-adaptor/src/MiddyMiddleware/promisifyMiddyMiddleware.ts index 7261fb5..5e89a84 100644 --- a/packages/middy-adaptor/src/MiddyMiddleware/promisifyMiddyMiddleware.ts +++ b/packages/middy-adaptor/src/MiddyMiddleware/promisifyMiddyMiddleware.ts @@ -1,15 +1,17 @@ import { MiddlewareObject, PromisifiedMiddlewareObject } from "../index"; import { promisifyMiddyMiddlewareFunction } from "./promisifyMiddyMiddlewareFunction"; +import { Context } from "aws-lambda"; -export function promisifyMiddyMiddleware( - middyMiddleware: MiddlewareObject -): PromisifiedMiddlewareObject { - const promisifiedMiddyMiddleware: PromisifiedMiddlewareObject = {}; - - for (const key in middyMiddleware) { - promisifiedMiddyMiddleware[key] = promisifyMiddyMiddlewareFunction( - middyMiddleware[key] - ); - } - return promisifiedMiddyMiddleware; +export function promisifyMiddyMiddleware( + middyMiddleware: MiddlewareObject +): PromisifiedMiddlewareObject { + return (Object.keys(middyMiddleware) as Array< + keyof typeof middyMiddleware + >).reduce( + (accumulator, key) => ({ + ...accumulator, + [key]: promisifyMiddyMiddlewareFunction(middyMiddleware[key]!), + }), + {} + ); } diff --git a/packages/middy-adaptor/src/MiddyMiddleware/promisifyMiddyMiddlewareFunction.ts b/packages/middy-adaptor/src/MiddyMiddleware/promisifyMiddyMiddlewareFunction.ts index 52f2b93..62c0c46 100644 --- a/packages/middy-adaptor/src/MiddyMiddleware/promisifyMiddyMiddlewareFunction.ts +++ b/packages/middy-adaptor/src/MiddyMiddleware/promisifyMiddyMiddlewareFunction.ts @@ -1,10 +1,15 @@ -import { Instance, MiddlewareFunction } from "../interfaces/MiddyTypes"; +import { + Instance, + MiddlewareFunction, + PromisifiedMiddlewareFunction, +} from "../interfaces/MiddyTypes"; import { isPromise } from "../utils/isPromise"; +import { Context } from "aws-lambda"; -export function promisifyMiddyMiddlewareFunction( - fn: MiddlewareFunction -) { - return (instance: Instance) => { +export function promisifyMiddyMiddlewareFunction( + fn: MiddlewareFunction +): PromisifiedMiddlewareFunction { + return (instance: Instance) => { return new Promise((resolve, reject) => { function next(err: unknown): void { if (err) { diff --git a/packages/middy-adaptor/src/interfaces/MiddyTypes.ts b/packages/middy-adaptor/src/interfaces/MiddyTypes.ts index 32ef1f5..81f4c8f 100644 --- a/packages/middy-adaptor/src/interfaces/MiddyTypes.ts +++ b/packages/middy-adaptor/src/interfaces/MiddyTypes.ts @@ -33,10 +33,12 @@ export interface Instance< callback: Callback; } -type PromisifiedMiddlewareFunction = (instance: Instance) => Promise; +export type PromisifiedMiddlewareFunction = ( + instance: Instance +) => Promise; -export type PromisifiedMiddlewareObject = { - before?: PromisifiedMiddlewareFunction; - after?: PromisifiedMiddlewareFunction; - onError?: PromisifiedMiddlewareFunction; +export type PromisifiedMiddlewareObject = { + before?: PromisifiedMiddlewareFunction; + after?: PromisifiedMiddlewareFunction; + onError?: PromisifiedMiddlewareFunction; }; diff --git a/packages/middy-adaptor/src/middy-adaptor.ts b/packages/middy-adaptor/src/middy-adaptor.ts index 8b5e1ca..58df81b 100644 --- a/packages/middy-adaptor/src/middy-adaptor.ts +++ b/packages/middy-adaptor/src/middy-adaptor.ts @@ -13,13 +13,13 @@ export const middyAdaptor = < middyMiddlewareObject: MiddlewareObject ) => (handler: PromiseHandler) => async ( event: Event, - context: Context + context: ContextLike ): Promise => { const callbackListener = new CallbackListener(); - const instance: Instance = { + const instance: Instance = { context: { ...context }, event: { ...event }, - response: null, + response: (null as unknown) as Response, error: null, callback: callbackListener.callback, }; diff --git a/packages/utils/src/promisifyHandler.ts b/packages/utils/src/promisifyHandler.ts index e62fb1e..5412711 100644 --- a/packages/utils/src/promisifyHandler.ts +++ b/packages/utils/src/promisifyHandler.ts @@ -7,7 +7,7 @@ export function promisifyHandler( ): PromiseHandler { return function (event: Event, context: Context): Promise { return new Promise((resolve, reject) => { - function callback(err: any, response: Response): void { + function callback(err: any, response: Response | undefined): void { if (err) { return reject(err); } diff --git a/tsconfig.json b/tsconfig.json index e9c5351..906ca7c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,14 +10,9 @@ "forceConsistentCasingInFileNames": true, "experimentalDecorators": true, "emitDecoratorMetadata": true, - "noImplicitReturns": true, - "noImplicitThis": true, - "noImplicitAny": true, "declaration": true, "importHelpers": true, - "strictNullChecks": true, - "suppressImplicitAnyIndexErrors": true, - "noUnusedLocals": true + "strict": true }, "include": ["**/*"] }