Skip to content

Commit

Permalink
strict type import/export
Browse files Browse the repository at this point in the history
updated esbuild

updated Router body-parser to parse form-urlencoded

introduce 'LOCAL' label to remove chunk of code in production

added quick start template
  • Loading branch information
Inqnuam committed Aug 14, 2023
1 parent 9281da7 commit 55ca9a2
Show file tree
Hide file tree
Showing 26 changed files with 248 additions and 227 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ yarn-error.log
/express
/router.d.ts
/router.js
TODO.md
TODO
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,16 @@

### **Installation**

**Quick start**

```bash
npx degit github:inqnuam/serverless-aws-lambda/templates/simple my-project
cd my-project && yarn install
yarn start
```

**Manual installation**

Usual node module installation...

```bash
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "serverless-aws-lambda",
"version": "4.6.1",
"version": "4.6.2",
"description": "AWS Application Load Balancer and API Gateway - Lambda dev tool for Serverless. Allows Express synthax in handlers. Supports packaging, local invoking and offline ALB, APG, S3, SNS, SQS, DynamoDB Stream server mocking.",
"author": "Inqnuam",
"license": "MIT",
Expand Down Expand Up @@ -61,7 +61,7 @@
"@smithy/eventstream-codec": "^2.0.3",
"@types/serverless": "^3.12.13",
"archiver": "^5.3.1",
"esbuild": "^0.19.1",
"esbuild": "^0.19.2",
"serve-static": "^1.15.0"
},
"devDependencies": {
Expand Down
20 changes: 13 additions & 7 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import path from "path";
import { Daemon } from "./lib/server/daemon";
import { ILambdaMock } from "./lib/runtime/rapidApi";
import type { ILambdaMock } from "./lib/runtime/rapidApi";
import { log } from "./lib/utils/colorize";
import { Zipper } from "./lib/utils/zip";
import esbuild from "esbuild";
Expand Down Expand Up @@ -184,6 +184,7 @@ class ServerlessAwsLambda extends Daemon {
outbase: "src",
plugins: [],
external: ["esbuild"],
dropLabels: [],
};

if (typeof process.env.NODE_ENV == "string") {
Expand All @@ -205,7 +206,9 @@ class ServerlessAwsLambda extends Daemon {
esBuildConfig.target = `node${this.nodeVersion}`;
esBuildConfig.plugins?.push(buildOptimizer({ isLocal, nodeVersion: this.nodeVersion, buildCallback: this.buildCallback }));
}

if (!isLocal) {
esBuildConfig.dropLabels!.push("LOCAL");
}
if (this.customEsBuildConfig) {
esBuildConfig = mergeEsbuildConfig(esBuildConfig, this.customEsBuildConfig);
}
Expand Down Expand Up @@ -331,6 +334,8 @@ class ServerlessAwsLambda extends Daemon {

// @ts-ignore
const Outputs = this.serverless.service.resources?.Outputs;
const region = this.runtimeConfig.environment.AWS_REGION ?? this.runtimeConfig.environment.REGION;

const lambdas = functionsNames.reduce((accum: any[], funcName: string) => {
const lambda = funcs[funcName];

Expand All @@ -339,7 +344,6 @@ class ServerlessAwsLambda extends Daemon {
const handlerName = ext.slice(1);
const esEntryPoint = path.resolve(handlerPath.replace(ext, ""));

const region = this.runtimeConfig.environment.AWS_REGION ?? this.runtimeConfig.environment.REGION;
const slsDeclaration: any = this.serverless.service.getFunction(funcName);
const runtime = slsDeclaration.runtime ?? defaultRuntime;

Expand Down Expand Up @@ -493,9 +497,11 @@ class ServerlessAwsLambda extends Daemon {
this.runtimeConfig.timeout = timeout;
this.runtimeConfig.environment = environment ?? {};

if (process.env.AWS_PROFILE) {
this.runtimeConfig.environment.AWS_PROFILE = process.env.AWS_PROFILE;
}
const awsEnvKeys = Object.keys(process.env).filter((x) => x.startsWith("AWS_"));

awsEnvKeys.forEach((x: string) => {
this.runtimeConfig.environment[x] = process.env[x] as string;
});
}

setEnv(lambdaName: string, key: string, value: string) {
Expand All @@ -511,7 +517,7 @@ class ServerlessAwsLambda extends Daemon {
slsDeclaration.environment[key] = value;
}
} else {
log.RED(`Can not set env var on '${lambdaName}'`);
log.RED(`Can not set env variable '${key}' on '${lambdaName}'`);
}
}
async #setCustomEsBuildConfig() {
Expand Down
68 changes: 37 additions & 31 deletions src/lambda/body-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,42 +3,47 @@ import type { RouteController, IRequest } from "./router";
const parse = (event: IRequest, spotText: boolean) => {
const files: any[] = [];
let body: any = {};
const boundary = event.headers?.["content-type"]?.split("=")[1];
if (!boundary) {
return {
files,
body,
};
}
const result: any = {};

if (event.isBase64Encoded) {
event.body = Buffer.from(event.body, "base64").toString("utf-8");
}
event.body.split(boundary).forEach((item: any) => {
if (/filename=".+"/g.test(item)) {
result[item.match(/name=".+";/g)[0].slice(6, -2)] = {
type: "file",
filename: item.match(/filename=".+"/g)[0].slice(10, -1),
contentType: item.match(/Content-Type:\s.+/g)[0].slice(14),
content: spotText
? Buffer.from(item.slice(item.search(/Content-Type:\s.+/g) + item.match(/Content-Type:\s.+/g)[0].length + 4, -4), "binary")
: item.slice(item.search(/Content-Type:\s.+/g) + item.match(/Content-Type:\s.+/g)[0].length + 4, -4),
};
} else if (/name=".+"/g.test(item)) {
result[item.match(/name=".+"/g)[0].slice(6, -1)] = item.slice(item.search(/name=".+"/g) + item.match(/name=".+"/g)[0].length + 4, -4);
}
});

for (const [key, value] of Object.entries(result)) {
if (typeof value == "string") {
body[key] = value;
} else if (value && typeof value == "object") {
files.push({
name: key,
...value,
});

const contentType = event.headers?.["content-type"];
const boundary = contentType?.split("=")[1];
if (boundary) {
const result: any = {};

event.body.split(boundary).forEach((item: any) => {
if (/filename=".+"/g.test(item)) {
result[item.match(/name=".+";/g)[0].slice(6, -2)] = {
type: "file",
filename: item.match(/filename=".+"/g)[0].slice(10, -1),
contentType: item.match(/Content-Type:\s.+/g)[0].slice(14),
content: spotText
? Buffer.from(item.slice(item.search(/Content-Type:\s.+/g) + item.match(/Content-Type:\s.+/g)[0].length + 4, -4), "binary")
: item.slice(item.search(/Content-Type:\s.+/g) + item.match(/Content-Type:\s.+/g)[0].length + 4, -4),
};
} else if (/name=".+"/g.test(item)) {
result[item.match(/name=".+"/g)[0].slice(6, -1)] = item.slice(item.search(/name=".+"/g) + item.match(/name=".+"/g)[0].length + 4, -4);
}
});

for (const [key, value] of Object.entries(result)) {
if (typeof value == "string") {
body[key] = value;
} else if (value && typeof value == "object") {
files.push({
name: key,
...value,
});
}
}
} else if (contentType == "application/x-www-form-urlencoded") {
event.body.split("&").forEach((x: string) => {
const [key, value] = x.split("=");
body[decodeURIComponent(key)] = decodeURIComponent(value);
});
event.body = body;
}

return {
Expand All @@ -52,6 +57,7 @@ const bodyParser: RouteController = (req, res, next) => {
const parsedBody = parse(req, true);
req.files = parsedBody.files;
req.body = parsedBody.body;
req.isBase64Encoded = false;
} catch (error) {}

next();
Expand Down
2 changes: 1 addition & 1 deletion src/lambda/express/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export interface KnownRequestProperties {
*
* Tries to parse with JSON.parse().
*
* Use `body-parser` middleware from `serverless-aws-lambda/body-parser` to parse Form-Data.
* Use `body-parser` middleware from `serverless-aws-lambda/body-parser` to parse Form-Data and x-www-form-urlencoded.
*/
body: any;
method: HttpMethod;
Expand Down
6 changes: 3 additions & 3 deletions src/lambda/express/response.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { RawResponseContent } from "./request";
import { cookie, CookieOptions } from "./cookies";

import type { RawResponseContent } from "./request";
import { cookie } from "./cookies";
import type { CookieOptions } from "./cookies";
export type RedirectOptions = [code: number, path: string];
type Stringifiable = [] | { [key: string]: any } | null | boolean;
export interface IResponse {
Expand Down
9 changes: 6 additions & 3 deletions src/lambda/router.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import EventEmitter from "events";
import { _buildUniversalEvent, IRequest } from "./express/request";
import { _Response, IResponse } from "./express/response";
import { _buildUniversalEvent } from "./express/request";
import { _Response } from "./express/response";

import type { IRequest } from "./express/request";
import type { IResponse } from "./express/response";
export type { IRequest, IResponse };

export { IRequest, IResponse };
export type NextFunction = (error?: any) => void;
export type RouteMiddleware = (error: any, req: IRequest, res: IResponse, next: NextFunction) => Promise<void> | void;
export type RouteController = (req: IRequest, res: IResponse, next: NextFunction) => Promise<void> | void;
Expand Down
8 changes: 5 additions & 3 deletions src/lib/esbuild/mergeEsbuildConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,7 @@ export const mergeEsbuildConfig = (esBuildConfig: BuildOptions, customEsBuildCon
esBuildConfig.tsconfig = customEsBuildConfig.tsconfig;
}

// @ts-ignore
if (typeof customEsBuildConfig.tsconfigRaw == "string") {
// @ts-ignore
if (customEsBuildConfig.tsconfigRaw && ["string", "object"].includes(typeof customEsBuildConfig.tsconfigRaw)) {
esBuildConfig.tsconfigRaw = customEsBuildConfig.tsconfigRaw;
}

Expand All @@ -102,6 +100,10 @@ export const mergeEsbuildConfig = (esBuildConfig: BuildOptions, customEsBuildCon
esBuildConfig.drop = customEsBuildConfig.drop;
}

if (Array.isArray(customEsBuildConfig.dropLabels)) {
esBuildConfig.dropLabels!.push(...customEsBuildConfig.dropLabels);
}

if (Array.isArray(customEsBuildConfig.resolveExtensions)) {
esBuildConfig.resolveExtensions = customEsBuildConfig.resolveExtensions;
}
Expand Down
9 changes: 6 additions & 3 deletions src/lib/esbuild/parseCustomEsbuild.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,8 @@ export const parseCustomEsbuild = (customConfig: BuildOptions) => {
if (typeof customConfig.tsconfig == "string") {
customEsBuild.tsconfig = customConfig.tsconfig;
}
// @ts-ignore
if (typeof customConfig.tsconfigRaw == "string") {
// @ts-ignore

if (customConfig.tsconfigRaw && ["string", "object"].includes(typeof customConfig.tsconfigRaw)) {
customEsBuild.tsconfigRaw = customConfig.tsconfigRaw;
}

Expand All @@ -99,6 +98,10 @@ export const parseCustomEsbuild = (customConfig: BuildOptions) => {
customEsBuild.drop = customConfig.drop;
}

if (Array.isArray(customConfig.dropLabels)) {
customEsBuild.dropLabels = customConfig.dropLabels;
}

if (Array.isArray(customConfig.resolveExtensions)) {
customEsBuild.resolveExtensions = customConfig.resolveExtensions;
}
Expand Down
2 changes: 1 addition & 1 deletion src/lib/parseEvents/endpoints.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { HttpMethod } from "../server/handlers";
import type { HttpMethod } from "../server/handlers";
import { log } from "../utils/colorize";

const pathPartsRegex = /^(\{[\w.:-]+\+?\}|[a-zA-Z0-9.:_-]+)$/;
Expand Down
3 changes: 2 additions & 1 deletion src/lib/parseEvents/kinesis.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { log } from "../utils/colorize";
import { IDestination, parseDestination } from "./index";
import { parseDestination } from "./index";
import type { IDestination } from "./index";

export interface IKinesisEvent {
StreamName: string;
Expand Down
4 changes: 3 additions & 1 deletion src/lib/runtime/awslambda.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { HttpResponseStream, StreamableHandler } from "./streamResponse";
import type { StreamableHandler } from "./streamResponse";
import type { IHttpResponseStream } from "./streamResponse";
import { HttpResponseStream } from "./streamResponse";

type HandlerMetadata = {
highWaterMark?: number;
};
Expand Down
4 changes: 2 additions & 2 deletions src/lib/runtime/runners/node/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,10 +215,10 @@ export class EventQueue extends Map {
if (timer.type != "Timeout") {
return;
}
const { _idleTimeout, _idleStart, _onTimeout, _repeat } = timer.resource;
const { _idleTimeout, _idleStart, _onTimeout, _repeat, _destroyed } = timer.resource;

const timerValue = _idleTimeout - _idleStart;
if (timerValue > -1 || _repeat) {
if (!_destroyed && (timerValue > -1 || _repeat)) {
EventQueue.context[this.requestId].timers.set(id, { timerValue, _onTimeout, interval: _repeat });
}

Expand Down
5 changes: 3 additions & 2 deletions src/lib/server/daemon.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import http, { Server, IncomingMessage, ServerResponse } from "http";
import { AddressInfo } from "net";
import type { AddressInfo } from "net";
import { networkInterfaces } from "os";
import { Handlers } from "./handlers";
import { ILambdaMock, LambdaMock } from "../runtime/rapidApi";
import type { ILambdaMock, } from "../runtime/rapidApi";
import { LambdaMock } from "../runtime/rapidApi";
import { log } from "../utils/colorize";
import inspector from "inspector";
import { html404 } from "../../plugins/lambda/htmlStatusMsg";
Expand Down
2 changes: 1 addition & 1 deletion src/lib/server/handlers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ILambdaMock, LambdaEndpoint } from "../runtime/rapidApi";
import type { ILambdaMock, LambdaEndpoint } from "../runtime/rapidApi";
import { log } from "../utils/colorize";
import type { normalizedSearchParams } from "../../plugins/lambda/events/common";

Expand Down
3 changes: 1 addition & 2 deletions src/plugins/lambda/defaultServer/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { IncomingMessage, IncomingHttpHeaders, ServerResponse } from "http";
import { CommonEventGenerator } from "../events/common";
import { Handlers } from "../../../lib/server/handlers";
import { BufferedStreamResponse } from "../../../lib/runtime/bufferedStreamResponse";
Expand All @@ -8,7 +7,7 @@ import { ApgRequestHandler } from "../events/apg";
import { randomUUID } from "crypto";
import type { HttpMethod } from "../../../lib/server/handlers";
import type { LambdaEndpoint } from "../../../lib/parseEvents/endpoints";

import type { IncomingMessage, IncomingHttpHeaders, ServerResponse } from "http";
const getRequestMockType = (searchParams: URLSearchParams, headers: IncomingHttpHeaders) => {
if (searchParams.get("x_mock_type") !== null) {
return searchParams.get("x_mock_type");
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/lambda/events/common.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IncomingMessage, IncomingHttpHeaders } from "http";
import type { IncomingMessage, IncomingHttpHeaders } from "http";
import type { LambdaEndpoint } from "../../../lib/parseEvents/endpoints";

export type normalizedSearchParams = { toString: () => string } & { [key: string]: string[] | undefined };
Expand Down
3 changes: 2 additions & 1 deletion src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,5 @@ export class Server {
}
}

export { ServerConfig, exitEvents };
export type { ServerConfig, };
export {exitEvents}
2 changes: 0 additions & 2 deletions templates/simple/config/sls.d.ts

This file was deleted.

8 changes: 0 additions & 8 deletions templates/simple/src/lambda/helloWorld.d.ts

This file was deleted.

5 changes: 5 additions & 0 deletions templates/simple/src/lambda/helloWorld.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
export default async (event, context) => {
const name = event.queryStringParameters?.name ?? "World";

LOCAL: {
console.log("log this message only on local server")
}

return {
statusCode: 200,
header: {
Expand Down
1 change: 0 additions & 1 deletion templates/simple/test/e2e/lambda/helloWorld.test.d.ts

This file was deleted.

2 changes: 0 additions & 2 deletions templates/simple/vitest.e2e.config.d.mts

This file was deleted.

Loading

0 comments on commit 55ca9a2

Please sign in to comment.