serverless-simple-middleware 0.0.73 → 0.0.75
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/.prettierignore +2 -2
- package/README.md +3 -3
- package/dist/middleware/base.d.ts +4 -0
- package/dist/middleware/base.js +6 -0
- package/dist/middleware/build.d.ts +30 -2
- package/dist/middleware/build.js +60 -1
- package/dist/middleware/buildWebSocket.d.ts +5 -0
- package/dist/middleware/buildWebSocket.js +127 -0
- package/dist/middleware/database/connectionProxy.d.ts +2 -0
- package/dist/middleware/database/connectionProxy.js +43 -19
- package/dist/middleware/database/sqlClient.js +34 -9
- package/dist/middleware/index.d.ts +30 -1
- package/dist/middleware/index.js +3 -0
- package/dist/middleware/mysql.d.ts +2 -0
- package/dist/middleware/websocketBase.d.ts +32 -0
- package/dist/middleware/websocketBase.js +65 -0
- package/dist/utils/secretsManager.d.ts +3 -0
- package/dist/utils/secretsManager.js +10 -1
- package/jest.config.js +7 -7
- package/package.json +70 -69
- package/src/aws/config.ts +46 -46
- package/src/aws/define.ts +10 -10
- package/src/aws/index.ts +3 -3
- package/src/aws/simple.ts +705 -705
- package/src/index.ts +3 -3
- package/src/internal/AwsError.ts +13 -13
- package/src/internal/oncePromise.ts +29 -29
- package/src/internal/s3.ts +75 -75
- package/src/middleware/aws.ts +78 -78
- package/src/middleware/base.ts +206 -198
- package/src/middleware/build.ts +273 -173
- package/src/middleware/buildWebSocket.ts +199 -0
- package/src/middleware/database/connectionProxy.ts +294 -247
- package/src/middleware/database/sqlClient.ts +208 -176
- package/src/middleware/index.ts +24 -21
- package/src/middleware/logger.ts +28 -28
- package/src/middleware/mysql.ts +62 -60
- package/src/middleware/trace.ts +265 -265
- package/src/middleware/websocketBase.ts +92 -0
- package/src/utils/index.ts +2 -2
- package/src/utils/logger.ts +94 -94
- package/src/utils/misc.ts +20 -20
- package/src/utils/secretsManager.ts +86 -73
- package/tsconfig.json +16 -15
- package/tslint.json +12 -12
package/.prettierignore
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
tslint.json
|
|
2
|
-
tsconfig.json
|
|
1
|
+
tslint.json
|
|
2
|
+
tsconfig.json
|
|
3
3
|
package.json
|
package/README.md
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
# Serverless simple middleware
|
|
2
|
-
|
|
3
|
-
This is just simple middleware for me to translate the interface of lambda's handler to use `request => response`.
|
|
1
|
+
# Serverless simple middleware
|
|
2
|
+
|
|
3
|
+
This is just simple middleware for me to translate the interface of lambda's handler to use `request => response`.
|
|
@@ -9,12 +9,16 @@ export declare class HandlerRequest {
|
|
|
9
9
|
private lazyBody?;
|
|
10
10
|
constructor(event: any, context: any);
|
|
11
11
|
get body(): any;
|
|
12
|
+
set body(value: any);
|
|
12
13
|
get path(): {
|
|
13
14
|
[key: string]: string | undefined;
|
|
14
15
|
};
|
|
15
16
|
get query(): {
|
|
16
17
|
[key: string]: string | undefined;
|
|
17
18
|
};
|
|
19
|
+
set query(value: {
|
|
20
|
+
[key: string]: any;
|
|
21
|
+
});
|
|
18
22
|
header(key: string): string | undefined;
|
|
19
23
|
records<T, U>(selector?: (each: T) => U): T[] | U[];
|
|
20
24
|
}
|
package/dist/middleware/base.js
CHANGED
|
@@ -29,12 +29,18 @@ class HandlerRequest {
|
|
|
29
29
|
}
|
|
30
30
|
return this.lazyBody || {};
|
|
31
31
|
}
|
|
32
|
+
set body(value) {
|
|
33
|
+
this.lazyBody = value;
|
|
34
|
+
}
|
|
32
35
|
get path() {
|
|
33
36
|
return this.event.pathParameters || {};
|
|
34
37
|
}
|
|
35
38
|
get query() {
|
|
36
39
|
return this.event.queryStringParameters || {};
|
|
37
40
|
}
|
|
41
|
+
set query(value) {
|
|
42
|
+
this.event.queryStringParameters = value;
|
|
43
|
+
}
|
|
38
44
|
header(key) {
|
|
39
45
|
return this.event.headers[key.toLowerCase()];
|
|
40
46
|
}
|
|
@@ -1,3 +1,31 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { type ZodError, type ZodSchema } from 'zod';
|
|
2
|
+
import { Handler, HandlerAuxBase, HandlerPluginBase, HandlerRequest, HandlerResponse } from './base';
|
|
3
|
+
declare const build: <Aux extends HandlerAuxBase>(plugins: Array<HandlerPluginBase<any>>) => ((handler: Handler<Aux>) => (event: any, context: any, callback: any) => void) & {
|
|
4
|
+
withBody: <S>(schema: ZodSchema<S>, handler: (context: {
|
|
5
|
+
request: Omit<HandlerRequest, "body"> & {
|
|
6
|
+
body: S;
|
|
7
|
+
};
|
|
8
|
+
response: HandlerResponse;
|
|
9
|
+
aux: Aux;
|
|
10
|
+
}) => any, onInvalid?: (error: ZodError) => {
|
|
11
|
+
statusCode: number;
|
|
12
|
+
body: any;
|
|
13
|
+
} | Promise<{
|
|
14
|
+
statusCode: number;
|
|
15
|
+
body: any;
|
|
16
|
+
} | void> | void) => (event: any, context: any, callback: any) => void;
|
|
17
|
+
withQuery: <Q>(schema: ZodSchema<Q>, handler: (context: {
|
|
18
|
+
request: Omit<HandlerRequest, "query"> & {
|
|
19
|
+
query: Q;
|
|
20
|
+
};
|
|
21
|
+
response: HandlerResponse;
|
|
22
|
+
aux: Aux;
|
|
23
|
+
}) => any, onInvalid?: (error: ZodError<Q>) => {
|
|
24
|
+
statusCode: number;
|
|
25
|
+
body: any;
|
|
26
|
+
} | Promise<{
|
|
27
|
+
statusCode: number;
|
|
28
|
+
body: any;
|
|
29
|
+
} | void> | void) => (event: any, context: any, callback: any) => void;
|
|
30
|
+
};
|
|
3
31
|
export default build;
|
package/dist/middleware/build.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const logger_1 = require("../utils/logger");
|
|
4
|
+
const zod_1 = require("zod");
|
|
4
5
|
const utils_1 = require("../utils");
|
|
5
6
|
const base_1 = require("./base");
|
|
6
7
|
const logger = (0, logger_1.getLogger)(__filename);
|
|
@@ -107,8 +108,66 @@ class HandlerProxy {
|
|
|
107
108
|
// It will break type safety because there is no relation between Aux and Plugin.
|
|
108
109
|
const build = (plugins) => {
|
|
109
110
|
const middleware = new HandlerMiddleware(plugins);
|
|
110
|
-
|
|
111
|
+
const invoke = (handler) => (event, context, callback) => {
|
|
111
112
|
new HandlerProxy(event, context, callback).call(middleware, handler);
|
|
112
113
|
};
|
|
114
|
+
/**
|
|
115
|
+
* @param schema - Zod schema to validate the request body.
|
|
116
|
+
* @param handler - Handler that receives the validated body.
|
|
117
|
+
* @param onInvalid - Optional callback to customize invalid responses. If it
|
|
118
|
+
* returns `{ statusCode, body }`, that is sent instead of the default zod
|
|
119
|
+
* error payload.
|
|
120
|
+
*/
|
|
121
|
+
const withBody = (schema, handler, onInvalid) => invoke(async ({ request, response, aux }) => {
|
|
122
|
+
const parsed = schema.safeParse(request.body);
|
|
123
|
+
if (!parsed.success) {
|
|
124
|
+
logger.error(`Validation failed: ${(0, utils_1.stringifyError)((0, zod_1.treeifyError)(parsed.error))}`);
|
|
125
|
+
if (onInvalid) {
|
|
126
|
+
const result = await onInvalid(parsed.error);
|
|
127
|
+
if (result) {
|
|
128
|
+
return response.fail(result.body, result.statusCode);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return response.fail((0, zod_1.treeifyError)(parsed.error), 400);
|
|
132
|
+
}
|
|
133
|
+
const typedRequest = request;
|
|
134
|
+
typedRequest.body = parsed.data;
|
|
135
|
+
return handler({
|
|
136
|
+
request: typedRequest,
|
|
137
|
+
response,
|
|
138
|
+
aux,
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
/**
|
|
142
|
+
* @param schema - Zod schema to validate the request query.
|
|
143
|
+
* @param handler - Handler that receives the validated query.
|
|
144
|
+
* @param onInvalid - Optional callback to customize invalid responses. If it
|
|
145
|
+
* returns `{ statusCode, body }`, that is sent instead of the default zod
|
|
146
|
+
* error payload.
|
|
147
|
+
*/
|
|
148
|
+
const withQuery = (schema, handler, onInvalid) => invoke(async ({ request, response, aux }) => {
|
|
149
|
+
const parsed = schema.safeParse(request.query);
|
|
150
|
+
if (!parsed.success) {
|
|
151
|
+
logger.error(`Validation failed: ${(0, utils_1.stringifyError)((0, zod_1.treeifyError)(parsed.error))}`);
|
|
152
|
+
if (onInvalid) {
|
|
153
|
+
const result = await onInvalid(parsed.error);
|
|
154
|
+
if (result) {
|
|
155
|
+
return response.fail(result.body, result.statusCode);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return response.fail((0, zod_1.treeifyError)(parsed.error), 400);
|
|
159
|
+
}
|
|
160
|
+
const typedRequest = request;
|
|
161
|
+
typedRequest.query = parsed.data;
|
|
162
|
+
return handler({
|
|
163
|
+
request: typedRequest,
|
|
164
|
+
response,
|
|
165
|
+
aux,
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
return Object.assign(invoke, {
|
|
169
|
+
withBody,
|
|
170
|
+
withQuery,
|
|
171
|
+
});
|
|
113
172
|
};
|
|
114
173
|
exports.default = build;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { APIGatewayProxyWebsocketHandlerV2 } from 'aws-lambda';
|
|
2
|
+
import { HandlerPluginBase } from './base';
|
|
3
|
+
import { WebSocketHandler, WebSocketHandlerAuxBase } from './websocketBase';
|
|
4
|
+
declare const buildWebSocket: <Aux extends WebSocketHandlerAuxBase>(plugins: Array<HandlerPluginBase<any>>) => (handler: WebSocketHandler<Aux>) => APIGatewayProxyWebsocketHandlerV2;
|
|
5
|
+
export default buildWebSocket;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const utils_1 = require("../utils");
|
|
4
|
+
const logger_1 = require("../utils/logger");
|
|
5
|
+
const websocketBase_1 = require("./websocketBase");
|
|
6
|
+
const logger = (0, logger_1.getLogger)(__filename);
|
|
7
|
+
class WebSocketHandlerMiddleware {
|
|
8
|
+
auxPromise;
|
|
9
|
+
plugins;
|
|
10
|
+
constructor(plugins) {
|
|
11
|
+
this.plugins = plugins;
|
|
12
|
+
this.auxPromise = this.createAuxPromise();
|
|
13
|
+
}
|
|
14
|
+
createAuxPromise = () => {
|
|
15
|
+
return !this.plugins || this.plugins.length === 0
|
|
16
|
+
? Promise.resolve({}) // tslint:disable-line
|
|
17
|
+
: Promise.all(this.plugins.map((plugin) => {
|
|
18
|
+
const maybePromise = plugin.create();
|
|
19
|
+
return maybePromise instanceof Promise
|
|
20
|
+
? maybePromise
|
|
21
|
+
: Promise.resolve(maybePromise);
|
|
22
|
+
})).then((auxes) => auxes.reduce((all, each) => ({ ...all, ...each }), {}));
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
class WebSocketHandlerProxy {
|
|
26
|
+
request;
|
|
27
|
+
aux;
|
|
28
|
+
result;
|
|
29
|
+
constructor(event, context) {
|
|
30
|
+
logger.stupid(`WebSocket event`, event);
|
|
31
|
+
this.request = new websocketBase_1.WebSocketHandlerRequest(event, context);
|
|
32
|
+
this.aux = {}; // tslint:disable-line
|
|
33
|
+
this.result = { statusCode: 200 };
|
|
34
|
+
}
|
|
35
|
+
call = async (middleware, handler) => {
|
|
36
|
+
try {
|
|
37
|
+
this.aux = await middleware.auxPromise;
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
logger.error(`Error while initializing plugins' aux: ${(0, utils_1.stringifyError)(error)}`);
|
|
41
|
+
return {
|
|
42
|
+
statusCode: 500,
|
|
43
|
+
body: JSON.stringify(error instanceof Error ? { error: error.message } : error),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const actualHandler = [this.generateHandlerDelegator(handler)];
|
|
47
|
+
const beginHandlers = middleware.plugins.map((plugin) => this.generatePluginDelegator(plugin.begin));
|
|
48
|
+
const endHandlers = middleware.plugins.map((plugin) => this.generatePluginDelegator(plugin.end));
|
|
49
|
+
const errorHandlers = middleware.plugins.map((plugin) => this.generatePluginDelegator(plugin.error));
|
|
50
|
+
const iterate = async (handlers) => Promise.all(handlers.map((each) => this.safeCall(each, errorHandlers)));
|
|
51
|
+
const results = [
|
|
52
|
+
...(await iterate(beginHandlers)),
|
|
53
|
+
...(await iterate(actualHandler)),
|
|
54
|
+
...(await iterate(endHandlers)),
|
|
55
|
+
].filter((x) => x);
|
|
56
|
+
// In test phase, throws any exception if there was.
|
|
57
|
+
if (process.env.NODE_ENV === 'test') {
|
|
58
|
+
for (const each of results) {
|
|
59
|
+
if (each instanceof Error) {
|
|
60
|
+
logger.error(`Error occurred: ${(0, utils_1.stringifyError)(each)}`);
|
|
61
|
+
throw each;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
results.forEach((result) => logger.silly(`WebSocket middleware result: ${JSON.stringify(result)}`));
|
|
66
|
+
return this.result;
|
|
67
|
+
};
|
|
68
|
+
safeCall = async (delegator, errorHandlers) => {
|
|
69
|
+
try {
|
|
70
|
+
const result = await delegator();
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
const handled = await this.handleError(error, errorHandlers);
|
|
75
|
+
return handled;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
generateHandlerDelegator = (handler) => async () => {
|
|
79
|
+
const maybePromise = handler({
|
|
80
|
+
request: this.request,
|
|
81
|
+
response: undefined, // WebSocket doesn't use response
|
|
82
|
+
aux: this.aux,
|
|
83
|
+
});
|
|
84
|
+
const result = maybePromise instanceof Promise ? await maybePromise : maybePromise;
|
|
85
|
+
logger.stupid(`WebSocket handler result`, result);
|
|
86
|
+
if (result) {
|
|
87
|
+
this.result = result;
|
|
88
|
+
}
|
|
89
|
+
return result;
|
|
90
|
+
};
|
|
91
|
+
generatePluginDelegator = (pluginCallback) => async () => {
|
|
92
|
+
const maybePromise = pluginCallback({
|
|
93
|
+
request: this.request,
|
|
94
|
+
response: undefined, // WebSocket doesn't use response (for HTTP plugin compatibility)
|
|
95
|
+
aux: this.aux,
|
|
96
|
+
});
|
|
97
|
+
const result = maybePromise instanceof Promise ? await maybePromise : maybePromise;
|
|
98
|
+
logger.stupid(`WebSocket plugin callback result`, result);
|
|
99
|
+
return result;
|
|
100
|
+
};
|
|
101
|
+
handleError = async (error, errorHandlers) => {
|
|
102
|
+
logger.error(error);
|
|
103
|
+
this.request.lastError = error;
|
|
104
|
+
if (errorHandlers) {
|
|
105
|
+
for (const handler of errorHandlers) {
|
|
106
|
+
try {
|
|
107
|
+
await handler();
|
|
108
|
+
}
|
|
109
|
+
catch (ignorable) {
|
|
110
|
+
logger.error(ignorable);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
this.result = {
|
|
115
|
+
statusCode: 500,
|
|
116
|
+
body: JSON.stringify(error instanceof Error ? { error: error.message } : error),
|
|
117
|
+
};
|
|
118
|
+
return error;
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
const buildWebSocket = (plugins) => {
|
|
122
|
+
const middleware = new WebSocketHandlerMiddleware(plugins);
|
|
123
|
+
return (handler) => async (event, context) => {
|
|
124
|
+
return new WebSocketHandlerProxy(event, context).call(middleware, handler);
|
|
125
|
+
};
|
|
126
|
+
};
|
|
127
|
+
exports.default = buildWebSocket;
|
|
@@ -8,6 +8,7 @@ export declare class ConnectionProxy {
|
|
|
8
8
|
private connectionInitOnce;
|
|
9
9
|
private initialized;
|
|
10
10
|
private dbName?;
|
|
11
|
+
private readonly MAX_RETRIES;
|
|
11
12
|
constructor(options: MySQLPluginOptions);
|
|
12
13
|
query: <T>(sql: string, params?: any[]) => Promise<T | undefined>;
|
|
13
14
|
fetch: <T>(sql: string, params?: any[]) => Promise<T[]>;
|
|
@@ -23,6 +24,7 @@ export declare class ConnectionProxy {
|
|
|
23
24
|
destroyConnection: () => void;
|
|
24
25
|
onPluginCreated: () => Promise<void>;
|
|
25
26
|
private prepareConnection;
|
|
27
|
+
private createConnection;
|
|
26
28
|
private ensureConnectionConfig;
|
|
27
29
|
private changeDatabase;
|
|
28
30
|
private tryToInitializeSchema;
|
|
@@ -15,6 +15,7 @@ class ConnectionProxy {
|
|
|
15
15
|
connectionInitOnce = new oncePromise_1.OncePromise();
|
|
16
16
|
initialized;
|
|
17
17
|
dbName;
|
|
18
|
+
MAX_RETRIES = 1;
|
|
18
19
|
constructor(options) {
|
|
19
20
|
this.options = options;
|
|
20
21
|
if (options.schema && options.schema.database) {
|
|
@@ -22,10 +23,11 @@ class ConnectionProxy {
|
|
|
22
23
|
options.config.database = undefined;
|
|
23
24
|
}
|
|
24
25
|
this.secretsCache = secretsManager_1.SecretsManagerCache.getInstance();
|
|
26
|
+
if (options.secretsManagerConfig) {
|
|
27
|
+
this.secretsCache.configure(options.secretsManagerConfig);
|
|
28
|
+
}
|
|
25
29
|
}
|
|
26
|
-
query = (sql, params) => new Promise(
|
|
27
|
-
const connection = await this.prepareConnection();
|
|
28
|
-
await this.tryToInitializeSchema(false);
|
|
30
|
+
query = (sql, params) => this.prepareConnection().then((connection) => this.tryToInitializeSchema(false).then(() => new Promise((resolve, reject) => {
|
|
29
31
|
if (process.env.NODE_ENV !== 'test') {
|
|
30
32
|
logger.silly(`Execute query[${sql}] with params[${params}]`);
|
|
31
33
|
}
|
|
@@ -41,7 +43,7 @@ class ConnectionProxy {
|
|
|
41
43
|
}
|
|
42
44
|
}
|
|
43
45
|
});
|
|
44
|
-
});
|
|
46
|
+
})));
|
|
45
47
|
fetch = (sql, params) => this.query(sql, params).then((res) => res || []);
|
|
46
48
|
fetchOne = (sql, params, defaultValue) => this.fetch(sql, params).then((res) => {
|
|
47
49
|
if (res === undefined || res[0] === undefined) {
|
|
@@ -50,9 +52,7 @@ class ConnectionProxy {
|
|
|
50
52
|
}
|
|
51
53
|
return res[0];
|
|
52
54
|
});
|
|
53
|
-
beginTransaction = () => new Promise(
|
|
54
|
-
const connection = await this.prepareConnection();
|
|
55
|
-
await this.tryToInitializeSchema(false);
|
|
55
|
+
beginTransaction = () => this.prepareConnection().then((connection) => this.tryToInitializeSchema(false).then(() => new Promise((resolve, reject) => {
|
|
56
56
|
connection.beginTransaction((err) => {
|
|
57
57
|
if (err) {
|
|
58
58
|
reject(err);
|
|
@@ -60,10 +60,8 @@ class ConnectionProxy {
|
|
|
60
60
|
}
|
|
61
61
|
resolve();
|
|
62
62
|
});
|
|
63
|
-
});
|
|
64
|
-
commit = () => new Promise(
|
|
65
|
-
const connection = await this.prepareConnection();
|
|
66
|
-
await this.tryToInitializeSchema(false);
|
|
63
|
+
})));
|
|
64
|
+
commit = () => this.prepareConnection().then((connection) => this.tryToInitializeSchema(false).then(() => new Promise((resolve, reject) => {
|
|
67
65
|
connection.commit((err) => {
|
|
68
66
|
if (err) {
|
|
69
67
|
reject(err);
|
|
@@ -71,10 +69,8 @@ class ConnectionProxy {
|
|
|
71
69
|
}
|
|
72
70
|
resolve();
|
|
73
71
|
});
|
|
74
|
-
});
|
|
75
|
-
rollback = () => new Promise(
|
|
76
|
-
const connection = await this.prepareConnection();
|
|
77
|
-
await this.tryToInitializeSchema(false);
|
|
72
|
+
})));
|
|
73
|
+
rollback = () => this.prepareConnection().then((connection) => this.tryToInitializeSchema(false).then(() => new Promise((resolve, reject) => {
|
|
78
74
|
connection.rollback((err) => {
|
|
79
75
|
if (err) {
|
|
80
76
|
reject(err);
|
|
@@ -82,7 +78,7 @@ class ConnectionProxy {
|
|
|
82
78
|
}
|
|
83
79
|
resolve();
|
|
84
80
|
});
|
|
85
|
-
});
|
|
81
|
+
})));
|
|
86
82
|
clearConnection = () => {
|
|
87
83
|
const conn = this.connection;
|
|
88
84
|
this.connection = undefined;
|
|
@@ -120,12 +116,40 @@ class ConnectionProxy {
|
|
|
120
116
|
}
|
|
121
117
|
return await this.connectionInitOnce.run(async () => {
|
|
122
118
|
await this.ensureConnectionConfig();
|
|
123
|
-
|
|
124
|
-
conn.connect();
|
|
125
|
-
this.connection = conn;
|
|
119
|
+
this.connection = await this.createConnection(this.MAX_RETRIES);
|
|
126
120
|
return this.connection;
|
|
127
121
|
});
|
|
128
122
|
};
|
|
123
|
+
createConnection = async (remainingRetries) => {
|
|
124
|
+
const conn = (0, mysql2_1.createConnection)(this.connectionConfig);
|
|
125
|
+
return new Promise((resolve, reject) => {
|
|
126
|
+
conn.on('error', (err) => {
|
|
127
|
+
logger.error(`Connection error event: ${err.message}`);
|
|
128
|
+
});
|
|
129
|
+
conn.connect((err) => {
|
|
130
|
+
if (err) {
|
|
131
|
+
logger.error(`Failed to connect to database: ${err.message}`);
|
|
132
|
+
conn.destroy();
|
|
133
|
+
if (remainingRetries > 0) {
|
|
134
|
+
logger.warn(`Retrying database connection... (${remainingRetries} attempt(s) remaining)`);
|
|
135
|
+
setTimeout(() => {
|
|
136
|
+
this.createConnection(remainingRetries - 1)
|
|
137
|
+
.then(resolve)
|
|
138
|
+
.catch(reject);
|
|
139
|
+
}, 100);
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
logger.error('Database connection failed after all retries. Giving up.');
|
|
143
|
+
reject(err);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
logger.verbose('Database connection established successfully.');
|
|
148
|
+
resolve(conn);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
};
|
|
129
153
|
ensureConnectionConfig = async () => {
|
|
130
154
|
if (this.connectionConfig) {
|
|
131
155
|
return;
|
|
@@ -14,9 +14,13 @@ class LazyConnectionPool {
|
|
|
14
14
|
secretsCache;
|
|
15
15
|
configInitOnce = new oncePromise_1.OncePromise();
|
|
16
16
|
connectionInitOnce = new oncePromise_1.OncePromise();
|
|
17
|
+
MAX_RETRIES = 1;
|
|
17
18
|
constructor(options) {
|
|
18
19
|
this.options = options;
|
|
19
20
|
this.secretsCache = secretsManager_1.SecretsManagerCache.getInstance();
|
|
21
|
+
if (options.secretsManagerConfig) {
|
|
22
|
+
this.secretsCache.configure(options.secretsManagerConfig);
|
|
23
|
+
}
|
|
20
24
|
}
|
|
21
25
|
ensureConnectionConfig = async () => {
|
|
22
26
|
if (this.connectionConfig) {
|
|
@@ -44,21 +48,42 @@ class LazyConnectionPool {
|
|
|
44
48
|
this.connectionInitOnce
|
|
45
49
|
.run(async () => {
|
|
46
50
|
await this.ensureConnectionConfig();
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
+
return await this.createConnection(this.MAX_RETRIES);
|
|
52
|
+
})
|
|
53
|
+
.then((conn) => callback(null, conn))
|
|
54
|
+
.catch((err) => callback(err, {}));
|
|
55
|
+
};
|
|
56
|
+
createConnection = async (remainingRetries) => {
|
|
57
|
+
const conn = (0, mysql2_1.createConnection)(this.connectionConfig);
|
|
58
|
+
return new Promise((resolve, reject) => {
|
|
59
|
+
conn.on('error', (err) => {
|
|
60
|
+
logger.error(`Database connection error occurred: ${err.message}`);
|
|
61
|
+
});
|
|
62
|
+
conn.connect((err) => {
|
|
63
|
+
if (err) {
|
|
64
|
+
logger.error(`Failed to connect to database: ${err.message}`);
|
|
65
|
+
conn.destroy();
|
|
66
|
+
if (remainingRetries > 0) {
|
|
67
|
+
logger.warn(`Retrying database connection... (${remainingRetries} attempt(s) remaining)`);
|
|
68
|
+
setTimeout(() => {
|
|
69
|
+
this.createConnection(remainingRetries - 1)
|
|
70
|
+
.then(resolve)
|
|
71
|
+
.catch(reject);
|
|
72
|
+
}, 100);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
logger.error('Database connection failed after all retries. Giving up.');
|
|
51
76
|
reject(err);
|
|
52
|
-
return;
|
|
53
77
|
}
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
logger.verbose('Database connection established successfully.');
|
|
54
81
|
const wrapped = this._addRelease(conn);
|
|
55
82
|
this.connection = wrapped;
|
|
56
83
|
resolve(wrapped);
|
|
57
|
-
}
|
|
84
|
+
}
|
|
58
85
|
});
|
|
59
|
-
})
|
|
60
|
-
.then((conn) => callback(null, conn))
|
|
61
|
-
.catch((err) => callback(err, {}));
|
|
86
|
+
});
|
|
62
87
|
};
|
|
63
88
|
end = (callback) => {
|
|
64
89
|
const conn = this.connection;
|
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
export declare const middleware: {
|
|
2
|
-
build: <Aux extends import("./base").HandlerAuxBase>(plugins: Array<import("./base").HandlerPluginBase<any>>) => (handler: import("./base").Handler<Aux>) => (event: any, context: any, callback: any) => void
|
|
2
|
+
build: <Aux extends import("./base").HandlerAuxBase>(plugins: Array<import("./base").HandlerPluginBase<any>>) => ((handler: import("./base").Handler<Aux>) => (event: any, context: any, callback: any) => void) & {
|
|
3
|
+
withBody: <S>(schema: import("zod").ZodType<S>, handler: (context: {
|
|
4
|
+
request: Omit<import("./base").HandlerRequest, "body"> & {
|
|
5
|
+
body: S;
|
|
6
|
+
};
|
|
7
|
+
response: import("./base").HandlerResponse;
|
|
8
|
+
aux: Aux;
|
|
9
|
+
}) => any, onInvalid?: (error: import("zod").ZodError) => {
|
|
10
|
+
statusCode: number;
|
|
11
|
+
body: any;
|
|
12
|
+
} | Promise<{
|
|
13
|
+
statusCode: number;
|
|
14
|
+
body: any;
|
|
15
|
+
} | void> | void) => (event: any, context: any, callback: any) => void;
|
|
16
|
+
withQuery: <Q>(schema: import("zod").ZodType<Q>, handler: (context: {
|
|
17
|
+
request: Omit<import("./base").HandlerRequest, "query"> & {
|
|
18
|
+
query: Q;
|
|
19
|
+
};
|
|
20
|
+
response: import("./base").HandlerResponse;
|
|
21
|
+
aux: Aux;
|
|
22
|
+
}) => any, onInvalid?: (error: import("zod").ZodError<Q>) => {
|
|
23
|
+
statusCode: number;
|
|
24
|
+
body: any;
|
|
25
|
+
} | Promise<{
|
|
26
|
+
statusCode: number;
|
|
27
|
+
body: any;
|
|
28
|
+
} | void> | void) => (event: any, context: any, callback: any) => void;
|
|
29
|
+
};
|
|
30
|
+
buildWebSocket: <Aux extends import("./websocketBase").WebSocketHandlerAuxBase>(plugins: Array<import("./base").HandlerPluginBase<any>>) => (handler: import("./websocketBase").WebSocketHandler<Aux>) => import("aws-lambda").APIGatewayProxyWebsocketHandlerV2;
|
|
3
31
|
aws: (options?: import("./aws").AWSPluginOptions) => import("./aws").AWSPlugin;
|
|
4
32
|
trace: (options: import("./trace").TracerPluginOptions) => import("./trace").TracerPlugin;
|
|
5
33
|
logger: (options: import("./logger").LoggerPluginOptions) => import("./logger").LoggerPlugin;
|
|
@@ -11,3 +39,4 @@ export * from './database/index';
|
|
|
11
39
|
export * from './logger';
|
|
12
40
|
export * from './mysql';
|
|
13
41
|
export * from './trace';
|
|
42
|
+
export * from './websocketBase';
|
package/dist/middleware/index.js
CHANGED
|
@@ -16,12 +16,14 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
exports.middleware = void 0;
|
|
18
18
|
const build_1 = require("./build");
|
|
19
|
+
const buildWebSocket_1 = require("./buildWebSocket");
|
|
19
20
|
const aws_1 = require("./aws");
|
|
20
21
|
const logger_1 = require("./logger");
|
|
21
22
|
const mysql_1 = require("./mysql");
|
|
22
23
|
const trace_1 = require("./trace");
|
|
23
24
|
exports.middleware = {
|
|
24
25
|
build: build_1.default,
|
|
26
|
+
buildWebSocket: buildWebSocket_1.default,
|
|
25
27
|
aws: aws_1.default,
|
|
26
28
|
trace: trace_1.default,
|
|
27
29
|
logger: logger_1.default,
|
|
@@ -33,3 +35,4 @@ __exportStar(require("./database/index"), exports);
|
|
|
33
35
|
__exportStar(require("./logger"), exports);
|
|
34
36
|
__exportStar(require("./mysql"), exports);
|
|
35
37
|
__exportStar(require("./trace"), exports);
|
|
38
|
+
__exportStar(require("./websocketBase"), exports);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { SecretsManagerClientConfig } from '@aws-sdk/client-secrets-manager';
|
|
1
2
|
import type { ConnectionOptions, PoolOptions } from 'mysql2';
|
|
2
3
|
import { HandlerAuxBase, HandlerPluginBase } from './base';
|
|
3
4
|
import { ConnectionProxy } from './database/connectionProxy';
|
|
@@ -12,6 +13,7 @@ export interface MySQLPluginOptions {
|
|
|
12
13
|
* AWS Secrets Manager secret ID containing {@link DatabaseCredentials}
|
|
13
14
|
*/
|
|
14
15
|
secretId?: string;
|
|
16
|
+
secretsManagerConfig?: SecretsManagerClientConfig;
|
|
15
17
|
schema?: {
|
|
16
18
|
eager?: boolean;
|
|
17
19
|
ignoreError?: boolean;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { APIGatewayProxyWebsocketEventV2, Context } from 'aws-lambda';
|
|
2
|
+
export interface WebSocketHandlerAuxBase {
|
|
3
|
+
[key: string]: any;
|
|
4
|
+
}
|
|
5
|
+
export declare class WebSocketHandlerRequest {
|
|
6
|
+
event: APIGatewayProxyWebsocketEventV2;
|
|
7
|
+
context: Context;
|
|
8
|
+
lastError: Error | string | undefined;
|
|
9
|
+
private lazyBody?;
|
|
10
|
+
constructor(event: APIGatewayProxyWebsocketEventV2, context: Context);
|
|
11
|
+
get body(): any;
|
|
12
|
+
get connectionId(): string;
|
|
13
|
+
get routeKey(): string;
|
|
14
|
+
get domainName(): string;
|
|
15
|
+
get stage(): string;
|
|
16
|
+
/**
|
|
17
|
+
* For HTTP plugin compatibility (TracerPlugin uses this).
|
|
18
|
+
* WebSocket events may have headers in $connect route (HTTP handshake),
|
|
19
|
+
* but typically don't have headers in other routes.
|
|
20
|
+
*/
|
|
21
|
+
header(key: string): string | undefined;
|
|
22
|
+
}
|
|
23
|
+
export interface WebSocketHandlerResponse {
|
|
24
|
+
statusCode: number;
|
|
25
|
+
body?: string;
|
|
26
|
+
}
|
|
27
|
+
export interface WebSocketHandlerContext<A extends WebSocketHandlerAuxBase> {
|
|
28
|
+
request: WebSocketHandlerRequest;
|
|
29
|
+
response: undefined;
|
|
30
|
+
aux: A;
|
|
31
|
+
}
|
|
32
|
+
export type WebSocketHandler<A extends WebSocketHandlerAuxBase> = (context: WebSocketHandlerContext<A>) => WebSocketHandlerResponse | Promise<WebSocketHandlerResponse> | undefined;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WebSocketHandlerRequest = void 0;
|
|
4
|
+
const logger_1 = require("../utils/logger");
|
|
5
|
+
const logger = (0, logger_1.getLogger)(__filename);
|
|
6
|
+
class WebSocketHandlerRequest {
|
|
7
|
+
event;
|
|
8
|
+
context;
|
|
9
|
+
lastError;
|
|
10
|
+
lazyBody;
|
|
11
|
+
constructor(event, context) {
|
|
12
|
+
this.event = event;
|
|
13
|
+
this.context = context;
|
|
14
|
+
this.lastError = undefined;
|
|
15
|
+
const ev = this.event;
|
|
16
|
+
if (ev.headers) {
|
|
17
|
+
const normalized = {};
|
|
18
|
+
for (const key of Object.keys(ev.headers)) {
|
|
19
|
+
normalized[key.toLowerCase()] = ev.headers[key];
|
|
20
|
+
}
|
|
21
|
+
ev.headers = normalized;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
get body() {
|
|
25
|
+
if (!this.event.body) {
|
|
26
|
+
return {};
|
|
27
|
+
}
|
|
28
|
+
if (this.lazyBody === undefined) {
|
|
29
|
+
try {
|
|
30
|
+
this.lazyBody = JSON.parse(this.event.body);
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
logger.error(`Failed to parse WebSocket body: ${error}`);
|
|
34
|
+
this.lazyBody = {};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return this.lazyBody || {};
|
|
38
|
+
}
|
|
39
|
+
get connectionId() {
|
|
40
|
+
return this.event.requestContext.connectionId;
|
|
41
|
+
}
|
|
42
|
+
get routeKey() {
|
|
43
|
+
return this.event.requestContext.routeKey;
|
|
44
|
+
}
|
|
45
|
+
get domainName() {
|
|
46
|
+
return this.event.requestContext.domainName;
|
|
47
|
+
}
|
|
48
|
+
get stage() {
|
|
49
|
+
return this.event.requestContext.stage;
|
|
50
|
+
}
|
|
51
|
+
// HTTP plugin compatibility methods
|
|
52
|
+
/**
|
|
53
|
+
* For HTTP plugin compatibility (TracerPlugin uses this).
|
|
54
|
+
* WebSocket events may have headers in $connect route (HTTP handshake),
|
|
55
|
+
* but typically don't have headers in other routes.
|
|
56
|
+
*/
|
|
57
|
+
header(key) {
|
|
58
|
+
const event = this.event;
|
|
59
|
+
if (event.headers) {
|
|
60
|
+
return event.headers[key.toLowerCase()];
|
|
61
|
+
}
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
exports.WebSocketHandlerRequest = WebSocketHandlerRequest;
|