@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 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
@@ -0,0 +1,2 @@
1
+ export * from './src/handler.ts';
2
+ export * from './src/util.ts';
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,3 @@
1
+ // @trv-no-transform
2
+ import { AwsLambdaWebHandler } from '../src/handler.ts';
3
+ export const handler = AwsLambdaWebHandler.entryPoint();
@@ -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
+ }