@middy/util 7.2.3 → 7.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.d.ts +25 -0
- package/index.js +92 -19
- package/package.json +2 -5
package/index.d.ts
CHANGED
|
@@ -184,3 +184,28 @@ declare function lambdaContext(
|
|
|
184
184
|
): unknown;
|
|
185
185
|
|
|
186
186
|
declare const httpErrorCodes: Record<number, string>;
|
|
187
|
+
|
|
188
|
+
export type OptionSchemaRule =
|
|
189
|
+
| "string"
|
|
190
|
+
| "number"
|
|
191
|
+
| "boolean"
|
|
192
|
+
| "function"
|
|
193
|
+
| "object"
|
|
194
|
+
| "array"
|
|
195
|
+
| "string?"
|
|
196
|
+
| "number?"
|
|
197
|
+
| "boolean?"
|
|
198
|
+
| "function?"
|
|
199
|
+
| "object?"
|
|
200
|
+
| "array?"
|
|
201
|
+
| ((value: unknown) => boolean);
|
|
202
|
+
|
|
203
|
+
export type OptionSchema = Record<string, OptionSchemaRule>;
|
|
204
|
+
|
|
205
|
+
export declare function validateOptions(
|
|
206
|
+
packageName: string,
|
|
207
|
+
schema: OptionSchema,
|
|
208
|
+
options?: Record<string, unknown>,
|
|
209
|
+
): void;
|
|
210
|
+
|
|
211
|
+
export declare const awsClientOptionSchema: OptionSchema;
|
package/index.js
CHANGED
|
@@ -1,5 +1,75 @@
|
|
|
1
1
|
// Copyright 2017 - 2026 will Farrell, Luciano Mammino, and Middy contributors.
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
// Option validation helper.
|
|
5
|
+
// Schema values:
|
|
6
|
+
// 'string' | 'number' | 'boolean' | 'function' | 'object' | 'array'
|
|
7
|
+
// Trailing '?' marks the field as optional (may be undefined).
|
|
8
|
+
// (value) => boolean predicate — only called when value is not undefined
|
|
9
|
+
// (i.e. predicates treat the field as optional by design).
|
|
10
|
+
// Keys in `options` that are not in `schema` throw, catching typos.
|
|
11
|
+
const validateOptionsTypeCheckers = {
|
|
12
|
+
string: (v) => typeof v === "string",
|
|
13
|
+
number: (v) => typeof v === "number" && !Number.isNaN(v),
|
|
14
|
+
boolean: (v) => typeof v === "boolean",
|
|
15
|
+
function: (v) => typeof v === "function",
|
|
16
|
+
object: (v) => v !== null && typeof v === "object" && !Array.isArray(v),
|
|
17
|
+
array: (v) => Array.isArray(v),
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// Shared schema for middlewares that wrap an AWS SDK client. Spread into
|
|
21
|
+
// package-specific schemas: `{ ...awsClientOptionSchema, extraField: 'type?' }`.
|
|
22
|
+
export const awsClientOptionSchema = {
|
|
23
|
+
AwsClient: "function?",
|
|
24
|
+
awsClientOptions: "object?",
|
|
25
|
+
awsClientAssumeRole: "string?",
|
|
26
|
+
awsClientCapture: "function?",
|
|
27
|
+
fetchData: "object?",
|
|
28
|
+
disablePrefetch: "boolean?",
|
|
29
|
+
cacheKey: "string?",
|
|
30
|
+
cacheKeyExpiry: "object?",
|
|
31
|
+
cacheExpiry: (v) => typeof v === "number" && v >= -1,
|
|
32
|
+
cacheMaxSize: (v) => Number.isInteger(v) && v >= 1,
|
|
33
|
+
setToContext: "boolean?",
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const validateOptions = (packageName, schema, options = {}) => {
|
|
37
|
+
const fail = (message) => {
|
|
38
|
+
throw new TypeError(message, { cause: { package: packageName } });
|
|
39
|
+
};
|
|
40
|
+
if (
|
|
41
|
+
options === null ||
|
|
42
|
+
typeof options !== "object" ||
|
|
43
|
+
Array.isArray(options)
|
|
44
|
+
) {
|
|
45
|
+
fail("options must be an object");
|
|
46
|
+
}
|
|
47
|
+
for (const key of Object.keys(options)) {
|
|
48
|
+
if (!Object.hasOwn(schema, key)) {
|
|
49
|
+
fail(`Unknown option '${key}'`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
for (const key of Object.keys(schema)) {
|
|
53
|
+
const rule = schema[key];
|
|
54
|
+
const value = options[key];
|
|
55
|
+
if (typeof rule === "function") {
|
|
56
|
+
if (value !== undefined && !rule(value)) {
|
|
57
|
+
fail(`Invalid option '${key}'`);
|
|
58
|
+
}
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
const optional = rule.endsWith("?");
|
|
62
|
+
const type = optional ? rule.slice(0, -1) : rule;
|
|
63
|
+
const checker = validateOptionsTypeCheckers[type];
|
|
64
|
+
if (!checker) fail(`Unknown schema type '${type}' for option '${key}'`);
|
|
65
|
+
if (value === undefined) {
|
|
66
|
+
if (!optional) fail(`Missing required option '${key}' (${type})`);
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (!checker(value)) fail(`Option '${key}' must be ${type}`);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
3
73
|
export const createPrefetchClient = (options) => {
|
|
4
74
|
const { awsClientOptions } = options;
|
|
5
75
|
const client = new options.AwsClient(awsClientOptions);
|
|
@@ -139,7 +209,10 @@ export const sanitizeKey = (key) => {
|
|
|
139
209
|
};
|
|
140
210
|
|
|
141
211
|
// fetch Cache
|
|
142
|
-
|
|
212
|
+
// Map keyed by cacheKey; value shape: { value:{fetchKey:Promise}, expiry, refresh?, modified? }
|
|
213
|
+
// Map chosen over plain object so deletion is O(1), frees the key slot, and
|
|
214
|
+
// avoids the `delete` operator (biome's performance/noDelete rule).
|
|
215
|
+
const cache = new Map();
|
|
143
216
|
const defaultCacheMaxSize = 128;
|
|
144
217
|
|
|
145
218
|
const validateCacheExpiry = (cacheExpiry) => {
|
|
@@ -173,8 +246,9 @@ export const processCache = (
|
|
|
173
246
|
if (cached.modified) {
|
|
174
247
|
const value = middlewareFetch(middlewareFetchRequest, cached.value);
|
|
175
248
|
Object.assign(cached.value, value);
|
|
176
|
-
|
|
177
|
-
|
|
249
|
+
const entry = { value: cached.value, expiry: cached.expiry };
|
|
250
|
+
cache.set(cacheKey, entry);
|
|
251
|
+
return entry;
|
|
178
252
|
}
|
|
179
253
|
cached.cache = true;
|
|
180
254
|
return cached;
|
|
@@ -189,16 +263,18 @@ export const processCache = (
|
|
|
189
263
|
const expiry = cacheExpiry > 86400000 ? cacheExpiry : now + cacheExpiry;
|
|
190
264
|
const duration = cacheExpiry > 86400000 ? cacheExpiry - now : cacheExpiry;
|
|
191
265
|
if (cacheExpiry) {
|
|
192
|
-
clearTimeout(cache
|
|
266
|
+
clearTimeout(cache.get(cacheKey)?.refresh);
|
|
267
|
+
// .unref() so a pending refresh timer does not keep the Lambda event
|
|
268
|
+
// loop alive (relevant under `callbackWaitsForEmptyEventLoop: false`).
|
|
193
269
|
const refresh =
|
|
194
270
|
duration > 0
|
|
195
271
|
? setTimeout(
|
|
196
272
|
() =>
|
|
197
273
|
processCache(options, middlewareFetch, middlewareFetchRequest),
|
|
198
274
|
duration,
|
|
199
|
-
)
|
|
275
|
+
).unref()
|
|
200
276
|
: undefined;
|
|
201
|
-
cache
|
|
277
|
+
cache.set(cacheKey, { value, expiry, refresh });
|
|
202
278
|
evictCache(cacheMaxSize);
|
|
203
279
|
}
|
|
204
280
|
return { value, expiry };
|
|
@@ -212,13 +288,12 @@ export const catchInvalidSignatureException = (e, client, command) => {
|
|
|
212
288
|
};
|
|
213
289
|
|
|
214
290
|
export const getCache = (key) => {
|
|
215
|
-
|
|
216
|
-
return cache[key];
|
|
291
|
+
return cache.get(key) ?? {};
|
|
217
292
|
};
|
|
218
293
|
|
|
219
294
|
// Used to remove parts of a cache
|
|
220
295
|
export const modifyCache = (cacheKey, value) => {
|
|
221
|
-
const entry = cache
|
|
296
|
+
const entry = cache.get(cacheKey);
|
|
222
297
|
if (!entry) return;
|
|
223
298
|
clearTimeout(entry.refresh);
|
|
224
299
|
entry.value = value;
|
|
@@ -226,32 +301,30 @@ export const modifyCache = (cacheKey, value) => {
|
|
|
226
301
|
};
|
|
227
302
|
|
|
228
303
|
const evictCache = (maxSize) => {
|
|
229
|
-
|
|
230
|
-
if (cacheKeys.length <= maxSize) return;
|
|
304
|
+
if (cache.size <= maxSize) return;
|
|
231
305
|
let oldestKey = null;
|
|
232
306
|
let oldestExpiry = Infinity;
|
|
233
|
-
for (const key of
|
|
234
|
-
const entry = cache[key];
|
|
307
|
+
for (const [key, entry] of cache) {
|
|
235
308
|
if (entry && entry.expiry < oldestExpiry) {
|
|
236
309
|
oldestExpiry = entry.expiry;
|
|
237
310
|
oldestKey = key;
|
|
238
311
|
}
|
|
239
312
|
}
|
|
240
|
-
if (oldestKey) {
|
|
241
|
-
clearTimeout(cache
|
|
242
|
-
cache
|
|
313
|
+
if (oldestKey !== null) {
|
|
314
|
+
clearTimeout(cache.get(oldestKey)?.refresh);
|
|
315
|
+
cache.delete(oldestKey);
|
|
243
316
|
}
|
|
244
317
|
};
|
|
245
318
|
|
|
246
319
|
export const clearCache = (inputKeys = null) => {
|
|
247
320
|
let keys = inputKeys;
|
|
248
|
-
keys ??=
|
|
321
|
+
keys ??= [...cache.keys()];
|
|
249
322
|
if (!Array.isArray(keys)) {
|
|
250
323
|
keys = [keys];
|
|
251
324
|
}
|
|
252
325
|
for (const cacheKey of keys) {
|
|
253
|
-
clearTimeout(cache
|
|
254
|
-
cache
|
|
326
|
+
clearTimeout(cache.get(cacheKey)?.refresh);
|
|
327
|
+
cache.delete(cacheKey);
|
|
255
328
|
}
|
|
256
329
|
};
|
|
257
330
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@middy/util",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.3.0",
|
|
4
4
|
"description": "🛵 The stylish Node.js middleware engine for AWS Lambda (util package)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -17,9 +17,6 @@
|
|
|
17
17
|
"import": {
|
|
18
18
|
"types": "./index.d.ts",
|
|
19
19
|
"default": "./index.js"
|
|
20
|
-
},
|
|
21
|
-
"require": {
|
|
22
|
-
"default": "./index.js"
|
|
23
20
|
}
|
|
24
21
|
}
|
|
25
22
|
},
|
|
@@ -60,7 +57,7 @@
|
|
|
60
57
|
},
|
|
61
58
|
"devDependencies": {
|
|
62
59
|
"@aws-sdk/client-ssm": "^3.0.0",
|
|
63
|
-
"@middy/core": "7.
|
|
60
|
+
"@middy/core": "7.3.0",
|
|
64
61
|
"@types/aws-lambda": "^8.0.0",
|
|
65
62
|
"@types/node": "^22.0.0",
|
|
66
63
|
"aws-xray-sdk": "^3.3.3"
|