From 273c9928d8a49d2d4c492fe24bdbef82170e3d98 Mon Sep 17 00:00:00 2001 From: Bishwa Thapa Date: Tue, 11 Apr 2023 20:58:35 +0545 Subject: [PATCH 1/3] feat: multiple handlers on same port --- src/example/multi.ts | 55 ++++++++++++++++++++++++++++++++++++++++++++ src/local.lambda.ts | 55 +++++++++++++++++++++++++++++++++++++++----- 2 files changed, 104 insertions(+), 6 deletions(-) create mode 100644 src/example/multi.ts diff --git a/src/example/multi.ts b/src/example/multi.ts new file mode 100644 index 0000000..2b58ca3 --- /dev/null +++ b/src/example/multi.ts @@ -0,0 +1,55 @@ +import { Context } from 'aws-lambda'; +import { LocalLambdaGroupConfig, LambdaConfig, LocalLambdaGroup } from '../local.lambda'; +import { LambdaResponse, RequestEvent } from '../types'; + +// handler is a function that takes in an event and context and returns a response +const handler = async (req: RequestEvent, context: Context): Promise => ({ statusCode: 200, body: `Hello World !!! My userId is ${req.pathParameters?.id}\n My JWT is ${JSON.stringify(req.requestContext.authorizer.lambda.jwt)}. My queryStringParameter is ${JSON.stringify(req.queryStringParameters)} ` }); +const funnyHandler = async (req: RequestEvent, context: Context): Promise => ({ statusCode: 200, body: `Hello World !!! I'm funny and my userId is ${req.pathParameters?.id}\n My JWT is ${JSON.stringify(req.requestContext.authorizer.lambda.jwt)}. My queryStringParameter is ${JSON.stringify(req.queryStringParameters)} ` }); + +// context is provided as optional field in config. +const config: LambdaConfig[] = [ + { + handler: handler, // if type is not compatible, do `handler: handler as any` + requestContext: { + authorizer: { + lambda: { + jwt: { + claims: { + sub: '1234567890', + name: 'John Doe', + iat: 1516239022, + }, + scopes: ['read', 'write'], + }, + }, + }, + }, + pathParamsPattern: '/user/:id', // optional, default to '/' + }, + { + handler: funnyHandler, // if type is not compatible, do `handler: handler as any` + pathParamsPattern: '/user/:id/funny', // optional, default to '/' + requestContext: { + authorizer: { + lambda: { + jwt: { + claims: { + sub: '1234567890', + name: 'John Doe', + iat: 1516239022, + }, + scopes: ['read', 'write'], + }, + }, + }, + }, + }, +]; + +const multiConfig: LocalLambdaGroupConfig = { + lambdas: config, + port: 8008, // optional, default to 8000 + defaultPath: '/api/v1', // optional, default to '/' +}; +const localLambdaGroup = new LocalLambdaGroup(multiConfig); +localLambdaGroup.run(); \ No newline at end of file diff --git a/src/local.lambda.ts b/src/local.lambda.ts index 48a71c4..a556e74 100644 --- a/src/local.lambda.ts +++ b/src/local.lambda.ts @@ -25,22 +25,27 @@ export class LocalLambda { enableCORS: boolean; binaryContentTypesOverride: Set; pathParamsPattern: string; + defaultPath: string; app: express.Application; requestContext: Record; - constructor(config: LocalLambdaConfig) { + constructor(config: LocalLambdaConfig, app?: express.Application, defaultPath?: string) { this.handler = config.handler; this.port = config.port ?? DefaultPort; this.context = config.context || {} as Context; this.enableCORS = config.enableCORS ?? true; this.binaryContentTypesOverride = new Set(config.binaryContentTypesOverride ?? defaultBinaryContentTypeHeaders); this.pathParamsPattern = config.pathParamsPattern ?? DefaultPathParamsPattern; - this.app = express(); + this.app = app || express(); + this.defaultPath = defaultPath ?? DefaultPathParamsPattern; this.requestContext = config.requestContext ?? {}; } - run(): void { - this.app.all(`${this.pathParamsPattern}*`,async (request: Request, response: Response) => { + createServer(): void { + const router = express.Router(); + this.app.use(this.defaultPath, router); + + router.all(`${this.pathParamsPattern}`, async (request: Request, response: Response) => { // create a copy of requestContext to avoid accidental mutation const copyOfRequestContext = cloneDeep(this.requestContext); const data: Buffer[] = []; @@ -84,6 +89,10 @@ export class LocalLambda { }); + } + + run(): void { + this.createServer(); this.app.listen(this.port, () => console.info(`🚀 Server ready at http://localhost:${this.port} at '${new Date().toLocaleString()}'`)); } @@ -96,9 +105,9 @@ export class LocalLambda { } -export interface LocalLambdaConfig { + +export interface LambdaConfig { handler: LambdaHandler; - port?: number; context?: Context; enableCORS?: boolean; // default binary content-type headers or override @@ -106,3 +115,37 @@ export interface LocalLambdaConfig { pathParamsPattern ?: string; requestContext?: Record; } + +// extend the LambdaConfig to add port +export interface LocalLambdaConfig extends LambdaConfig { + port?: number; +} + +export interface LocalLambdaGroupConfig { + lambdas: LambdaConfig[]; + port?: number; + defaultPath?: string; +} + +export class LocalLambdaGroup { + lambdas: LambdaConfig[] = []; + app: express.Application; + port: number; + defaultPath: string; + + constructor(config: LocalLambdaGroupConfig) { + this.lambdas = config.lambdas; + this.app = express(); + this.port = config.port ?? DefaultPort; + this.defaultPath = config.defaultPath ?? DefaultPathParamsPattern; + } + + run(): void { + this.lambdas.forEach(lambda => { + const localLambda = new LocalLambda(lambda, this.app, this.defaultPath); + localLambda.createServer(); + this.app = localLambda.app; + }); + this.app.listen(this.port, () => console.info(`🚀 Lambda Group Server ready at http://localhost:${this.port} at '${new Date().toLocaleString()}'`)); + } +} \ No newline at end of file From d201143fdc8cad603025b11420a4e9f57a07189b Mon Sep 17 00:00:00 2001 From: Bishwa Thapa Date: Sun, 23 Apr 2023 18:07:25 +0545 Subject: [PATCH 2/3] feat: Create new file for Lambda Group server --- src/example/multi.ts | 4 +++- src/index.ts | 3 ++- src/lambda.group.ts | 45 ++++++++++++++++++++++++++++++++++++++++++++ src/local.lambda.ts | 45 +++----------------------------------------- 4 files changed, 53 insertions(+), 44 deletions(-) create mode 100644 src/lambda.group.ts diff --git a/src/example/multi.ts b/src/example/multi.ts index 2b58ca3..93293b2 100644 --- a/src/example/multi.ts +++ b/src/example/multi.ts @@ -1,6 +1,6 @@ import { Context } from 'aws-lambda'; -import { LocalLambdaGroupConfig, LambdaConfig, LocalLambdaGroup } from '../local.lambda'; import { LambdaResponse, RequestEvent } from '../types'; +import { LambdaConfig, LocalLambdaGroupConfig, LocalLambdaGroup } from '../lambda.group'; // handler is a function that takes in an event and context and returns a response const handler = async (req: RequestEvent, context: Context): Promise => ({ statusCode: 200, body: `Hello World !!! My userId is ${req.pathParameters?.id}\n My JWT is ${JSON.stringify(req.requestContext.authorizer.lambda.jwt)}. My queryStringParameter is ${JSON.stringify(req.queryStringParameters)} ` }); @@ -51,5 +51,7 @@ const multiConfig: LocalLambdaGroupConfig = { port: 8008, // optional, default to 8000 defaultPath: '/api/v1', // optional, default to '/' }; + +// visit http://localhost:8008/user/1234567890 to see the response const localLambdaGroup = new LocalLambdaGroup(multiConfig); localLambdaGroup.run(); \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 723f654..8579904 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,2 +1,3 @@ export * from './local.lambda'; -export * from './types/index'; \ No newline at end of file +export * from './types/index'; +export * from './lambda.group'; \ No newline at end of file diff --git a/src/lambda.group.ts b/src/lambda.group.ts new file mode 100644 index 0000000..f2ebe25 --- /dev/null +++ b/src/lambda.group.ts @@ -0,0 +1,45 @@ +import { Context } from 'aws-lambda'; +import express from 'express'; +import { LambdaHandler } from 'index'; +import { DefaultPathParamsPattern, DefaultPort, LocalLambda } from './local.lambda'; + + +export interface LambdaConfig { + handler: LambdaHandler; + context?: Context; + enableCORS?: boolean; + // default binary content-type headers or override + binaryContentTypesOverride?: string[]; + pathParamsPattern ?: string; + requestContext?: Record; +} + + +export interface LocalLambdaGroupConfig { + lambdas: LambdaConfig[]; + port?: number; + defaultPath?: string; +} + +export class LocalLambdaGroup { + lambdas: LambdaConfig[] = []; + app: express.Application; + port: number; + defaultPath: string; + + constructor(config: LocalLambdaGroupConfig) { + this.lambdas = config.lambdas; + this.app = express(); + this.port = config.port ?? DefaultPort; + this.defaultPath = config.defaultPath ?? DefaultPathParamsPattern; + } + + run(): void { + this.lambdas.forEach(lambda => { + const localLambda = new LocalLambda(lambda, this.app, this.defaultPath); + localLambda.createServer(); + this.app = localLambda.app; + }); + this.app.listen(this.port, () => console.info(`🚀 Lambda Group Server ready at http://localhost:${this.port} at '${new Date().toLocaleString()}'`)); + } +} \ No newline at end of file diff --git a/src/local.lambda.ts b/src/local.lambda.ts index a556e74..16cb882 100644 --- a/src/local.lambda.ts +++ b/src/local.lambda.ts @@ -4,7 +4,8 @@ import HTTPMethod from 'http-method-enum'; import { LambdaHandler, RequestEvent } from './types'; import express, { Request, Response } from 'express'; import { flattenArraysInJSON, cloneDeep } from './utils'; -const DefaultPort = 8000; +import { LambdaConfig } from './lambda.group'; +export const DefaultPort = 8000; // binary upload content-type headers const defaultBinaryContentTypeHeaders = [ @@ -16,7 +17,7 @@ const defaultBinaryContentTypeHeaders = [ 'application/zip', ]; -const DefaultPathParamsPattern = '/'; +export const DefaultPathParamsPattern = '/'; export class LocalLambda { handler: LambdaHandler; @@ -105,47 +106,7 @@ export class LocalLambda { } - -export interface LambdaConfig { - handler: LambdaHandler; - context?: Context; - enableCORS?: boolean; - // default binary content-type headers or override - binaryContentTypesOverride?: string[]; - pathParamsPattern ?: string; - requestContext?: Record; -} - // extend the LambdaConfig to add port export interface LocalLambdaConfig extends LambdaConfig { port?: number; } - -export interface LocalLambdaGroupConfig { - lambdas: LambdaConfig[]; - port?: number; - defaultPath?: string; -} - -export class LocalLambdaGroup { - lambdas: LambdaConfig[] = []; - app: express.Application; - port: number; - defaultPath: string; - - constructor(config: LocalLambdaGroupConfig) { - this.lambdas = config.lambdas; - this.app = express(); - this.port = config.port ?? DefaultPort; - this.defaultPath = config.defaultPath ?? DefaultPathParamsPattern; - } - - run(): void { - this.lambdas.forEach(lambda => { - const localLambda = new LocalLambda(lambda, this.app, this.defaultPath); - localLambda.createServer(); - this.app = localLambda.app; - }); - this.app.listen(this.port, () => console.info(`🚀 Lambda Group Server ready at http://localhost:${this.port} at '${new Date().toLocaleString()}'`)); - } -} \ No newline at end of file From 58b4cc08e1d6a69972bc6b88717ddd4b7e7e1819 Mon Sep 17 00:00:00 2001 From: Bishwa Thapa Date: Sun, 7 May 2023 20:04:38 +0545 Subject: [PATCH 3/3] chore: address PR comments --- src/example/multi.ts | 2 +- src/lambda.group.ts | 3 +-- src/local.lambda.ts | 4 ++-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/example/multi.ts b/src/example/multi.ts index 93293b2..d97abe3 100644 --- a/src/example/multi.ts +++ b/src/example/multi.ts @@ -52,6 +52,6 @@ const multiConfig: LocalLambdaGroupConfig = { defaultPath: '/api/v1', // optional, default to '/' }; -// visit http://localhost:8008/user/1234567890 to see the response +// visit http://localhost:8008/api/v1/user/1234567890 and http://localhost:8008/api/v1/user/1234567890/funny to see the response const localLambdaGroup = new LocalLambdaGroup(multiConfig); localLambdaGroup.run(); \ No newline at end of file diff --git a/src/lambda.group.ts b/src/lambda.group.ts index f2ebe25..87abc39 100644 --- a/src/lambda.group.ts +++ b/src/lambda.group.ts @@ -37,8 +37,7 @@ export class LocalLambdaGroup { run(): void { this.lambdas.forEach(lambda => { const localLambda = new LocalLambda(lambda, this.app, this.defaultPath); - localLambda.createServer(); - this.app = localLambda.app; + localLambda.createRoute(); }); this.app.listen(this.port, () => console.info(`🚀 Lambda Group Server ready at http://localhost:${this.port} at '${new Date().toLocaleString()}'`)); } diff --git a/src/local.lambda.ts b/src/local.lambda.ts index 16cb882..a4badad 100644 --- a/src/local.lambda.ts +++ b/src/local.lambda.ts @@ -42,7 +42,7 @@ export class LocalLambda { this.requestContext = config.requestContext ?? {}; } - createServer(): void { + createRoute(): void { const router = express.Router(); this.app.use(this.defaultPath, router); @@ -93,7 +93,7 @@ export class LocalLambda { } run(): void { - this.createServer(); + this.createRoute(); this.app.listen(this.port, () => console.info(`🚀 Server ready at http://localhost:${this.port} at '${new Date().toLocaleString()}'`)); }