@aesop-fables/triginta 0.2.0
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/LICENSE +21 -0
- package/README.md +47 -0
- package/lib/Decorators.d.ts +14 -0
- package/lib/Decorators.js +53 -0
- package/lib/HttpLambda.d.ts +20 -0
- package/lib/HttpLambda.js +78 -0
- package/lib/HttpLambdaServices.d.ts +3 -0
- package/lib/HttpLambdaServices.js +6 -0
- package/lib/IConfiguredRoute.d.ts +5 -0
- package/lib/IConfiguredRoute.js +2 -0
- package/lib/IHandler.d.ts +3 -0
- package/lib/IHandler.js +2 -0
- package/lib/IHttpEndpoint.d.ts +4 -0
- package/lib/IHttpEndpoint.js +2 -0
- package/lib/Middleware.d.ts +10 -0
- package/lib/Middleware.js +72 -0
- package/lib/RouteRegistry.d.ts +8 -0
- package/lib/RouteRegistry.js +14 -0
- package/lib/index.d.ts +9 -0
- package/lib/index.js +22 -0
- package/lib/invokeHttpHandler.d.ts +11 -0
- package/lib/invokeHttpHandler.js +145 -0
- package/package.json +61 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Aesop
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# @aesop-fables/triginta
|
|
2
|
+
|
|
3
|
+
`triginta` is a lightweight framework that wraps the basic infrastructure usages of AWS Lambda (SQS, Kinesis, etc.). For APIs, see our `API framework`. It aims
|
|
4
|
+
to conventionalize logging, error handling, and X-Ray integration.
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
```
|
|
8
|
+
npm install @aesop-fables/triginta
|
|
9
|
+
```
|
|
10
|
+
```
|
|
11
|
+
yarn add @aesop-fables/triginta
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Supported Lambdas
|
|
15
|
+
The following can be created via the `ILambdaFactory` interface:
|
|
16
|
+
|
|
17
|
+
1. SQS Lambdas (`createSqsLambda`)
|
|
18
|
+
2. S3 Lambdas (`createS3Lambda`)
|
|
19
|
+
3. Kinesis Lambdas (`createKinesisLambda`)
|
|
20
|
+
|
|
21
|
+
## Known issues
|
|
22
|
+
We're missing unit tests, logging, error handling, middleware support, and X-Ray integration. This will all come soon - I just wanted to get the basic structure published.
|
|
23
|
+
|
|
24
|
+
## Example
|
|
25
|
+
```typescript
|
|
26
|
+
// index.ts
|
|
27
|
+
interface MyQueuedMessage {
|
|
28
|
+
message: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
class MyLambda implements IHandler<MyQueuedMessage> {
|
|
32
|
+
async handle(message: MyQueuedMessage): Promise<void> {
|
|
33
|
+
console.log('HELLO, WORLD!');
|
|
34
|
+
console.log(JSON.stringify(message, null, 2));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// obviously you need to register all of your stuff
|
|
39
|
+
const container = bootstrap();
|
|
40
|
+
// triginta doesn't depend on containr but it abstracts out the creation of your handler
|
|
41
|
+
// so that you can easily plug it in (see the `factory` property)
|
|
42
|
+
const lambdaFactory = container.get<ILambdaFactory>();
|
|
43
|
+
|
|
44
|
+
export const handler = lambdaFactory.createSqsLambda<MyLambda>({
|
|
45
|
+
factory: () => container.get<MyLambda>('lambda'),
|
|
46
|
+
});
|
|
47
|
+
```
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { IConfiguredRoute } from './IConfiguredRoute';
|
|
2
|
+
export declare const endpointMetadataKey: unique symbol;
|
|
3
|
+
export declare function getRoute(target: Function): IConfiguredRoute | undefined;
|
|
4
|
+
export declare function httpGet(route: string): (target: Object) => void;
|
|
5
|
+
export declare function httpPut(route: string): (target: Object) => void;
|
|
6
|
+
export declare function httpDelete(route: string): (target: Object) => void;
|
|
7
|
+
export declare function httpPost(route: string): (target: Object) => void;
|
|
8
|
+
/**
|
|
9
|
+
* This is exposed internally for testing.
|
|
10
|
+
* There shouldn't be a need to export this publicly
|
|
11
|
+
*/
|
|
12
|
+
export declare const middlewareMetadataKey: unique symbol;
|
|
13
|
+
export declare function useMiddleware(...args: any[]): (target: Object) => void;
|
|
14
|
+
export declare function getMiddleware(target: any): any[] | undefined;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getMiddleware = exports.useMiddleware = exports.middlewareMetadataKey = exports.httpPost = exports.httpDelete = exports.httpPut = exports.httpGet = exports.getRoute = exports.endpointMetadataKey = void 0;
|
|
7
|
+
const RouteRegistry_1 = __importDefault(require("./RouteRegistry"));
|
|
8
|
+
/* eslint-disable @typescript-eslint/ban-types */
|
|
9
|
+
exports.endpointMetadataKey = Symbol('@endpointMetadataKey');
|
|
10
|
+
function defineEndpointMetadata(method, route) {
|
|
11
|
+
return (target) => {
|
|
12
|
+
const params = { method, route, constructor: target };
|
|
13
|
+
Reflect.defineMetadata(exports.endpointMetadataKey, params, target);
|
|
14
|
+
RouteRegistry_1.default.register(params);
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function getRoute(target) {
|
|
18
|
+
return Reflect.getMetadata(exports.endpointMetadataKey, target);
|
|
19
|
+
}
|
|
20
|
+
exports.getRoute = getRoute;
|
|
21
|
+
function httpGet(route) {
|
|
22
|
+
return defineEndpointMetadata('get', route);
|
|
23
|
+
}
|
|
24
|
+
exports.httpGet = httpGet;
|
|
25
|
+
function httpPut(route) {
|
|
26
|
+
return defineEndpointMetadata('put', route);
|
|
27
|
+
}
|
|
28
|
+
exports.httpPut = httpPut;
|
|
29
|
+
function httpDelete(route) {
|
|
30
|
+
return defineEndpointMetadata('delete', route);
|
|
31
|
+
}
|
|
32
|
+
exports.httpDelete = httpDelete;
|
|
33
|
+
function httpPost(route) {
|
|
34
|
+
return defineEndpointMetadata('post', route);
|
|
35
|
+
}
|
|
36
|
+
exports.httpPost = httpPost;
|
|
37
|
+
/**
|
|
38
|
+
* This is exposed internally for testing.
|
|
39
|
+
* There shouldn't be a need to export this publicly
|
|
40
|
+
*/
|
|
41
|
+
exports.middlewareMetadataKey = Symbol('@middlewareMetadataKey');
|
|
42
|
+
function useMiddleware(...args) {
|
|
43
|
+
return (target) => {
|
|
44
|
+
let params = Reflect.getMetadata(exports.middlewareMetadataKey, target) || [];
|
|
45
|
+
params = params.concat(args);
|
|
46
|
+
Reflect.defineMetadata(exports.middlewareMetadataKey, params, target);
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
exports.useMiddleware = useMiddleware;
|
|
50
|
+
function getMiddleware(target) {
|
|
51
|
+
return Reflect.getMetadata(exports.middlewareMetadataKey, target);
|
|
52
|
+
}
|
|
53
|
+
exports.getMiddleware = getMiddleware;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { IServiceContainer, IServiceModule, Newable } from '@aesop-fables/containr';
|
|
2
|
+
import { APIGatewayProxyEventV2, Handler } from 'aws-lambda';
|
|
3
|
+
import { IHttpEndpoint } from './IHttpEndpoint';
|
|
4
|
+
export declare type NonNoisyEvent = Omit<APIGatewayProxyEventV2, 'requestContext'>;
|
|
5
|
+
export interface BootstrappedHttpLambdaContext {
|
|
6
|
+
createHttpLambda<Input, Output>(newable: Newable<IHttpEndpoint<Input, Output>>): Handler<APIGatewayProxyEventV2, Output>;
|
|
7
|
+
}
|
|
8
|
+
export interface IHttpLambdaFactory {
|
|
9
|
+
createHandler<Input, Output>(newable: Newable<IHttpEndpoint<Input, Output>>): Handler<APIGatewayProxyEventV2, Output>;
|
|
10
|
+
}
|
|
11
|
+
export declare class HttpLambdaFactory implements IHttpLambdaFactory {
|
|
12
|
+
private readonly container;
|
|
13
|
+
constructor(container: IServiceContainer);
|
|
14
|
+
createHandler<Input, Output>(newable: Newable<IHttpEndpoint<Input, Output>>): Handler<APIGatewayProxyEventV2, Output>;
|
|
15
|
+
}
|
|
16
|
+
export declare const useTrigintaHttp: IServiceModule;
|
|
17
|
+
export declare class HttpLambda {
|
|
18
|
+
static initialize(modules?: IServiceModule[]): BootstrappedHttpLambdaContext;
|
|
19
|
+
static getContainer(): IServiceContainer;
|
|
20
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.HttpLambda = exports.useTrigintaHttp = exports.HttpLambdaFactory = void 0;
|
|
16
|
+
/* eslint-disable @typescript-eslint/ban-types */
|
|
17
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
18
|
+
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
19
|
+
const containr_1 = require("@aesop-fables/containr");
|
|
20
|
+
const core_1 = __importDefault(require("@middy/core"));
|
|
21
|
+
const Decorators_1 = require("./Decorators");
|
|
22
|
+
const HttpLambdaServices_1 = require("./HttpLambdaServices");
|
|
23
|
+
class HttpLambdaFactory {
|
|
24
|
+
constructor(container) {
|
|
25
|
+
this.container = container;
|
|
26
|
+
}
|
|
27
|
+
createHandler(newable) {
|
|
28
|
+
const handler = (event) => __awaiter(this, void 0, void 0, function* () {
|
|
29
|
+
const childContainer = this.container.createChildContainer('httpLambda');
|
|
30
|
+
try {
|
|
31
|
+
const endpoint = this.container.resolve(newable);
|
|
32
|
+
const { body: request } = event;
|
|
33
|
+
const response = (yield endpoint.handle(request, event));
|
|
34
|
+
return response;
|
|
35
|
+
}
|
|
36
|
+
finally {
|
|
37
|
+
if (childContainer) {
|
|
38
|
+
try {
|
|
39
|
+
childContainer.dispose();
|
|
40
|
+
}
|
|
41
|
+
catch (_a) {
|
|
42
|
+
// no-op
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
const middlewareMetadata = (0, Decorators_1.getMiddleware)(newable);
|
|
48
|
+
if (middlewareMetadata) {
|
|
49
|
+
let midHandler = (0, core_1.default)(handler);
|
|
50
|
+
middlewareMetadata.forEach((midFunc) => {
|
|
51
|
+
midHandler = midHandler.use(midFunc());
|
|
52
|
+
});
|
|
53
|
+
return midHandler;
|
|
54
|
+
}
|
|
55
|
+
return handler;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
exports.HttpLambdaFactory = HttpLambdaFactory;
|
|
59
|
+
exports.useTrigintaHttp = (0, containr_1.createServiceModule)('triginta/http', (services) => {
|
|
60
|
+
services.register(HttpLambdaServices_1.HttpLambdaServices.HttpLambdaFactory, (container) => new HttpLambdaFactory(container));
|
|
61
|
+
});
|
|
62
|
+
let _currentContainer;
|
|
63
|
+
class HttpLambda {
|
|
64
|
+
static initialize(modules = []) {
|
|
65
|
+
const container = (0, containr_1.createContainer)([exports.useTrigintaHttp, ...modules]);
|
|
66
|
+
_currentContainer = container;
|
|
67
|
+
return {
|
|
68
|
+
createHttpLambda(newable) {
|
|
69
|
+
const factory = container.get(HttpLambdaServices_1.HttpLambdaServices.HttpLambdaFactory);
|
|
70
|
+
return factory.createHandler(newable);
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
static getContainer() {
|
|
75
|
+
return _currentContainer;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
exports.HttpLambda = HttpLambda;
|
package/lib/IHandler.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import middy from '@middy/core';
|
|
2
|
+
export declare const errorWrapper: () => {
|
|
3
|
+
onError: (handler: middy.HandlerLambda, next: middy.NextFunction) => void;
|
|
4
|
+
};
|
|
5
|
+
export declare const convertNullTo200: () => {
|
|
6
|
+
after: (handler: middy.HandlerLambda, next: middy.NextFunction) => void;
|
|
7
|
+
};
|
|
8
|
+
export declare const xssFilter: () => {
|
|
9
|
+
before: (handler: middy.HandlerLambda) => Promise<void>;
|
|
10
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.xssFilter = exports.convertNullTo200 = exports.errorWrapper = void 0;
|
|
16
|
+
const xss_1 = __importDefault(require("xss"));
|
|
17
|
+
const errorWrapper = () => ({
|
|
18
|
+
onError: (handler, next) => {
|
|
19
|
+
if (handler.error) {
|
|
20
|
+
handler.response = {
|
|
21
|
+
statusCode: 400,
|
|
22
|
+
headers: {
|
|
23
|
+
'Content-Type': 'application/json',
|
|
24
|
+
'Access-Control-Allow-Origin': '*',
|
|
25
|
+
'Access-Control-Allow-Headers': '*',
|
|
26
|
+
'Access-Control-Allow-Methods': '*',
|
|
27
|
+
},
|
|
28
|
+
body: JSON.stringify({
|
|
29
|
+
error: handler.error,
|
|
30
|
+
}),
|
|
31
|
+
};
|
|
32
|
+
return next();
|
|
33
|
+
}
|
|
34
|
+
return next(handler.error);
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
exports.errorWrapper = errorWrapper;
|
|
38
|
+
const convertNullTo200 = () => ({
|
|
39
|
+
after: (handler, next) => {
|
|
40
|
+
if (handler.response === null) {
|
|
41
|
+
handler.response = {
|
|
42
|
+
statusCode: 200,
|
|
43
|
+
headers: {
|
|
44
|
+
'Content-Type': 'application/json',
|
|
45
|
+
'Access-Control-Allow-Origin': '*',
|
|
46
|
+
'Access-Control-Allow-Headers': '*',
|
|
47
|
+
'Access-Control-Allow-Methods': '*',
|
|
48
|
+
},
|
|
49
|
+
body: '',
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return next();
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
exports.convertNullTo200 = convertNullTo200;
|
|
56
|
+
const xssFilter = () => ({
|
|
57
|
+
before: (handler) => __awaiter(void 0, void 0, void 0, function* () {
|
|
58
|
+
const { event } = handler;
|
|
59
|
+
const { body } = event;
|
|
60
|
+
if (body) {
|
|
61
|
+
const keys = Object.keys(body);
|
|
62
|
+
for (let i = 0; i < keys.length; i++) {
|
|
63
|
+
const key = keys[i];
|
|
64
|
+
const value = body[key];
|
|
65
|
+
if (value && typeof value === 'string') {
|
|
66
|
+
body[key] = (0, xss_1.default)(value);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}),
|
|
71
|
+
});
|
|
72
|
+
exports.xssFilter = xssFilter;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
class RouteRegistry {
|
|
4
|
+
constructor() {
|
|
5
|
+
this.routes = [];
|
|
6
|
+
}
|
|
7
|
+
allRoutes() {
|
|
8
|
+
return this.routes;
|
|
9
|
+
}
|
|
10
|
+
register(route) {
|
|
11
|
+
this.routes.push(route);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
exports.default = new RouteRegistry();
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { IHandler } from './IHandler';
|
|
2
|
+
import { IHttpEndpoint } from './IHttpEndpoint';
|
|
3
|
+
export * from './Decorators';
|
|
4
|
+
export * from './Middleware';
|
|
5
|
+
export * from './HttpLambda';
|
|
6
|
+
export * from './HttpLambdaServices';
|
|
7
|
+
export * from './RouteRegistry';
|
|
8
|
+
export * from './invokeHttpHandler';
|
|
9
|
+
export { IHandler, IHttpEndpoint };
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./Decorators"), exports);
|
|
18
|
+
__exportStar(require("./Middleware"), exports);
|
|
19
|
+
__exportStar(require("./HttpLambda"), exports);
|
|
20
|
+
__exportStar(require("./HttpLambdaServices"), exports);
|
|
21
|
+
__exportStar(require("./RouteRegistry"), exports);
|
|
22
|
+
__exportStar(require("./invokeHttpHandler"), exports);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { APIGatewayProxyEventPathParameters, APIGatewayProxyEventV2 } from 'aws-lambda';
|
|
2
|
+
import { IConfiguredRoute } from './IConfiguredRoute';
|
|
3
|
+
export interface InvocationContext {
|
|
4
|
+
configuredRoute: IConfiguredRoute;
|
|
5
|
+
body?: any;
|
|
6
|
+
path: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function parseRouteParams(route: string, path: string): APIGatewayProxyEventPathParameters;
|
|
9
|
+
export declare function parsePathParameters(context: InvocationContext): Partial<APIGatewayProxyEventV2>;
|
|
10
|
+
export declare function createApiGatewayEvent(context: InvocationContext): Partial<APIGatewayProxyEventV2>;
|
|
11
|
+
export declare function invokeHttpHandler<Output>(context: InvocationContext): Promise<Output>;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.invokeHttpHandler = exports.createApiGatewayEvent = exports.parsePathParameters = exports.parseRouteParams = void 0;
|
|
16
|
+
const HttpLambda_1 = require("./HttpLambda");
|
|
17
|
+
const node_querystring_1 = __importDefault(require("node:querystring"));
|
|
18
|
+
const HttpLambdaServices_1 = require("./HttpLambdaServices");
|
|
19
|
+
function normalizeAndParse(input) {
|
|
20
|
+
let valueToParse = input;
|
|
21
|
+
if (valueToParse.endsWith('/')) {
|
|
22
|
+
valueToParse = valueToParse.substring(0, valueToParse.length - 1);
|
|
23
|
+
}
|
|
24
|
+
if (!valueToParse.startsWith('/')) {
|
|
25
|
+
valueToParse = `/${valueToParse}`;
|
|
26
|
+
}
|
|
27
|
+
return valueToParse.split('/').filter((x) => x.trim() !== '');
|
|
28
|
+
}
|
|
29
|
+
function parseRouteParams(route, path) {
|
|
30
|
+
let routeToParse = route;
|
|
31
|
+
if (routeToParse.endsWith('/')) {
|
|
32
|
+
routeToParse = routeToParse.substring(0, routeToParse.length - 1);
|
|
33
|
+
}
|
|
34
|
+
if (!routeToParse.startsWith('/')) {
|
|
35
|
+
routeToParse = `/${routeToParse}`;
|
|
36
|
+
}
|
|
37
|
+
const routeParts = normalizeAndParse(route);
|
|
38
|
+
const pathParts = normalizeAndParse(path);
|
|
39
|
+
if (routeParts.length !== pathParts.length) {
|
|
40
|
+
throw new Error(`Missing route parameters`);
|
|
41
|
+
}
|
|
42
|
+
const params = {};
|
|
43
|
+
for (let i = 0; i < routeParts.length; i++) {
|
|
44
|
+
const part = routeParts[i];
|
|
45
|
+
const result = /\{([\w]*)\}/g.exec(part);
|
|
46
|
+
if (result) {
|
|
47
|
+
const paramKey = result[1];
|
|
48
|
+
const value = pathParts[i];
|
|
49
|
+
params[paramKey] = value;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return params;
|
|
53
|
+
}
|
|
54
|
+
exports.parseRouteParams = parseRouteParams;
|
|
55
|
+
function parsePathParameters(context) {
|
|
56
|
+
const { path } = context;
|
|
57
|
+
let queryStringParameters = {};
|
|
58
|
+
if (path.indexOf('?') !== -1) {
|
|
59
|
+
const values = path.split('?');
|
|
60
|
+
if (values.length > 1) {
|
|
61
|
+
queryStringParameters = node_querystring_1.default.parse(values[1]);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
rawPath: path,
|
|
66
|
+
pathParameters: parseRouteParams(context.configuredRoute.route, context.path),
|
|
67
|
+
queryStringParameters,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
exports.parsePathParameters = parsePathParameters;
|
|
71
|
+
function createApiGatewayEvent(context) {
|
|
72
|
+
const { configuredRoute, path, body } = context;
|
|
73
|
+
const routeKey = `${configuredRoute.method} ${configuredRoute.route}`;
|
|
74
|
+
const event = {
|
|
75
|
+
version: '2.0',
|
|
76
|
+
routeKey,
|
|
77
|
+
headers: {
|
|
78
|
+
accept: '*/*',
|
|
79
|
+
'accept-encoding': 'gzip, deflate, br',
|
|
80
|
+
// 'content-length': '0',
|
|
81
|
+
// 'content-type': 'application/json',
|
|
82
|
+
host: '',
|
|
83
|
+
'user-agent': 'triginta/1.0',
|
|
84
|
+
'x-amzn-trace-id': 'Root=1-63e26f79-577a8db87d3b31fa4da65566',
|
|
85
|
+
'x-forwarded-for': '127.0.0.1',
|
|
86
|
+
'x-forwarded-port': '80',
|
|
87
|
+
'x-forwarded-proto': 'http',
|
|
88
|
+
},
|
|
89
|
+
requestContext: {
|
|
90
|
+
accountId: '888888888888',
|
|
91
|
+
apiId: 'trigintaLocal',
|
|
92
|
+
domainName: '',
|
|
93
|
+
domainPrefix: '',
|
|
94
|
+
http: {
|
|
95
|
+
method: configuredRoute.method,
|
|
96
|
+
path,
|
|
97
|
+
protocol: 'HTTP/1.1',
|
|
98
|
+
sourceIp: '127.0.0.1',
|
|
99
|
+
userAgent: 'triginta/1.0',
|
|
100
|
+
},
|
|
101
|
+
requestId: '1234',
|
|
102
|
+
routeKey,
|
|
103
|
+
stage: '$default',
|
|
104
|
+
time: '',
|
|
105
|
+
timeEpoch: Date.now(),
|
|
106
|
+
},
|
|
107
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
108
|
+
};
|
|
109
|
+
return event;
|
|
110
|
+
}
|
|
111
|
+
exports.createApiGatewayEvent = createApiGatewayEvent;
|
|
112
|
+
// TODO -- We need to rename this to make it clear that it's for testing ONLY
|
|
113
|
+
function invokeHttpHandler(context) {
|
|
114
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
115
|
+
const container = HttpLambda_1.HttpLambda.getContainer();
|
|
116
|
+
const factory = container.get(HttpLambdaServices_1.HttpLambdaServices.HttpLambdaFactory);
|
|
117
|
+
const configuredHandler = factory.createHandler(context.configuredRoute.constructor);
|
|
118
|
+
return configuredHandler(createApiGatewayEvent(context), {
|
|
119
|
+
callbackWaitsForEmptyEventLoop: false,
|
|
120
|
+
functionName: 'httpLambda',
|
|
121
|
+
functionVersion: '0.1',
|
|
122
|
+
invokedFunctionArn: 'arn::test',
|
|
123
|
+
memoryLimitInMB: '128',
|
|
124
|
+
awsRequestId: '1234',
|
|
125
|
+
logGroupName: 'test-group',
|
|
126
|
+
logStreamName: 'test-stream',
|
|
127
|
+
getRemainingTimeInMillis: function () {
|
|
128
|
+
throw new Error('Function not implemented.');
|
|
129
|
+
},
|
|
130
|
+
done: function (error, result) {
|
|
131
|
+
throw new Error('Function not implemented.');
|
|
132
|
+
},
|
|
133
|
+
fail: function (error) {
|
|
134
|
+
throw new Error('Function not implemented.');
|
|
135
|
+
},
|
|
136
|
+
succeed: function (messageOrObject) {
|
|
137
|
+
throw new Error('Function not implemented.');
|
|
138
|
+
},
|
|
139
|
+
}, () => {
|
|
140
|
+
// no-op
|
|
141
|
+
throw new Error('Not supported');
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
exports.invokeHttpHandler = invokeHttpHandler;
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aesop-fables/triginta",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "A lightweight framework that wraps the basic infrastructure usages of AWS Lambda (SQS, Kinesis, etc.).",
|
|
5
|
+
"type": "commonjs",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./lib/index.d.ts",
|
|
9
|
+
"default": "./lib/index.js"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"main": "./lib/index.js",
|
|
13
|
+
"types": "./lib/index.d.ts",
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "rimraf ./lib && tsc --p ./tsconfig.build.json",
|
|
16
|
+
"format": "prettier --write \"src/**/*.(js|ts)\"",
|
|
17
|
+
"lint": "eslint src --ext .js,.ts",
|
|
18
|
+
"lint:fix": "eslint src --fix --ext .js,.ts",
|
|
19
|
+
"test": "jest --config jest.config.js",
|
|
20
|
+
"test:watch": "jest --config jest.config.js --watch",
|
|
21
|
+
"prepublishOnly": "npm test && npm run lint",
|
|
22
|
+
"preversion": "npm run lint",
|
|
23
|
+
"version": "npm run format && git add -A src",
|
|
24
|
+
"postversion": "git push && git push --tags"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@aesop-fables/containr": "^0.1.5",
|
|
28
|
+
"@types/aws-lambda": "^8.10.109",
|
|
29
|
+
"@types/jest": "^29.2.4",
|
|
30
|
+
"@types/node": "^18.11.11",
|
|
31
|
+
"@typescript-eslint/eslint-plugin": "^5.45.1",
|
|
32
|
+
"@typescript-eslint/parser": "5.45.0",
|
|
33
|
+
"esbuild": "^0.16.2",
|
|
34
|
+
"eslint": "8.29.0",
|
|
35
|
+
"eslint-config-prettier": "^8.5.0",
|
|
36
|
+
"eslint-plugin-jest": "27.1.6",
|
|
37
|
+
"eslint-plugin-prettier": "^4.2.1",
|
|
38
|
+
"jest": "29.3.1",
|
|
39
|
+
"jest-mock-extended": "^3.0.1",
|
|
40
|
+
"prettier": "^2.8.1",
|
|
41
|
+
"ts-jest": "29.0.1",
|
|
42
|
+
"typescript": "4.9.3"
|
|
43
|
+
},
|
|
44
|
+
"files": [
|
|
45
|
+
"lib/**/*"
|
|
46
|
+
],
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"@middy/core": "^1.5.2",
|
|
49
|
+
"@middy/http-error-handler": "^1.5.2",
|
|
50
|
+
"@middy/http-json-body-parser": "^1.5.2",
|
|
51
|
+
"reflect-metadata": "^0.1.13",
|
|
52
|
+
"xss": "^1.0.9"
|
|
53
|
+
},
|
|
54
|
+
"repository": {
|
|
55
|
+
"type": "git",
|
|
56
|
+
"url": "git+ssh://git@github.com/aesop-fables/typescript-libraries.git"
|
|
57
|
+
},
|
|
58
|
+
"publishConfig": {
|
|
59
|
+
"access": "public"
|
|
60
|
+
}
|
|
61
|
+
}
|