serverless-simple-middleware 0.0.73 → 0.0.74
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/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/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 +28 -1
- package/dist/middleware/mysql.d.ts +2 -0
- package/dist/utils/secretsManager.d.ts +3 -0
- package/dist/utils/secretsManager.js +10 -1
- package/package.json +3 -2
- package/src/middleware/base.ts +9 -1
- package/src/middleware/build.ts +102 -2
- package/src/middleware/database/connectionProxy.ts +109 -62
- package/src/middleware/database/sqlClient.ts +44 -12
- package/src/middleware/mysql.ts +2 -0
- package/src/utils/secretsManager.ts +14 -1
- package/tsconfig.json +2 -1
|
@@ -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;
|
|
@@ -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,32 @@
|
|
|
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
|
+
};
|
|
3
30
|
aws: (options?: import("./aws").AWSPluginOptions) => import("./aws").AWSPlugin;
|
|
4
31
|
trace: (options: import("./trace").TracerPluginOptions) => import("./trace").TracerPlugin;
|
|
5
32
|
logger: (options: import("./logger").LoggerPluginOptions) => import("./logger").LoggerPlugin;
|
|
@@ -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;
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
import { SecretsManagerClientConfig } from '@aws-sdk/client-secrets-manager';
|
|
1
2
|
import type { DatabaseCredentials } from '../middleware/mysql';
|
|
2
3
|
export declare class SecretsManagerCache {
|
|
3
4
|
private static instance;
|
|
4
5
|
private client;
|
|
6
|
+
private clientConfig;
|
|
5
7
|
private cache;
|
|
6
8
|
private constructor();
|
|
7
9
|
static getInstance(): SecretsManagerCache;
|
|
10
|
+
configure(config: SecretsManagerClientConfig): void;
|
|
8
11
|
private getClient;
|
|
9
12
|
getSecret<T = any>(secretId: string): Promise<T>;
|
|
10
13
|
getDatabaseCredentials(secretId: string): Promise<DatabaseCredentials>;
|
|
@@ -8,6 +8,7 @@ const logger = (0, logger_1.getLogger)(__filename);
|
|
|
8
8
|
class SecretsManagerCache {
|
|
9
9
|
static instance;
|
|
10
10
|
client;
|
|
11
|
+
clientConfig;
|
|
11
12
|
cache = new Map();
|
|
12
13
|
constructor() { }
|
|
13
14
|
static getInstance() {
|
|
@@ -16,9 +17,17 @@ class SecretsManagerCache {
|
|
|
16
17
|
}
|
|
17
18
|
return SecretsManagerCache.instance;
|
|
18
19
|
}
|
|
20
|
+
configure(config) {
|
|
21
|
+
if (this.client) {
|
|
22
|
+
logger.warn('SecretsManager client already initialized. Reconfiguring with new config.');
|
|
23
|
+
this.client = undefined;
|
|
24
|
+
}
|
|
25
|
+
this.clientConfig = config;
|
|
26
|
+
logger.debug('SecretsManager client config updated');
|
|
27
|
+
}
|
|
19
28
|
getClient() {
|
|
20
29
|
if (!this.client) {
|
|
21
|
-
this.client = new client_secrets_manager_1.SecretsManagerClient({});
|
|
30
|
+
this.client = new client_secrets_manager_1.SecretsManagerClient(this.clientConfig ?? {});
|
|
22
31
|
logger.debug('SecretsManager client initialized');
|
|
23
32
|
}
|
|
24
33
|
return this.client;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "serverless-simple-middleware",
|
|
3
3
|
"description": "Simple middleware to translate the interface of lambda's handler to request => response",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.74",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"author": "VoyagerX",
|
|
@@ -45,7 +45,8 @@
|
|
|
45
45
|
"nanoid": "4.0.2",
|
|
46
46
|
"simple-staging": "^0.0.12",
|
|
47
47
|
"ts-enum-util": "^3.1.0",
|
|
48
|
-
"uuid": "^3.3.2"
|
|
48
|
+
"uuid": "^3.3.2",
|
|
49
|
+
"zod": "^4.3.5"
|
|
49
50
|
},
|
|
50
51
|
"devDependencies": {
|
|
51
52
|
"@types/jest": "^23.3.1",
|
package/src/middleware/base.ts
CHANGED
|
@@ -40,6 +40,10 @@ export class HandlerRequest {
|
|
|
40
40
|
return this.lazyBody || {};
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
set body(value: any) {
|
|
44
|
+
this.lazyBody = value;
|
|
45
|
+
}
|
|
46
|
+
|
|
43
47
|
get path(): { [key: string]: string | undefined } {
|
|
44
48
|
return this.event.pathParameters || {};
|
|
45
49
|
}
|
|
@@ -48,6 +52,10 @@ export class HandlerRequest {
|
|
|
48
52
|
return this.event.queryStringParameters || {};
|
|
49
53
|
}
|
|
50
54
|
|
|
55
|
+
set query(value: { [key: string]: any }) {
|
|
56
|
+
this.event.queryStringParameters = value;
|
|
57
|
+
}
|
|
58
|
+
|
|
51
59
|
public header(key: string): string | undefined {
|
|
52
60
|
return this.event.headers[key.toLowerCase()];
|
|
53
61
|
}
|
|
@@ -93,7 +101,7 @@ export class HandlerResponse {
|
|
|
93
101
|
if (this.crossOrigin) {
|
|
94
102
|
headers['Access-Control-Allow-Origin'] = this.crossOrigin;
|
|
95
103
|
}
|
|
96
|
-
let multiValueHeaders = undefined;
|
|
104
|
+
let multiValueHeaders: any = undefined;
|
|
97
105
|
if (this.cookies.length > 0) {
|
|
98
106
|
multiValueHeaders = { 'Set-Cookie': this.cookies };
|
|
99
107
|
}
|
package/src/middleware/build.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getLogger } from '../utils/logger';
|
|
2
2
|
|
|
3
|
+
import { treeifyError, type ZodError, type ZodSchema } from 'zod';
|
|
3
4
|
import { stringifyError } from '../utils';
|
|
4
5
|
import {
|
|
5
6
|
Handler,
|
|
@@ -165,9 +166,108 @@ const build = <Aux extends HandlerAuxBase>(
|
|
|
165
166
|
plugins: Array<HandlerPluginBase<any>>,
|
|
166
167
|
) => {
|
|
167
168
|
const middleware = new HandlerMiddleware<Aux>(plugins);
|
|
168
|
-
|
|
169
|
-
(event: any, context: any, callback: any) => {
|
|
169
|
+
const invoke =
|
|
170
|
+
(handler: Handler<Aux>) => (event: any, context: any, callback: any) => {
|
|
170
171
|
new HandlerProxy<Aux>(event, context, callback).call(middleware, handler);
|
|
171
172
|
};
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* @param schema - Zod schema to validate the request body.
|
|
176
|
+
* @param handler - Handler that receives the validated body.
|
|
177
|
+
* @param onInvalid - Optional callback to customize invalid responses. If it
|
|
178
|
+
* returns `{ statusCode, body }`, that is sent instead of the default zod
|
|
179
|
+
* error payload.
|
|
180
|
+
*/
|
|
181
|
+
const withBody = <S>(
|
|
182
|
+
schema: ZodSchema<S>,
|
|
183
|
+
handler: (context: {
|
|
184
|
+
request: Omit<HandlerRequest, 'body'> & { body: S };
|
|
185
|
+
response: HandlerResponse;
|
|
186
|
+
aux: Aux;
|
|
187
|
+
}) => any,
|
|
188
|
+
onInvalid?: (
|
|
189
|
+
error: ZodError,
|
|
190
|
+
) =>
|
|
191
|
+
| { statusCode: number; body: any }
|
|
192
|
+
| Promise<{ statusCode: number; body: any } | void>
|
|
193
|
+
| void,
|
|
194
|
+
) =>
|
|
195
|
+
invoke(async ({ request, response, aux }) => {
|
|
196
|
+
const parsed = schema.safeParse(request.body);
|
|
197
|
+
if (!parsed.success) {
|
|
198
|
+
logger.error(
|
|
199
|
+
`Validation failed: ${stringifyError(treeifyError(parsed.error))}`,
|
|
200
|
+
);
|
|
201
|
+
if (onInvalid) {
|
|
202
|
+
const result = await onInvalid(parsed.error);
|
|
203
|
+
if (result) {
|
|
204
|
+
return response.fail(result.body, result.statusCode);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return response.fail(treeifyError(parsed.error), 400);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const typedRequest = request as Omit<HandlerRequest, 'body'> & {
|
|
211
|
+
body: S;
|
|
212
|
+
};
|
|
213
|
+
typedRequest.body = parsed.data;
|
|
214
|
+
return handler({
|
|
215
|
+
request: typedRequest,
|
|
216
|
+
response,
|
|
217
|
+
aux,
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* @param schema - Zod schema to validate the request query.
|
|
223
|
+
* @param handler - Handler that receives the validated query.
|
|
224
|
+
* @param onInvalid - Optional callback to customize invalid responses. If it
|
|
225
|
+
* returns `{ statusCode, body }`, that is sent instead of the default zod
|
|
226
|
+
* error payload.
|
|
227
|
+
*/
|
|
228
|
+
const withQuery = <Q>(
|
|
229
|
+
schema: ZodSchema<Q>,
|
|
230
|
+
handler: (context: {
|
|
231
|
+
request: Omit<HandlerRequest, 'query'> & { query: Q };
|
|
232
|
+
response: HandlerResponse;
|
|
233
|
+
aux: Aux;
|
|
234
|
+
}) => any,
|
|
235
|
+
onInvalid?: (
|
|
236
|
+
error: ZodError<Q>,
|
|
237
|
+
) =>
|
|
238
|
+
| { statusCode: number; body: any }
|
|
239
|
+
| Promise<{ statusCode: number; body: any } | void>
|
|
240
|
+
| void,
|
|
241
|
+
) =>
|
|
242
|
+
invoke(async ({ request, response, aux }) => {
|
|
243
|
+
const parsed = schema.safeParse(request.query);
|
|
244
|
+
if (!parsed.success) {
|
|
245
|
+
logger.error(
|
|
246
|
+
`Validation failed: ${stringifyError(treeifyError(parsed.error))}`,
|
|
247
|
+
);
|
|
248
|
+
if (onInvalid) {
|
|
249
|
+
const result = await onInvalid(parsed.error);
|
|
250
|
+
if (result) {
|
|
251
|
+
return response.fail(result.body, result.statusCode);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return response.fail(treeifyError(parsed.error), 400);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const typedRequest = request as Omit<HandlerRequest, 'query'> & {
|
|
258
|
+
query: Q;
|
|
259
|
+
};
|
|
260
|
+
typedRequest.query = parsed.data;
|
|
261
|
+
return handler({
|
|
262
|
+
request: typedRequest,
|
|
263
|
+
response,
|
|
264
|
+
aux,
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
return Object.assign(invoke, {
|
|
269
|
+
withBody,
|
|
270
|
+
withQuery,
|
|
271
|
+
});
|
|
172
272
|
};
|
|
173
273
|
export default build;
|
|
@@ -23,40 +23,47 @@ export class ConnectionProxy {
|
|
|
23
23
|
private initialized: boolean;
|
|
24
24
|
private dbName?: string;
|
|
25
25
|
|
|
26
|
+
private readonly MAX_RETRIES: number = 1;
|
|
27
|
+
|
|
26
28
|
public constructor(private readonly options: MySQLPluginOptions) {
|
|
27
29
|
if (options.schema && options.schema.database) {
|
|
28
30
|
this.dbName = options.config.database;
|
|
29
31
|
options.config.database = undefined;
|
|
30
32
|
}
|
|
31
33
|
this.secretsCache = SecretsManagerCache.getInstance();
|
|
34
|
+
if (options.secretsManagerConfig) {
|
|
35
|
+
this.secretsCache.configure(options.secretsManagerConfig);
|
|
36
|
+
}
|
|
32
37
|
}
|
|
33
38
|
|
|
34
39
|
public query = <T>(sql: string, params?: any[]) =>
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if (process.env.NODE_ENV !== 'test') {
|
|
40
|
-
logger.silly(`Execute query[${sql}] with params[${params}]`);
|
|
41
|
-
}
|
|
42
|
-
connection.query(
|
|
43
|
-
sql,
|
|
44
|
-
params,
|
|
45
|
-
(err: QueryError, result: QueryResult, _fields?: FieldPacket[]) => {
|
|
46
|
-
if (err) {
|
|
47
|
-
logger.error(
|
|
48
|
-
`error occurred in database query=${sql}, error=${err}`,
|
|
49
|
-
);
|
|
50
|
-
reject(err);
|
|
51
|
-
} else {
|
|
52
|
-
resolve(result as T);
|
|
40
|
+
this.prepareConnection().then((connection) =>
|
|
41
|
+
this.tryToInitializeSchema(false).then(
|
|
42
|
+
() =>
|
|
43
|
+
new Promise<T | undefined>((resolve, reject) => {
|
|
53
44
|
if (process.env.NODE_ENV !== 'test') {
|
|
54
|
-
logger.silly(`
|
|
45
|
+
logger.silly(`Execute query[${sql}] with params[${params}]`);
|
|
55
46
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
47
|
+
connection.query(
|
|
48
|
+
sql,
|
|
49
|
+
params,
|
|
50
|
+
(err: QueryError, result: QueryResult, _fields?: FieldPacket[]) => {
|
|
51
|
+
if (err) {
|
|
52
|
+
logger.error(
|
|
53
|
+
`error occurred in database query=${sql}, error=${err}`,
|
|
54
|
+
);
|
|
55
|
+
reject(err);
|
|
56
|
+
} else {
|
|
57
|
+
resolve(result as T);
|
|
58
|
+
if (process.env.NODE_ENV !== 'test') {
|
|
59
|
+
logger.silly(`DB result is ${JSON.stringify(result)}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
);
|
|
64
|
+
}),
|
|
65
|
+
),
|
|
66
|
+
);
|
|
60
67
|
|
|
61
68
|
public fetch = <T>(sql: string, params?: any[]) =>
|
|
62
69
|
this.query<T[]>(sql, params).then((res) => res || []);
|
|
@@ -71,46 +78,52 @@ export class ConnectionProxy {
|
|
|
71
78
|
});
|
|
72
79
|
|
|
73
80
|
public beginTransaction = () =>
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
81
|
+
this.prepareConnection().then((connection) =>
|
|
82
|
+
this.tryToInitializeSchema(false).then(
|
|
83
|
+
() =>
|
|
84
|
+
new Promise<void>((resolve, reject) => {
|
|
85
|
+
connection.beginTransaction((err: QueryError) => {
|
|
86
|
+
if (err) {
|
|
87
|
+
reject(err);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
resolve();
|
|
91
|
+
});
|
|
92
|
+
}),
|
|
93
|
+
),
|
|
94
|
+
);
|
|
86
95
|
|
|
87
96
|
public commit = () =>
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
97
|
+
this.prepareConnection().then((connection) =>
|
|
98
|
+
this.tryToInitializeSchema(false).then(
|
|
99
|
+
() =>
|
|
100
|
+
new Promise<void>((resolve, reject) => {
|
|
101
|
+
connection.commit((err: QueryError) => {
|
|
102
|
+
if (err) {
|
|
103
|
+
reject(err);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
resolve();
|
|
107
|
+
});
|
|
108
|
+
}),
|
|
109
|
+
),
|
|
110
|
+
);
|
|
100
111
|
|
|
101
112
|
public rollback = () =>
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
113
|
+
this.prepareConnection().then((connection) =>
|
|
114
|
+
this.tryToInitializeSchema(false).then(
|
|
115
|
+
() =>
|
|
116
|
+
new Promise<void>((resolve, reject) => {
|
|
117
|
+
connection.rollback((err: QueryError) => {
|
|
118
|
+
if (err) {
|
|
119
|
+
reject(err);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
resolve();
|
|
123
|
+
});
|
|
124
|
+
}),
|
|
125
|
+
),
|
|
126
|
+
);
|
|
114
127
|
|
|
115
128
|
public clearConnection = () => {
|
|
116
129
|
const conn = this.connection;
|
|
@@ -153,13 +166,47 @@ export class ConnectionProxy {
|
|
|
153
166
|
|
|
154
167
|
return await this.connectionInitOnce.run(async () => {
|
|
155
168
|
await this.ensureConnectionConfig();
|
|
156
|
-
|
|
157
|
-
conn.connect();
|
|
158
|
-
this.connection = conn;
|
|
169
|
+
this.connection = await this.createConnection(this.MAX_RETRIES);
|
|
159
170
|
return this.connection;
|
|
160
171
|
});
|
|
161
172
|
};
|
|
162
173
|
|
|
174
|
+
private createConnection = async (
|
|
175
|
+
remainingRetries: number,
|
|
176
|
+
): Promise<Connection> => {
|
|
177
|
+
const conn = createConnection(this.connectionConfig);
|
|
178
|
+
|
|
179
|
+
return new Promise((resolve, reject) => {
|
|
180
|
+
conn.on('error', (err) => {
|
|
181
|
+
logger.error(`Connection error event: ${err.message}`);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
conn.connect((err) => {
|
|
185
|
+
if (err) {
|
|
186
|
+
logger.error(`Failed to connect to database: ${err.message}`);
|
|
187
|
+
conn.destroy();
|
|
188
|
+
|
|
189
|
+
if (remainingRetries > 0) {
|
|
190
|
+
logger.warn(
|
|
191
|
+
`Retrying database connection... (${remainingRetries} attempt(s) remaining)`,
|
|
192
|
+
);
|
|
193
|
+
setTimeout(() => {
|
|
194
|
+
this.createConnection(remainingRetries - 1)
|
|
195
|
+
.then(resolve)
|
|
196
|
+
.catch(reject);
|
|
197
|
+
}, 100);
|
|
198
|
+
} else {
|
|
199
|
+
logger.error('Database connection failed after all retries. Giving up.');
|
|
200
|
+
reject(err);
|
|
201
|
+
}
|
|
202
|
+
} else {
|
|
203
|
+
logger.verbose('Database connection established successfully.');
|
|
204
|
+
resolve(conn);
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
};
|
|
209
|
+
|
|
163
210
|
private ensureConnectionConfig = async (): Promise<void> => {
|
|
164
211
|
if (this.connectionConfig) {
|
|
165
212
|
return;
|
|
@@ -30,8 +30,13 @@ class LazyConnectionPool implements MysqlPool {
|
|
|
30
30
|
private configInitOnce = new OncePromise<void>();
|
|
31
31
|
private connectionInitOnce = new OncePromise<LazyMysqlPoolConnection>();
|
|
32
32
|
|
|
33
|
+
private readonly MAX_RETRIES: number = 1;
|
|
34
|
+
|
|
33
35
|
constructor(private readonly options: MySQLPluginOptions) {
|
|
34
36
|
this.secretsCache = SecretsManagerCache.getInstance();
|
|
37
|
+
if (options.secretsManagerConfig) {
|
|
38
|
+
this.secretsCache.configure(options.secretsManagerConfig);
|
|
39
|
+
}
|
|
35
40
|
}
|
|
36
41
|
|
|
37
42
|
private ensureConnectionConfig = async (): Promise<void> => {
|
|
@@ -68,23 +73,50 @@ class LazyConnectionPool implements MysqlPool {
|
|
|
68
73
|
this.connectionInitOnce
|
|
69
74
|
.run(async () => {
|
|
70
75
|
await this.ensureConnectionConfig();
|
|
71
|
-
|
|
72
|
-
return await new Promise<LazyMysqlPoolConnection>((resolve, reject) => {
|
|
73
|
-
conn.connect((err: QueryError) => {
|
|
74
|
-
if (err) {
|
|
75
|
-
reject(err);
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
const wrapped = this._addRelease(conn);
|
|
79
|
-
this.connection = wrapped;
|
|
80
|
-
resolve(wrapped);
|
|
81
|
-
});
|
|
82
|
-
});
|
|
76
|
+
return await this.createConnection(this.MAX_RETRIES);
|
|
83
77
|
})
|
|
84
78
|
.then((conn) => callback(null, conn))
|
|
85
79
|
.catch((err) => callback(err, {} as LazyMysqlPoolConnection));
|
|
86
80
|
};
|
|
87
81
|
|
|
82
|
+
private createConnection = async (
|
|
83
|
+
remainingRetries: number,
|
|
84
|
+
): Promise<LazyMysqlPoolConnection> => {
|
|
85
|
+
const conn = createConnection(this.connectionConfig);
|
|
86
|
+
|
|
87
|
+
return new Promise((resolve, reject) => {
|
|
88
|
+
conn.on('error', (err) => {
|
|
89
|
+
logger.error(`Database connection error occurred: ${err.message}`);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
conn.connect((err: QueryError) => {
|
|
93
|
+
if (err) {
|
|
94
|
+
logger.error(`Failed to connect to database: ${err.message}`);
|
|
95
|
+
conn.destroy();
|
|
96
|
+
|
|
97
|
+
if (remainingRetries > 0) {
|
|
98
|
+
logger.warn(
|
|
99
|
+
`Retrying database connection... (${remainingRetries} attempt(s) remaining)`,
|
|
100
|
+
);
|
|
101
|
+
setTimeout(() => {
|
|
102
|
+
this.createConnection(remainingRetries - 1)
|
|
103
|
+
.then(resolve)
|
|
104
|
+
.catch(reject);
|
|
105
|
+
}, 100);
|
|
106
|
+
} else {
|
|
107
|
+
logger.error('Database connection failed after all retries. Giving up.');
|
|
108
|
+
reject(err);
|
|
109
|
+
}
|
|
110
|
+
} else {
|
|
111
|
+
logger.verbose('Database connection established successfully.');
|
|
112
|
+
const wrapped = this._addRelease(conn);
|
|
113
|
+
this.connection = wrapped;
|
|
114
|
+
resolve(wrapped);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
};
|
|
119
|
+
|
|
88
120
|
public end = (callback: (error: unknown) => void): void => {
|
|
89
121
|
const conn = this.connection;
|
|
90
122
|
this.connection = null;
|
package/src/middleware/mysql.ts
CHANGED
|
@@ -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';
|
|
@@ -14,6 +15,7 @@ export interface MySQLPluginOptions {
|
|
|
14
15
|
* AWS Secrets Manager secret ID containing {@link DatabaseCredentials}
|
|
15
16
|
*/
|
|
16
17
|
secretId?: string;
|
|
18
|
+
secretsManagerConfig?: SecretsManagerClientConfig;
|
|
17
19
|
schema?: {
|
|
18
20
|
eager?: boolean;
|
|
19
21
|
ignoreError?: boolean;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
GetSecretValueCommand,
|
|
3
3
|
SecretsManagerClient,
|
|
4
|
+
SecretsManagerClientConfig,
|
|
4
5
|
} from '@aws-sdk/client-secrets-manager';
|
|
5
6
|
import type { DatabaseCredentials } from '../middleware/mysql';
|
|
6
7
|
import { getLogger } from './logger';
|
|
@@ -11,6 +12,7 @@ const logger = getLogger(__filename);
|
|
|
11
12
|
export class SecretsManagerCache {
|
|
12
13
|
private static instance: SecretsManagerCache;
|
|
13
14
|
private client: SecretsManagerClient | undefined;
|
|
15
|
+
private clientConfig: SecretsManagerClientConfig | undefined;
|
|
14
16
|
private cache = new Map<string, any>();
|
|
15
17
|
|
|
16
18
|
private constructor() {}
|
|
@@ -22,9 +24,20 @@ export class SecretsManagerCache {
|
|
|
22
24
|
return SecretsManagerCache.instance;
|
|
23
25
|
}
|
|
24
26
|
|
|
27
|
+
public configure(config: SecretsManagerClientConfig): void {
|
|
28
|
+
if (this.client) {
|
|
29
|
+
logger.warn(
|
|
30
|
+
'SecretsManager client already initialized. Reconfiguring with new config.',
|
|
31
|
+
);
|
|
32
|
+
this.client = undefined;
|
|
33
|
+
}
|
|
34
|
+
this.clientConfig = config;
|
|
35
|
+
logger.debug('SecretsManager client config updated');
|
|
36
|
+
}
|
|
37
|
+
|
|
25
38
|
private getClient(): SecretsManagerClient {
|
|
26
39
|
if (!this.client) {
|
|
27
|
-
this.client = new SecretsManagerClient({});
|
|
40
|
+
this.client = new SecretsManagerClient(this.clientConfig ?? {});
|
|
28
41
|
logger.debug('SecretsManager client initialized');
|
|
29
42
|
}
|
|
30
43
|
return this.client;
|