@cumulusds/aws-apig-bypass 0.0.1
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/.babelrc.js +15 -0
- package/.eslintrc.js +15 -0
- package/.flowconfig +14 -0
- package/.github/dependabot.yml +15 -0
- package/.github/pull_request_template.md +18 -0
- package/.github/workflows/ci-cd.yml +86 -0
- package/LICENSE +9 -0
- package/README.md +68 -0
- package/doc/CreateAccessKey.png +0 -0
- package/doc/development.md +244 -0
- package/doc/prettier-watchers-pc.xml +222 -0
- package/doc/prettier-watchers-unix.xml +222 -0
- package/doc/webstorm-file-watcher-prettier.png +0 -0
- package/doc/webstorm-flow.png +0 -0
- package/flow-typed/npm/jest_v26.x.x.js +1218 -0
- package/lib/create-api-gateway-event.js +50 -0
- package/lib/create-api-gateway-event.js.flow +57 -0
- package/lib/create-client.js +25 -0
- package/lib/create-client.js.flow +30 -0
- package/lib/create-lambda-client.js +33 -0
- package/lib/create-lambda-client.js.flow +35 -0
- package/lib/index.js +23 -0
- package/lib/index.js.flow +4 -0
- package/lib/json-stringify.js +23 -0
- package/lib/json-stringify.js.flow +16 -0
- package/lib/lambda-invoke.js +1 -0
- package/lib/lambda-invoke.js.flow +10 -0
- package/lib/parse-payload.js +31 -0
- package/lib/parse-payload.js.flow +25 -0
- package/lib/response.js +1 -0
- package/lib/response.js.flow +15 -0
- package/package.json +86 -0
- package/prettier.config.js +3 -0
- package/src/create-api-gateway-event.js +57 -0
- package/src/create-client.js +30 -0
- package/src/create-lambda-client.js +35 -0
- package/src/index.js +4 -0
- package/src/json-stringify.js +16 -0
- package/src/lambda-invoke.js +10 -0
- package/src/parse-payload.js +25 -0
- package/src/response.js +15 -0
- package/test/unit/index.test.js +142 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = createAPIGatewayEvent;
|
|
7
|
+
|
|
8
|
+
function createAPIGatewayEvent({
|
|
9
|
+
pathParameters = null,
|
|
10
|
+
path = "",
|
|
11
|
+
body = null,
|
|
12
|
+
queryStringParameters = null
|
|
13
|
+
} = {}) {
|
|
14
|
+
return {
|
|
15
|
+
body,
|
|
16
|
+
headers: {},
|
|
17
|
+
multiValueHeaders: {},
|
|
18
|
+
httpMethod: "",
|
|
19
|
+
isBase64Encoded: false,
|
|
20
|
+
path,
|
|
21
|
+
pathParameters,
|
|
22
|
+
queryStringParameters,
|
|
23
|
+
multiValueQueryStringParameters: null,
|
|
24
|
+
stageVariables: null,
|
|
25
|
+
requestContext: {
|
|
26
|
+
accountId: "",
|
|
27
|
+
apiId: "",
|
|
28
|
+
httpMethod: "",
|
|
29
|
+
identity: {
|
|
30
|
+
accessKey: null,
|
|
31
|
+
accountId: null,
|
|
32
|
+
apiKey: null,
|
|
33
|
+
caller: null,
|
|
34
|
+
cognitoAuthenticationProvider: null,
|
|
35
|
+
cognitoAuthenticationType: null,
|
|
36
|
+
cognitoIdentityId: null,
|
|
37
|
+
cognitoIdentityPoolId: null,
|
|
38
|
+
sourceIp: "",
|
|
39
|
+
user: null,
|
|
40
|
+
userAgent: null,
|
|
41
|
+
userArn: null
|
|
42
|
+
},
|
|
43
|
+
stage: "",
|
|
44
|
+
requestId: "",
|
|
45
|
+
resourceId: "",
|
|
46
|
+
resourcePath: ""
|
|
47
|
+
},
|
|
48
|
+
resource: ""
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
import type { APIGatewayEvent } from "flow-aws-lambda";
|
|
4
|
+
|
|
5
|
+
export type PathParameters = { [name: string]: string } | null;
|
|
6
|
+
export type QueryStringParameters = { [name: string]: string } | null;
|
|
7
|
+
|
|
8
|
+
export type CreateAPIGatewayEventOptions = {
|
|
9
|
+
body?: string,
|
|
10
|
+
path?: string,
|
|
11
|
+
pathParameters?: PathParameters,
|
|
12
|
+
queryStringParameters?: QueryStringParameters
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export default function createAPIGatewayEvent({
|
|
16
|
+
pathParameters = null,
|
|
17
|
+
path = "",
|
|
18
|
+
body = null,
|
|
19
|
+
queryStringParameters = null
|
|
20
|
+
}: CreateAPIGatewayEventOptions = {}): APIGatewayEvent<string> {
|
|
21
|
+
return {
|
|
22
|
+
body,
|
|
23
|
+
headers: {},
|
|
24
|
+
multiValueHeaders: {},
|
|
25
|
+
httpMethod: "",
|
|
26
|
+
isBase64Encoded: false,
|
|
27
|
+
path,
|
|
28
|
+
pathParameters,
|
|
29
|
+
queryStringParameters,
|
|
30
|
+
multiValueQueryStringParameters: null,
|
|
31
|
+
stageVariables: null,
|
|
32
|
+
requestContext: {
|
|
33
|
+
accountId: "",
|
|
34
|
+
apiId: "",
|
|
35
|
+
httpMethod: "",
|
|
36
|
+
identity: {
|
|
37
|
+
accessKey: null,
|
|
38
|
+
accountId: null,
|
|
39
|
+
apiKey: null,
|
|
40
|
+
caller: null,
|
|
41
|
+
cognitoAuthenticationProvider: null,
|
|
42
|
+
cognitoAuthenticationType: null,
|
|
43
|
+
cognitoIdentityId: null,
|
|
44
|
+
cognitoIdentityPoolId: null,
|
|
45
|
+
sourceIp: "",
|
|
46
|
+
user: null,
|
|
47
|
+
userAgent: null,
|
|
48
|
+
userArn: null
|
|
49
|
+
},
|
|
50
|
+
stage: "",
|
|
51
|
+
requestId: "",
|
|
52
|
+
resourceId: "",
|
|
53
|
+
resourcePath: ""
|
|
54
|
+
},
|
|
55
|
+
resource: ""
|
|
56
|
+
};
|
|
57
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = createClient;
|
|
7
|
+
|
|
8
|
+
var _createLambdaClient = _interopRequireDefault(require("./create-lambda-client"));
|
|
9
|
+
|
|
10
|
+
var _jsonStringify = _interopRequireDefault(require("./json-stringify"));
|
|
11
|
+
|
|
12
|
+
var _parsePayload = _interopRequireDefault(require("./parse-payload"));
|
|
13
|
+
|
|
14
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Create an APIGatewayHandlerClient. The client invokes an API Gateway handler, bypassing the gateway. It adapts the AWS SDK by packing and unpacking messages for invoking the handler.
|
|
18
|
+
*
|
|
19
|
+
* @param options
|
|
20
|
+
* @returns {APIGatewayHandlerClient<T>}
|
|
21
|
+
*/
|
|
22
|
+
function createClient(options) {
|
|
23
|
+
const client = (0, _createLambdaClient.default)(options);
|
|
24
|
+
return async event => (0, _parsePayload.default)((await client((0, _jsonStringify.default)(event))));
|
|
25
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
import type { APIGatewayEvent } from "flow-aws-lambda";
|
|
4
|
+
import createLambdaClient from "./create-lambda-client";
|
|
5
|
+
import type { Response } from "./response";
|
|
6
|
+
import jsonStringify from "./json-stringify";
|
|
7
|
+
import parsePayload from "./parse-payload";
|
|
8
|
+
import type { LambdaInvoke } from "./lambda-invoke";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Client that directly invokes an API Gateway handler, bypassing the gateway. The client handles packing and unpacking messages for invoking the handler.
|
|
12
|
+
*/
|
|
13
|
+
export type APIGatewayHandlerClient<T> = (event: APIGatewayEvent<string>) => Promise<Response<T>>;
|
|
14
|
+
|
|
15
|
+
export type CreateClientOptions<T> = {
|
|
16
|
+
Lambda: LambdaInvoke,
|
|
17
|
+
FunctionName: string,
|
|
18
|
+
Validate?: T => void
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Create an APIGatewayHandlerClient. The client invokes an API Gateway handler, bypassing the gateway. It adapts the AWS SDK by packing and unpacking messages for invoking the handler.
|
|
23
|
+
*
|
|
24
|
+
* @param options
|
|
25
|
+
* @returns {APIGatewayHandlerClient<T>}
|
|
26
|
+
*/
|
|
27
|
+
export default function createClient<T>(options: CreateClientOptions<T>): APIGatewayHandlerClient<T> {
|
|
28
|
+
const client = createLambdaClient(options);
|
|
29
|
+
return async (event: APIGatewayEvent<string>) => parsePayload<T>(await client(jsonStringify(event)));
|
|
30
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = createLambdaClient;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* AWS Lambda SDK adapter for createClient.
|
|
10
|
+
*/
|
|
11
|
+
function createLambdaClient({
|
|
12
|
+
Lambda,
|
|
13
|
+
FunctionName
|
|
14
|
+
}) {
|
|
15
|
+
return async Payload => {
|
|
16
|
+
const invocationResponse = await Lambda.invoke({
|
|
17
|
+
FunctionName,
|
|
18
|
+
Payload
|
|
19
|
+
}).promise();
|
|
20
|
+
|
|
21
|
+
if (invocationResponse.StatusCode !== 200) {
|
|
22
|
+
var _invocationResponse$S;
|
|
23
|
+
|
|
24
|
+
throw new Error(`AWS Lambda invocation API failed with status ${(_invocationResponse$S = invocationResponse.StatusCode) !== null && _invocationResponse$S !== void 0 ? _invocationResponse$S : "(no status code)"}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (typeof invocationResponse.Payload !== "string") {
|
|
28
|
+
throw new Error(`AWS Lambda invocation API returned the wrong payload type '${typeof invocationResponse.Payload}'; expected 'string'`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return invocationResponse.Payload;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
import type { LambdaInvoke } from "./lambda-invoke";
|
|
4
|
+
|
|
5
|
+
interface Blob {}
|
|
6
|
+
type _Blob = Buffer | Uint8Array | Blob | string;
|
|
7
|
+
type LambdaClientType = (event: _Blob) => Promise<string>;
|
|
8
|
+
|
|
9
|
+
export type CreateLambdaClientOptions = {
|
|
10
|
+
Lambda: LambdaInvoke,
|
|
11
|
+
FunctionName: string
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* AWS Lambda SDK adapter for createClient.
|
|
16
|
+
*/
|
|
17
|
+
export default function createLambdaClient({ Lambda, FunctionName }: CreateLambdaClientOptions): LambdaClientType {
|
|
18
|
+
return async (Payload: _Blob) => {
|
|
19
|
+
const invocationResponse = await Lambda.invoke({
|
|
20
|
+
FunctionName,
|
|
21
|
+
Payload
|
|
22
|
+
}).promise();
|
|
23
|
+
if (invocationResponse.StatusCode !== 200) {
|
|
24
|
+
throw new Error(
|
|
25
|
+
`AWS Lambda invocation API failed with status ${invocationResponse.StatusCode ?? "(no status code)"}`
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
if (typeof invocationResponse.Payload !== "string") {
|
|
29
|
+
throw new Error(
|
|
30
|
+
`AWS Lambda invocation API returned the wrong payload type '${typeof invocationResponse.Payload}'; expected 'string'`
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
return invocationResponse.Payload;
|
|
34
|
+
};
|
|
35
|
+
}
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
Object.defineProperty(exports, "createClient", {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
get: function () {
|
|
9
|
+
return _createClient.default;
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
Object.defineProperty(exports, "createAPIGatewayEvent", {
|
|
13
|
+
enumerable: true,
|
|
14
|
+
get: function () {
|
|
15
|
+
return _createApiGatewayEvent.default;
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
var _createClient = _interopRequireDefault(require("./create-client"));
|
|
20
|
+
|
|
21
|
+
var _createApiGatewayEvent = _interopRequireDefault(require("./create-api-gateway-event"));
|
|
22
|
+
|
|
23
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = jsonStringify;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Stringify to JSON, returning a string or else throwing an exception because of unserializable input.
|
|
10
|
+
*
|
|
11
|
+
* @param value
|
|
12
|
+
* @returns {string}
|
|
13
|
+
*/
|
|
14
|
+
// flowlint-next-line unclear-type:off
|
|
15
|
+
function jsonStringify(value) {
|
|
16
|
+
const result = JSON.stringify(value);
|
|
17
|
+
|
|
18
|
+
if (result == null) {
|
|
19
|
+
throw new Error("Cannot stringify value");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return result;
|
|
23
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Stringify to JSON, returning a string or else throwing an exception because of unserializable input.
|
|
5
|
+
*
|
|
6
|
+
* @param value
|
|
7
|
+
* @returns {string}
|
|
8
|
+
*/
|
|
9
|
+
// flowlint-next-line unclear-type:off
|
|
10
|
+
export default function jsonStringify(value: any): string {
|
|
11
|
+
const result = JSON.stringify(value);
|
|
12
|
+
if (result == null) {
|
|
13
|
+
throw new Error("Cannot stringify value");
|
|
14
|
+
}
|
|
15
|
+
return result;
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = parsePayload;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Decode the handler response
|
|
10
|
+
*
|
|
11
|
+
* @param payload
|
|
12
|
+
* @param validate if given, the validate function is called on the parsed response body. The function should throw an exception if the response is invalid.
|
|
13
|
+
* @returns {{headers: {[p: string]: boolean|number|string}, data: *, status: number}}
|
|
14
|
+
*/
|
|
15
|
+
function parsePayload(payload, validate) {
|
|
16
|
+
var _proxyResult$headers;
|
|
17
|
+
|
|
18
|
+
const proxyResult = JSON.parse(payload);
|
|
19
|
+
|
|
20
|
+
if (proxyResult.isBase64Encoded === true) {
|
|
21
|
+
throw new Error("A base64 encoded response from API Gateway handler was received, but is not supported.");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const data = JSON.parse(proxyResult.body);
|
|
25
|
+
validate === null || validate === void 0 ? void 0 : validate(data);
|
|
26
|
+
return {
|
|
27
|
+
data,
|
|
28
|
+
status: proxyResult.statusCode,
|
|
29
|
+
headers: (_proxyResult$headers = proxyResult.headers) !== null && _proxyResult$headers !== void 0 ? _proxyResult$headers : {}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
import type { ProxyResult } from "flow-aws-lambda";
|
|
4
|
+
import type { Response } from "./response";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Decode the handler response
|
|
8
|
+
*
|
|
9
|
+
* @param payload
|
|
10
|
+
* @param validate if given, the validate function is called on the parsed response body. The function should throw an exception if the response is invalid.
|
|
11
|
+
* @returns {{headers: {[p: string]: boolean|number|string}, data: *, status: number}}
|
|
12
|
+
*/
|
|
13
|
+
export default function parsePayload<T>(payload: string, validate?: T => void): Response<T> {
|
|
14
|
+
const proxyResult: ProxyResult = JSON.parse(payload);
|
|
15
|
+
if (proxyResult.isBase64Encoded === true) {
|
|
16
|
+
throw new Error("A base64 encoded response from API Gateway handler was received, but is not supported.");
|
|
17
|
+
}
|
|
18
|
+
const data: T = JSON.parse(proxyResult.body);
|
|
19
|
+
validate?.(data);
|
|
20
|
+
return {
|
|
21
|
+
data,
|
|
22
|
+
status: proxyResult.statusCode,
|
|
23
|
+
headers: proxyResult.headers ?? {}
|
|
24
|
+
};
|
|
25
|
+
}
|
package/lib/response.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Decoded handler response
|
|
5
|
+
*/
|
|
6
|
+
export type Response<T> = {|
|
|
7
|
+
// the response provided by the handler
|
|
8
|
+
data: T,
|
|
9
|
+
|
|
10
|
+
// the HTTP status code
|
|
11
|
+
status: number,
|
|
12
|
+
|
|
13
|
+
// response headers
|
|
14
|
+
headers: { [header: string]: boolean | number | string }
|
|
15
|
+
|};
|
package/package.json
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cumulusds/aws-apig-bypass",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Client for API Gateway handlers, bypassing API Gateway by directly invoking the Lambda",
|
|
5
|
+
"repository": "https://github.com/CumulusDS/aws-apig-bypass",
|
|
6
|
+
"author": "Cumulus Digital Systems",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"private": false,
|
|
9
|
+
"engines": {
|
|
10
|
+
"node": ">=10"
|
|
11
|
+
},
|
|
12
|
+
"main": "lib/index.js",
|
|
13
|
+
"src": [
|
|
14
|
+
"bin, lib",
|
|
15
|
+
"package.json"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "run-p build:babel build:flow-copy-source build:license-checker",
|
|
19
|
+
"build:babel": "babel src --out-dir lib --ignore test.js",
|
|
20
|
+
"build:flow-copy-source": "flow-copy-source --ignore test.js src lib",
|
|
21
|
+
"build:license-checker": "license-checker --csv --out var/licenses.csv",
|
|
22
|
+
"clean": "shx rm -rf lib",
|
|
23
|
+
"git:push": "git push --follow-tags",
|
|
24
|
+
"publish:patch": "run-s version:patch git:push",
|
|
25
|
+
"publish:minor": "run-s version:minor git:push",
|
|
26
|
+
"publish:major": "run-s version:major git:push",
|
|
27
|
+
"sites": "node bin/sites.js",
|
|
28
|
+
"test": "run-p --aggregate-output test:**",
|
|
29
|
+
"test:flow:coverage-report": "flow-coverage-report -i '{src,test}/**/*.js' -t html -t text --threshold 99 --output-dir var/coverage/flow",
|
|
30
|
+
"test:flow:status": "flow status",
|
|
31
|
+
"test:jest": "jest --color",
|
|
32
|
+
"test:lint": "eslint *.js src test",
|
|
33
|
+
"test:prettier": "prettier --list-different \"{src,test}/**/*.{js,jsx,yml}\" \"*.{js,yml}\"",
|
|
34
|
+
"version:patch": "yarn version --new-version patch",
|
|
35
|
+
"version:minor": "yarn version --new-version minor",
|
|
36
|
+
"version:major": "yarn version --new-version major"
|
|
37
|
+
},
|
|
38
|
+
"jest": {
|
|
39
|
+
"verbose": true,
|
|
40
|
+
"collectCoverage": true,
|
|
41
|
+
"collectCoverageFrom": [
|
|
42
|
+
"src/**/*.js"
|
|
43
|
+
],
|
|
44
|
+
"testEnvironment": "node",
|
|
45
|
+
"coverageDirectory": "var/coverage/test",
|
|
46
|
+
"coverageThreshold": {
|
|
47
|
+
"global": {
|
|
48
|
+
"branches": 100,
|
|
49
|
+
"functions": 100,
|
|
50
|
+
"lines": 100,
|
|
51
|
+
"statements": 100
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"dependencies": {
|
|
56
|
+
"@cumulusds/flow-aws-sdk": "^2.818.0",
|
|
57
|
+
"flow-aws-lambda": "CumulusDS/flow-aws-lambda#ef1bd9a0342e55a0e9e1b225bdbc2a3c2a4d5632"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@babel/cli": "^7.0.0",
|
|
61
|
+
"@babel/core": "^7.7.4",
|
|
62
|
+
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.7.4",
|
|
63
|
+
"@babel/plugin-proposal-optional-chaining": "^7.12.7",
|
|
64
|
+
"@babel/preset-env": "^7.0.0",
|
|
65
|
+
"@babel/preset-flow": "^7.0.0",
|
|
66
|
+
"babel-eslint": "10.0.3",
|
|
67
|
+
"babel-jest": "^24.9.0",
|
|
68
|
+
"eslint": "^7.5.0",
|
|
69
|
+
"eslint-config-airbnb-base": "14.0.0",
|
|
70
|
+
"eslint-config-prettier": "^2.9.0",
|
|
71
|
+
"eslint-plugin-flowtype": "^2.49.3",
|
|
72
|
+
"eslint-plugin-import": "2.18.2",
|
|
73
|
+
"eslint-plugin-jest": "^21.15.1",
|
|
74
|
+
"eslint-plugin-prettier": "^2.6.0",
|
|
75
|
+
"flow-bin": "^0.141.0",
|
|
76
|
+
"flow-copy-source": "^2.0.9",
|
|
77
|
+
"flow-coverage-report": "^0.8.0",
|
|
78
|
+
"flow-typed": "^3.2.1",
|
|
79
|
+
"git-describe": "^4.0.4",
|
|
80
|
+
"jest": "^26.0.0",
|
|
81
|
+
"license-checker": "^25.0.1",
|
|
82
|
+
"npm-run-all": "^4.1.3",
|
|
83
|
+
"prettier": "^1.14.0",
|
|
84
|
+
"shx": "^0.3.2"
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
import type { APIGatewayEvent } from "flow-aws-lambda";
|
|
4
|
+
|
|
5
|
+
export type PathParameters = { [name: string]: string } | null;
|
|
6
|
+
export type QueryStringParameters = { [name: string]: string } | null;
|
|
7
|
+
|
|
8
|
+
export type CreateAPIGatewayEventOptions = {
|
|
9
|
+
body?: string,
|
|
10
|
+
path?: string,
|
|
11
|
+
pathParameters?: PathParameters,
|
|
12
|
+
queryStringParameters?: QueryStringParameters
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export default function createAPIGatewayEvent({
|
|
16
|
+
pathParameters = null,
|
|
17
|
+
path = "",
|
|
18
|
+
body = null,
|
|
19
|
+
queryStringParameters = null
|
|
20
|
+
}: CreateAPIGatewayEventOptions = {}): APIGatewayEvent<string> {
|
|
21
|
+
return {
|
|
22
|
+
body,
|
|
23
|
+
headers: {},
|
|
24
|
+
multiValueHeaders: {},
|
|
25
|
+
httpMethod: "",
|
|
26
|
+
isBase64Encoded: false,
|
|
27
|
+
path,
|
|
28
|
+
pathParameters,
|
|
29
|
+
queryStringParameters,
|
|
30
|
+
multiValueQueryStringParameters: null,
|
|
31
|
+
stageVariables: null,
|
|
32
|
+
requestContext: {
|
|
33
|
+
accountId: "",
|
|
34
|
+
apiId: "",
|
|
35
|
+
httpMethod: "",
|
|
36
|
+
identity: {
|
|
37
|
+
accessKey: null,
|
|
38
|
+
accountId: null,
|
|
39
|
+
apiKey: null,
|
|
40
|
+
caller: null,
|
|
41
|
+
cognitoAuthenticationProvider: null,
|
|
42
|
+
cognitoAuthenticationType: null,
|
|
43
|
+
cognitoIdentityId: null,
|
|
44
|
+
cognitoIdentityPoolId: null,
|
|
45
|
+
sourceIp: "",
|
|
46
|
+
user: null,
|
|
47
|
+
userAgent: null,
|
|
48
|
+
userArn: null
|
|
49
|
+
},
|
|
50
|
+
stage: "",
|
|
51
|
+
requestId: "",
|
|
52
|
+
resourceId: "",
|
|
53
|
+
resourcePath: ""
|
|
54
|
+
},
|
|
55
|
+
resource: ""
|
|
56
|
+
};
|
|
57
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
import type { APIGatewayEvent } from "flow-aws-lambda";
|
|
4
|
+
import createLambdaClient from "./create-lambda-client";
|
|
5
|
+
import type { Response } from "./response";
|
|
6
|
+
import jsonStringify from "./json-stringify";
|
|
7
|
+
import parsePayload from "./parse-payload";
|
|
8
|
+
import type { LambdaInvoke } from "./lambda-invoke";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Client that directly invokes an API Gateway handler, bypassing the gateway. The client handles packing and unpacking messages for invoking the handler.
|
|
12
|
+
*/
|
|
13
|
+
export type APIGatewayHandlerClient<T> = (event: APIGatewayEvent<string>) => Promise<Response<T>>;
|
|
14
|
+
|
|
15
|
+
export type CreateClientOptions<T> = {
|
|
16
|
+
Lambda: LambdaInvoke,
|
|
17
|
+
FunctionName: string,
|
|
18
|
+
Validate?: T => void
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Create an APIGatewayHandlerClient. The client invokes an API Gateway handler, bypassing the gateway. It adapts the AWS SDK by packing and unpacking messages for invoking the handler.
|
|
23
|
+
*
|
|
24
|
+
* @param options
|
|
25
|
+
* @returns {APIGatewayHandlerClient<T>}
|
|
26
|
+
*/
|
|
27
|
+
export default function createClient<T>(options: CreateClientOptions<T>): APIGatewayHandlerClient<T> {
|
|
28
|
+
const client = createLambdaClient(options);
|
|
29
|
+
return async (event: APIGatewayEvent<string>) => parsePayload<T>(await client(jsonStringify(event)));
|
|
30
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
import type { LambdaInvoke } from "./lambda-invoke";
|
|
4
|
+
|
|
5
|
+
interface Blob {}
|
|
6
|
+
type _Blob = Buffer | Uint8Array | Blob | string;
|
|
7
|
+
type LambdaClientType = (event: _Blob) => Promise<string>;
|
|
8
|
+
|
|
9
|
+
export type CreateLambdaClientOptions = {
|
|
10
|
+
Lambda: LambdaInvoke,
|
|
11
|
+
FunctionName: string
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* AWS Lambda SDK adapter for createClient.
|
|
16
|
+
*/
|
|
17
|
+
export default function createLambdaClient({ Lambda, FunctionName }: CreateLambdaClientOptions): LambdaClientType {
|
|
18
|
+
return async (Payload: _Blob) => {
|
|
19
|
+
const invocationResponse = await Lambda.invoke({
|
|
20
|
+
FunctionName,
|
|
21
|
+
Payload
|
|
22
|
+
}).promise();
|
|
23
|
+
if (invocationResponse.StatusCode !== 200) {
|
|
24
|
+
throw new Error(
|
|
25
|
+
`AWS Lambda invocation API failed with status ${invocationResponse.StatusCode ?? "(no status code)"}`
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
if (typeof invocationResponse.Payload !== "string") {
|
|
29
|
+
throw new Error(
|
|
30
|
+
`AWS Lambda invocation API returned the wrong payload type '${typeof invocationResponse.Payload}'; expected 'string'`
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
return invocationResponse.Payload;
|
|
34
|
+
};
|
|
35
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Stringify to JSON, returning a string or else throwing an exception because of unserializable input.
|
|
5
|
+
*
|
|
6
|
+
* @param value
|
|
7
|
+
* @returns {string}
|
|
8
|
+
*/
|
|
9
|
+
// flowlint-next-line unclear-type:off
|
|
10
|
+
export default function jsonStringify(value: any): string {
|
|
11
|
+
const result = JSON.stringify(value);
|
|
12
|
+
if (result == null) {
|
|
13
|
+
throw new Error("Cannot stringify value");
|
|
14
|
+
}
|
|
15
|
+
return result;
|
|
16
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
import type { ProxyResult } from "flow-aws-lambda";
|
|
4
|
+
import type { Response } from "./response";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Decode the handler response
|
|
8
|
+
*
|
|
9
|
+
* @param payload
|
|
10
|
+
* @param validate if given, the validate function is called on the parsed response body. The function should throw an exception if the response is invalid.
|
|
11
|
+
* @returns {{headers: {[p: string]: boolean|number|string}, data: *, status: number}}
|
|
12
|
+
*/
|
|
13
|
+
export default function parsePayload<T>(payload: string, validate?: T => void): Response<T> {
|
|
14
|
+
const proxyResult: ProxyResult = JSON.parse(payload);
|
|
15
|
+
if (proxyResult.isBase64Encoded === true) {
|
|
16
|
+
throw new Error("A base64 encoded response from API Gateway handler was received, but is not supported.");
|
|
17
|
+
}
|
|
18
|
+
const data: T = JSON.parse(proxyResult.body);
|
|
19
|
+
validate?.(data);
|
|
20
|
+
return {
|
|
21
|
+
data,
|
|
22
|
+
status: proxyResult.statusCode,
|
|
23
|
+
headers: proxyResult.headers ?? {}
|
|
24
|
+
};
|
|
25
|
+
}
|