@travetto/web-aws-lambda 6.0.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -0
- package/__index__.ts +2 -0
- package/package.json +53 -0
- package/src/handler.ts +38 -0
- package/src/util.ts +59 -0
- package/support/cli.pack_lambda.ts +29 -0
- package/support/entry.handler.ts +3 -0
- package/support/test/dispatcher.ts +81 -0
package/README.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<!-- This file was generated by @travetto/doc and should not be modified directly -->
|
|
2
|
+
<!-- Please modify https://github.com/travetto/travetto/tree/main/module/web-aws-lambda/DOC.tsx and execute "npx trv doc" to rebuild -->
|
|
3
|
+
# Web AWS Lambda
|
|
4
|
+
|
|
5
|
+
## Web APIs entry point support for AWS Lambdas.
|
|
6
|
+
|
|
7
|
+
**Install: @travetto/web-aws-lambda**
|
|
8
|
+
```bash
|
|
9
|
+
npm install @travetto/web-aws-lambda
|
|
10
|
+
|
|
11
|
+
# or
|
|
12
|
+
|
|
13
|
+
yarn add @travetto/web-aws-lambda
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
This module provides an adapter between [Web API](https://github.com/travetto/travetto/tree/main/module/web#readme "Declarative api for Web Applications with support for the dependency injection.") and [AWS Lambda](https://aws.amazon.com/lambda/). The event-driven invocation model for [Web API](https://github.com/travetto/travetto/tree/main/module/web#readme "Declarative api for Web Applications with support for the dependency injection.") aligns cleanly with [AWS Lambda](https://aws.amazon.com/lambda/)'s model for event-driven operation.
|
|
17
|
+
|
|
18
|
+
**NOTE:** The only caveat to consider, is that while the framework supports streams for responses, [AWS Lambda](https://aws.amazon.com/lambda/) does not. Any streaming result will be read and converted into a [Buffer](https://nodejs.org/api/buffer.html) before being sent back.
|
|
19
|
+
|
|
20
|
+
## CLI - Packaging Lambdas
|
|
21
|
+
|
|
22
|
+
**Terminal: Invoking a Package Build**
|
|
23
|
+
```bash
|
|
24
|
+
$ trv pack:lambda -h
|
|
25
|
+
|
|
26
|
+
Usage: pack:lambda [options]
|
|
27
|
+
|
|
28
|
+
Options:
|
|
29
|
+
-b, --build-dir <string> Workspace for building (default: "/tmp/<temp-folder>")
|
|
30
|
+
--clean, --no-clean Clean workspace (default: true)
|
|
31
|
+
-o, --output <string> Output location (default: "<module>.zip")
|
|
32
|
+
-es, --main-scripts Create entry scripts (default: false)
|
|
33
|
+
-f, --main-name <string> Main name for build artifact
|
|
34
|
+
-e, --entry-point <string> Entry point (default: "@travetto/web-aws-lambda/support/entry.handler.ts")
|
|
35
|
+
--minify, --no-minify Minify output (default: true)
|
|
36
|
+
-sm, --sourcemap Bundle source maps (default: false)
|
|
37
|
+
-is, --include-sources Include source with source maps (default: false)
|
|
38
|
+
-x, --eject-file <string> Eject commands to file
|
|
39
|
+
-r, --rollup-configuration <string> Rollup configuration file (default: "@travetto/pack/support/rollup/build.ts")
|
|
40
|
+
--env-file <string> Env Flag File Name (default: ".env")
|
|
41
|
+
--manifest-file <string> Manifest File Name (default: "manifest.json")
|
|
42
|
+
-wr, --include-workspace-resources Include workspace resources (default: false)
|
|
43
|
+
-np, --npm-package <string> External NPM Packages (default: [])
|
|
44
|
+
-m, --module <module> Module to run for
|
|
45
|
+
-h, --help display help for command
|
|
46
|
+
```
|
package/__index__.ts
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@travetto/web-aws-lambda",
|
|
3
|
+
"version": "6.0.0-rc.2",
|
|
4
|
+
"description": "Web APIs entry point support for AWS Lambdas.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"web",
|
|
7
|
+
"aws-lambda",
|
|
8
|
+
"travetto",
|
|
9
|
+
"typescript"
|
|
10
|
+
],
|
|
11
|
+
"homepage": "https://travetto.io",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"author": {
|
|
14
|
+
"email": "travetto.framework@gmail.com",
|
|
15
|
+
"name": "Travetto Framework"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"__index__.ts",
|
|
19
|
+
"src",
|
|
20
|
+
"support"
|
|
21
|
+
],
|
|
22
|
+
"main": "__index__.ts",
|
|
23
|
+
"repository": {
|
|
24
|
+
"url": "git+https://github.com/travetto/travetto.git",
|
|
25
|
+
"directory": "module/web-aws-lambda"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@travetto/web": "^6.0.0-rc.2",
|
|
29
|
+
"@types/aws-lambda": "^8.10.149"
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"@travetto/cli": "^6.0.0-rc.2",
|
|
33
|
+
"@travetto/pack": "^6.0.0-rc.2",
|
|
34
|
+
"@travetto/test": "^6.0.0-rc.2"
|
|
35
|
+
},
|
|
36
|
+
"peerDependenciesMeta": {
|
|
37
|
+
"@travetto/test": {
|
|
38
|
+
"optional": true
|
|
39
|
+
},
|
|
40
|
+
"@travetto/pack": {
|
|
41
|
+
"optional": true
|
|
42
|
+
},
|
|
43
|
+
"@travetto/cli": {
|
|
44
|
+
"optional": true
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"travetto": {
|
|
48
|
+
"displayName": "Web AWS Lambda"
|
|
49
|
+
},
|
|
50
|
+
"publishConfig": {
|
|
51
|
+
"access": "public"
|
|
52
|
+
}
|
|
53
|
+
}
|
package/src/handler.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type lambda from 'aws-lambda';
|
|
2
|
+
|
|
3
|
+
import { Runtime, ConsoleManager } from '@travetto/runtime';
|
|
4
|
+
import { DependencyRegistry, Inject, Injectable } from '@travetto/di';
|
|
5
|
+
import { RootRegistry } from '@travetto/registry';
|
|
6
|
+
import { ConfigurationService } from '@travetto/config';
|
|
7
|
+
import { StandardWebRouter } from '@travetto/web';
|
|
8
|
+
|
|
9
|
+
import { AwsLambdaWebUtil } from './util.ts';
|
|
10
|
+
|
|
11
|
+
@Injectable()
|
|
12
|
+
export class AwsLambdaWebHandler {
|
|
13
|
+
|
|
14
|
+
static inst: AwsLambdaWebHandler;
|
|
15
|
+
|
|
16
|
+
static entryPoint(): (event: lambda.APIGatewayProxyEvent, context: lambda.Context) => Promise<lambda.APIGatewayProxyResult> {
|
|
17
|
+
ConsoleManager.debug(Runtime.debug);
|
|
18
|
+
|
|
19
|
+
return async (event, context) => {
|
|
20
|
+
if (!this.inst) {
|
|
21
|
+
await RootRegistry.init();
|
|
22
|
+
await DependencyRegistry.getInstance(ConfigurationService).then(v => v.initBanner());
|
|
23
|
+
this.inst = await DependencyRegistry.getInstance(AwsLambdaWebHandler);
|
|
24
|
+
}
|
|
25
|
+
return this.inst.handle(event, context);
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@Inject()
|
|
30
|
+
router: StandardWebRouter;
|
|
31
|
+
|
|
32
|
+
async handle(event: lambda.APIGatewayProxyEvent, context: lambda.Context): Promise<lambda.APIGatewayProxyResult> {
|
|
33
|
+
context.callbackWaitsForEmptyEventLoop = false;
|
|
34
|
+
const request = AwsLambdaWebUtil.toWebRequest(event);
|
|
35
|
+
const response = await this.router.dispatch({ request });
|
|
36
|
+
return AwsLambdaWebUtil.toLambdaResult(response, event.isBase64Encoded);
|
|
37
|
+
}
|
|
38
|
+
}
|
package/src/util.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
|
|
2
|
+
|
|
3
|
+
import { castTo } from '@travetto/runtime';
|
|
4
|
+
import { WebBodyUtil, WebCommonUtil, WebRequest, WebResponse } from '@travetto/web';
|
|
5
|
+
|
|
6
|
+
export class AwsLambdaWebUtil {
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Create a request from an api gateway event
|
|
10
|
+
*/
|
|
11
|
+
static toWebRequest(event: APIGatewayProxyEvent): WebRequest {
|
|
12
|
+
// Build request
|
|
13
|
+
const body = event.body ? Buffer.from(event.body, event.isBase64Encoded ? 'base64' : 'utf8') : undefined;
|
|
14
|
+
|
|
15
|
+
return new WebRequest({
|
|
16
|
+
context: {
|
|
17
|
+
connection: {
|
|
18
|
+
httpProtocol: 'http',
|
|
19
|
+
ip: event.requestContext.identity?.sourceIp,
|
|
20
|
+
},
|
|
21
|
+
httpMethod: castTo(event.httpMethod.toUpperCase()),
|
|
22
|
+
httpQuery: castTo(event.queryStringParameters!),
|
|
23
|
+
path: event.path,
|
|
24
|
+
},
|
|
25
|
+
headers: { ...event.headers, ...event.multiValueHeaders },
|
|
26
|
+
body: WebBodyUtil.markRaw(body)
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Create an API Gateway result from a web response
|
|
32
|
+
*/
|
|
33
|
+
static async toLambdaResult(response: WebResponse, base64Encoded: boolean = false): Promise<APIGatewayProxyResult> {
|
|
34
|
+
const binaryResponse = new WebResponse({
|
|
35
|
+
context: response.context,
|
|
36
|
+
...WebBodyUtil.toBinaryMessage(response)
|
|
37
|
+
});
|
|
38
|
+
const output = binaryResponse.body ? await WebBodyUtil.toBuffer(binaryResponse.body!) : Buffer.alloc(0);
|
|
39
|
+
const isBase64Encoded = !!output.length && base64Encoded;
|
|
40
|
+
const headers: Record<string, string> = {};
|
|
41
|
+
const multiValueHeaders: Record<string, string[]> = {};
|
|
42
|
+
|
|
43
|
+
binaryResponse.headers.forEach((v, k) => {
|
|
44
|
+
if (Array.isArray(v)) {
|
|
45
|
+
multiValueHeaders[k] = v;
|
|
46
|
+
} else {
|
|
47
|
+
headers[k] = v;
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
statusCode: WebCommonUtil.getStatusCode(binaryResponse),
|
|
53
|
+
isBase64Encoded,
|
|
54
|
+
body: output.toString(isBase64Encoded ? 'base64' : 'utf8'),
|
|
55
|
+
headers,
|
|
56
|
+
multiValueHeaders,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { CliCommand, CliUtil } from '@travetto/cli';
|
|
2
|
+
import { PackOperation } from '@travetto/pack/support/bin/operation.ts';
|
|
3
|
+
import { BasePackCommand, PackOperationShape } from '@travetto/pack/support/pack.base.ts';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Standard lambda support for pack
|
|
7
|
+
*/
|
|
8
|
+
@CliCommand({ with: { module: true } })
|
|
9
|
+
export class PackLambdaCommand extends BasePackCommand {
|
|
10
|
+
|
|
11
|
+
preMain(): void {
|
|
12
|
+
this.entryPoint ??= '@travetto/web-aws-lambda/support/entry.handler.ts';
|
|
13
|
+
this.output ??= CliUtil.getSimpleModuleName('<module>.zip', this.module);
|
|
14
|
+
this.mainScripts = false;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
preHelp(): void {
|
|
18
|
+
this.output = undefined!;
|
|
19
|
+
this.entryPoint = undefined!;
|
|
20
|
+
this.preMain();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
getOperations(): PackOperationShape<this>[] {
|
|
24
|
+
return [
|
|
25
|
+
...super.getOperations(),
|
|
26
|
+
PackOperation.compress
|
|
27
|
+
];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { APIGatewayProxyEvent, Context } from 'aws-lambda';
|
|
2
|
+
|
|
3
|
+
import { Inject, Injectable } from '@travetto/di';
|
|
4
|
+
import { WebDispatcher, WebFilterContext, WebRequest, WebResponse } from '@travetto/web';
|
|
5
|
+
import { AppError, asFull, castTo } from '@travetto/runtime';
|
|
6
|
+
|
|
7
|
+
import { WebTestDispatchUtil } from '@travetto/web/support/test/dispatch-util.ts';
|
|
8
|
+
|
|
9
|
+
import { AwsLambdaWebHandler } from '../../src/handler.ts';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Create an api gateway event given a web request
|
|
13
|
+
*/
|
|
14
|
+
function toLambdaEvent(request: WebRequest): APIGatewayProxyEvent {
|
|
15
|
+
const body = request.body;
|
|
16
|
+
const headers: Record<string, string> = {};
|
|
17
|
+
const multiValueHeaders: Record<string, string[]> = {};
|
|
18
|
+
const queryStringParameters: Record<string, string> = {};
|
|
19
|
+
const multiValueQueryStringParameters: Record<string, string[]> = {};
|
|
20
|
+
|
|
21
|
+
if (!(body === undefined || body === null || Buffer.isBuffer(body))) {
|
|
22
|
+
throw new AppError('Unsupported request type, only buffer bodies supported');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
request.headers.forEach((v, k) => {
|
|
26
|
+
headers[k] = Array.isArray(v) ? v.join('; ') : v;
|
|
27
|
+
multiValueHeaders[k] = request.headers.getList(k) ?? [];
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
Object.entries(request.context.httpQuery ?? {}).forEach(([k, v]) => {
|
|
31
|
+
if (Array.isArray(v)) {
|
|
32
|
+
multiValueQueryStringParameters[k] = v;
|
|
33
|
+
} else {
|
|
34
|
+
queryStringParameters[k] = v?.toString() ?? '';
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
resource: '/{proxy+}',
|
|
40
|
+
pathParameters: {},
|
|
41
|
+
stageVariables: {},
|
|
42
|
+
path: request.context.path,
|
|
43
|
+
httpMethod: request.context.httpMethod ?? 'POST',
|
|
44
|
+
queryStringParameters,
|
|
45
|
+
multiValueQueryStringParameters,
|
|
46
|
+
headers,
|
|
47
|
+
multiValueHeaders,
|
|
48
|
+
isBase64Encoded: true,
|
|
49
|
+
body: body?.toString('base64')!,
|
|
50
|
+
requestContext: castTo({
|
|
51
|
+
identity: castTo({ sourceIp: '127.0.0.1' }),
|
|
52
|
+
}),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* AWS Lambda support for invoking directly
|
|
58
|
+
*/
|
|
59
|
+
@Injectable()
|
|
60
|
+
export class LocalAwsLambdaWebDispatcher implements WebDispatcher {
|
|
61
|
+
|
|
62
|
+
@Inject()
|
|
63
|
+
app: AwsLambdaWebHandler;
|
|
64
|
+
|
|
65
|
+
async dispatch({ request }: WebFilterContext): Promise<WebResponse> {
|
|
66
|
+
const event = toLambdaEvent(await WebTestDispatchUtil.applyRequestBody(request));
|
|
67
|
+
|
|
68
|
+
const response = await this.app.handle(event, asFull<Context>({}));
|
|
69
|
+
|
|
70
|
+
return WebTestDispatchUtil.finalizeResponseBody(
|
|
71
|
+
new WebResponse<unknown>({
|
|
72
|
+
body: Buffer.from(response.body, response.isBase64Encoded ? 'base64' : 'utf8'),
|
|
73
|
+
headers: { ...response.headers ?? {}, ...response.multiValueHeaders ?? {} },
|
|
74
|
+
context: {
|
|
75
|
+
httpStatusCode: response.statusCode
|
|
76
|
+
}
|
|
77
|
+
}),
|
|
78
|
+
true
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
}
|