@middy/util 7.3.0 → 7.3.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/index.d.ts +73 -13
- package/index.js +215 -42
- package/package.json +2 -2
package/index.d.ts
CHANGED
|
@@ -185,27 +185,87 @@ declare function lambdaContext(
|
|
|
185
185
|
|
|
186
186
|
declare const httpErrorCodes: Record<number, string>;
|
|
187
187
|
|
|
188
|
-
export type
|
|
188
|
+
export type JsonSchemaType =
|
|
189
189
|
| "string"
|
|
190
190
|
| "number"
|
|
191
|
+
| "integer"
|
|
191
192
|
| "boolean"
|
|
192
|
-
| "function"
|
|
193
193
|
| "object"
|
|
194
|
-
| "array"
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
194
|
+
| "array";
|
|
195
|
+
|
|
196
|
+
export type StringRule = {
|
|
197
|
+
type: "string";
|
|
198
|
+
pattern?: string;
|
|
199
|
+
minLength?: number;
|
|
200
|
+
maxLength?: number;
|
|
201
|
+
enum?: readonly string[];
|
|
202
|
+
examples?: readonly string[];
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
export type NumberRule = {
|
|
206
|
+
type: "number" | "integer";
|
|
207
|
+
minimum?: number;
|
|
208
|
+
maximum?: number;
|
|
209
|
+
enum?: readonly number[];
|
|
210
|
+
examples?: readonly number[];
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
export type BooleanRule = {
|
|
214
|
+
type: "boolean";
|
|
215
|
+
enum?: readonly boolean[];
|
|
216
|
+
examples?: readonly boolean[];
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
export type ArrayRule = {
|
|
220
|
+
type: "array";
|
|
221
|
+
items?: OptionSchemaRule;
|
|
222
|
+
examples?: readonly unknown[];
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
export type ObjectRule = {
|
|
226
|
+
type: "object";
|
|
227
|
+
required?: readonly string[];
|
|
228
|
+
properties?: { [key: string]: OptionSchemaRule };
|
|
229
|
+
additionalProperties?: boolean | OptionSchemaRule;
|
|
230
|
+
examples?: readonly object[];
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
export type EnumRule = {
|
|
234
|
+
enum: readonly unknown[];
|
|
235
|
+
type?: JsonSchemaType;
|
|
236
|
+
examples?: readonly unknown[];
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
export type ConstRule = {
|
|
240
|
+
const: unknown;
|
|
241
|
+
examples?: readonly unknown[];
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
export type InstanceofRule = {
|
|
245
|
+
instanceof: string;
|
|
246
|
+
examples?: readonly unknown[];
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
export type OneOfRule = {
|
|
250
|
+
oneOf: readonly OptionSchemaRule[];
|
|
251
|
+
examples?: readonly unknown[];
|
|
252
|
+
};
|
|
202
253
|
|
|
203
|
-
export type
|
|
254
|
+
export type OptionSchemaRule =
|
|
255
|
+
| StringRule
|
|
256
|
+
| NumberRule
|
|
257
|
+
| BooleanRule
|
|
258
|
+
| ArrayRule
|
|
259
|
+
| ObjectRule
|
|
260
|
+
| EnumRule
|
|
261
|
+
| ConstRule
|
|
262
|
+
| InstanceofRule
|
|
263
|
+
| OneOfRule;
|
|
264
|
+
|
|
265
|
+
export type OptionSchema = ObjectRule;
|
|
204
266
|
|
|
205
267
|
export declare function validateOptions(
|
|
206
268
|
packageName: string,
|
|
207
269
|
schema: OptionSchema,
|
|
208
270
|
options?: Record<string, unknown>,
|
|
209
271
|
): void;
|
|
210
|
-
|
|
211
|
-
export declare const awsClientOptionSchema: OptionSchema;
|
package/index.js
CHANGED
|
@@ -3,71 +3,244 @@
|
|
|
3
3
|
|
|
4
4
|
// Option validation helper.
|
|
5
5
|
// Schema values:
|
|
6
|
-
// 'string' | 'number' | 'boolean' | 'function' | 'object' | 'array'
|
|
6
|
+
// 'string' | 'number' | 'integer' | 'boolean' | 'function' | 'object' | 'array'
|
|
7
7
|
// Trailing '?' marks the field as optional (may be undefined).
|
|
8
8
|
// (value) => boolean predicate — only called when value is not undefined
|
|
9
9
|
// (i.e. predicates treat the field as optional by design).
|
|
10
|
-
//
|
|
10
|
+
// { type: 'array' | 'array?', items: <itemSchema> }
|
|
11
|
+
// `items` is applied to each array element. It can be a type string,
|
|
12
|
+
// a predicate function, or a plain object treated as a per-element
|
|
13
|
+
// object schema (validated recursively with the same rules).
|
|
14
|
+
// { type: '<type>' | '<type>?', minimum?, maximum?, minLength?, maxLength?, pattern? }
|
|
15
|
+
// Numeric: `minimum`/`maximum` (number/integer).
|
|
16
|
+
// String: `minLength`/`maxLength` (string length), `pattern` (regex source).
|
|
17
|
+
// { type: 'object' | 'object?', properties?: {...}, additionalProperties?: <rule> }
|
|
18
|
+
// `properties` validates known keys with the flat-schema form.
|
|
19
|
+
// `additionalProperties` validates every other key's value against the
|
|
20
|
+
// given rule (string, predicate, or nested object schema). Without it,
|
|
21
|
+
// unknown keys throw.
|
|
22
|
+
// { enum: [...values], type?: '<type>' | '<type>?' }
|
|
23
|
+
// Value must strict-equal one of the listed values. Optional by default;
|
|
24
|
+
// combine with `type` to require a specific type and/or presence.
|
|
25
|
+
// Keys in `options` (or nested objects) that are not in `schema` throw,
|
|
26
|
+
// catching typos.
|
|
11
27
|
const validateOptionsTypeCheckers = {
|
|
12
28
|
string: (v) => typeof v === "string",
|
|
13
29
|
number: (v) => typeof v === "number" && !Number.isNaN(v),
|
|
30
|
+
integer: (v) => Number.isInteger(v),
|
|
14
31
|
boolean: (v) => typeof v === "boolean",
|
|
15
32
|
function: (v) => typeof v === "function",
|
|
16
33
|
object: (v) => v !== null && typeof v === "object" && !Array.isArray(v),
|
|
17
34
|
array: (v) => Array.isArray(v),
|
|
18
35
|
};
|
|
19
36
|
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
};
|
|
37
|
+
const isPlainObject = (v) =>
|
|
38
|
+
v !== null && typeof v === "object" && !Array.isArray(v);
|
|
35
39
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
options === null ||
|
|
42
|
-
typeof options !== "object" ||
|
|
43
|
-
Array.isArray(options)
|
|
44
|
-
) {
|
|
45
|
-
fail("options must be an object");
|
|
40
|
+
const checkSchemaObject = (schema, options, path, fail) => {
|
|
41
|
+
if (!isPlainObject(options)) {
|
|
42
|
+
fail(
|
|
43
|
+
path ? `Option '${path}' must be object` : "options must be an object",
|
|
44
|
+
);
|
|
46
45
|
}
|
|
47
46
|
for (const key of Object.keys(options)) {
|
|
48
47
|
if (!Object.hasOwn(schema, key)) {
|
|
49
|
-
fail(`Unknown option '${key}'`);
|
|
48
|
+
fail(`Unknown option '${path ? `${path}.${key}` : key}'`);
|
|
50
49
|
}
|
|
51
50
|
}
|
|
52
51
|
for (const key of Object.keys(schema)) {
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
52
|
+
const childPath = path ? `${path}.${key}` : key;
|
|
53
|
+
checkRule(schema[key], options[key], childPath, fail);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// Returns true if type check passed (and value is defined), false if the
|
|
58
|
+
// caller should stop validating (value was undefined and optional).
|
|
59
|
+
const checkTypeSpec = (rawType, value, path, fail) => {
|
|
60
|
+
const optional = rawType.endsWith("?");
|
|
61
|
+
const type = optional ? rawType.slice(0, -1) : rawType;
|
|
62
|
+
const checker = validateOptionsTypeCheckers[type];
|
|
63
|
+
if (!checker) fail(`Unknown schema type '${type}' for option '${path}'`);
|
|
64
|
+
if (value === undefined) {
|
|
65
|
+
if (!optional) fail(`Missing required option '${path}' (${type})`);
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
if (!checker(value)) fail(`Option '${path}' must be ${type}`);
|
|
69
|
+
return true;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// Plain object with no rule-marker key (`type`, `enum`, `oneOf`, `const`,
|
|
73
|
+
// `instanceof`) is a flat object schema; anything else is a rule. Used when
|
|
74
|
+
// dispatching `items` and `additionalProperties`.
|
|
75
|
+
const checkNestedRule = (rule, value, path, fail) => {
|
|
76
|
+
if (
|
|
77
|
+
isPlainObject(rule) &&
|
|
78
|
+
typeof rule.type !== "string" &&
|
|
79
|
+
!Array.isArray(rule.enum) &&
|
|
80
|
+
!Array.isArray(rule.oneOf) &&
|
|
81
|
+
!Object.hasOwn(rule, "const") &&
|
|
82
|
+
typeof rule.instanceof !== "string"
|
|
83
|
+
) {
|
|
84
|
+
checkSchemaObject(rule, value, path, fail);
|
|
85
|
+
} else {
|
|
86
|
+
checkRule(rule, value, path, fail);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const childPathOf = (path, key) => (path ? `${path}.${key}` : key);
|
|
91
|
+
|
|
92
|
+
const resolveInstance = (name) => {
|
|
93
|
+
const ctor = globalThis[name];
|
|
94
|
+
if (typeof ctor !== "function") {
|
|
95
|
+
throw new Error(`Unknown 'instanceof' class '${name}'`);
|
|
96
|
+
}
|
|
97
|
+
return ctor;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const checkRule = (rule, value, path, fail) => {
|
|
101
|
+
if (typeof rule === "function") {
|
|
102
|
+
if (value !== undefined && !rule(value)) {
|
|
103
|
+
fail(`Invalid option '${path}'`);
|
|
104
|
+
}
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (typeof rule === "string") {
|
|
108
|
+
checkTypeSpec(rule, value, path, fail);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (isPlainObject(rule) && Object.hasOwn(rule, "const")) {
|
|
112
|
+
if (value === undefined) return;
|
|
113
|
+
if (value !== rule.const) {
|
|
114
|
+
fail(`Option '${path}' must equal ${JSON.stringify(rule.const)}`);
|
|
115
|
+
}
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (isPlainObject(rule) && Array.isArray(rule.oneOf)) {
|
|
119
|
+
if (value === undefined) return;
|
|
120
|
+
let matches = 0;
|
|
121
|
+
for (const sub of rule.oneOf) {
|
|
122
|
+
try {
|
|
123
|
+
checkRule(sub, value, path, (msg) => {
|
|
124
|
+
throw new TypeError(msg);
|
|
125
|
+
});
|
|
126
|
+
matches++;
|
|
127
|
+
} catch {}
|
|
128
|
+
}
|
|
129
|
+
if (matches !== 1) {
|
|
130
|
+
fail(`Option '${path}' must match exactly one schema in oneOf`);
|
|
131
|
+
}
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (isPlainObject(rule) && typeof rule.instanceof === "string") {
|
|
135
|
+
if (value === undefined) return;
|
|
136
|
+
const ctor = resolveInstance(rule.instanceof);
|
|
137
|
+
if (!(value instanceof ctor)) {
|
|
138
|
+
fail(`Option '${path}' must be instanceof ${rule.instanceof}`);
|
|
139
|
+
}
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (isPlainObject(rule) && Array.isArray(rule.enum)) {
|
|
143
|
+
if (typeof rule.type === "string") {
|
|
144
|
+
if (!checkTypeSpec(rule.type, value, path, fail)) return;
|
|
145
|
+
} else if (value === undefined) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
if (!rule.enum.includes(value)) {
|
|
149
|
+
fail(`Option '${path}' must be one of ${JSON.stringify(rule.enum)}`);
|
|
150
|
+
}
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (isPlainObject(rule) && typeof rule.type === "string") {
|
|
154
|
+
const {
|
|
155
|
+
type: rawType,
|
|
156
|
+
items,
|
|
157
|
+
properties,
|
|
158
|
+
required,
|
|
159
|
+
additionalProperties,
|
|
160
|
+
minimum,
|
|
161
|
+
maximum,
|
|
162
|
+
pattern,
|
|
163
|
+
minLength,
|
|
164
|
+
maxLength,
|
|
165
|
+
} = rule;
|
|
166
|
+
if (!checkTypeSpec(rawType, value, path, fail)) return;
|
|
167
|
+
const type = rawType.endsWith("?") ? rawType.slice(0, -1) : rawType;
|
|
168
|
+
if (minimum !== undefined && value < minimum) {
|
|
169
|
+
fail(`Option '${path}' must be >= ${minimum}`);
|
|
170
|
+
}
|
|
171
|
+
if (maximum !== undefined && value > maximum) {
|
|
172
|
+
fail(`Option '${path}' must be <= ${maximum}`);
|
|
173
|
+
}
|
|
174
|
+
if (pattern !== undefined && !new RegExp(pattern).test(value)) {
|
|
175
|
+
fail(`Option '${path}' must match pattern ${pattern}`);
|
|
176
|
+
}
|
|
177
|
+
if (minLength !== undefined && value.length < minLength) {
|
|
178
|
+
fail(`Option '${path}' must have length >= ${minLength}`);
|
|
179
|
+
}
|
|
180
|
+
if (maxLength !== undefined && value.length > maxLength) {
|
|
181
|
+
fail(`Option '${path}' must have length <= ${maxLength}`);
|
|
182
|
+
}
|
|
183
|
+
if (type === "array" && items !== undefined) {
|
|
184
|
+
for (let i = 0; i < value.length; i++) {
|
|
185
|
+
checkNestedRule(items, value[i], `${path}[${i}]`, fail);
|
|
58
186
|
}
|
|
59
|
-
continue;
|
|
60
187
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
continue;
|
|
188
|
+
if (type === "object" && Array.isArray(required)) {
|
|
189
|
+
for (const key of required) {
|
|
190
|
+
if (value[key] === undefined) {
|
|
191
|
+
fail(`Missing required option '${childPathOf(path, key)}'`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
68
194
|
}
|
|
69
|
-
if (
|
|
195
|
+
if (
|
|
196
|
+
type === "object" &&
|
|
197
|
+
(properties || additionalProperties !== undefined)
|
|
198
|
+
) {
|
|
199
|
+
for (const key of Object.keys(value)) {
|
|
200
|
+
if (properties && Object.hasOwn(properties, key)) continue;
|
|
201
|
+
if (
|
|
202
|
+
additionalProperties === undefined ||
|
|
203
|
+
additionalProperties === false
|
|
204
|
+
) {
|
|
205
|
+
fail(`Unknown option '${childPathOf(path, key)}'`);
|
|
206
|
+
}
|
|
207
|
+
if (additionalProperties === true) continue;
|
|
208
|
+
checkNestedRule(
|
|
209
|
+
additionalProperties,
|
|
210
|
+
value[key],
|
|
211
|
+
childPathOf(path, key),
|
|
212
|
+
fail,
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
if (properties) {
|
|
216
|
+
for (const key of Object.keys(properties)) {
|
|
217
|
+
if (value[key] === undefined) continue;
|
|
218
|
+
checkRule(properties[key], value[key], childPathOf(path, key), fail);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
fail(`Invalid schema for option '${path}'`);
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const isJsonSchemaForm = (schema) =>
|
|
228
|
+
isPlainObject(schema) &&
|
|
229
|
+
schema.type === "object" &&
|
|
230
|
+
(Object.hasOwn(schema, "properties") ||
|
|
231
|
+
Object.hasOwn(schema, "required") ||
|
|
232
|
+
Object.hasOwn(schema, "additionalProperties"));
|
|
233
|
+
|
|
234
|
+
export const validateOptions = (packageName, schema, options = {}) => {
|
|
235
|
+
const fail = (message) => {
|
|
236
|
+
throw new TypeError(message, { cause: { package: packageName } });
|
|
237
|
+
};
|
|
238
|
+
if (isJsonSchemaForm(schema)) {
|
|
239
|
+
checkRule(schema, options, "", fail);
|
|
240
|
+
} else {
|
|
241
|
+
checkSchemaObject(schema, options, "", fail);
|
|
70
242
|
}
|
|
243
|
+
return options;
|
|
71
244
|
};
|
|
72
245
|
|
|
73
246
|
export const createPrefetchClient = (options) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@middy/util",
|
|
3
|
-
"version": "7.3.
|
|
3
|
+
"version": "7.3.1",
|
|
4
4
|
"description": "🛵 The stylish Node.js middleware engine for AWS Lambda (util package)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@aws-sdk/client-ssm": "^3.0.0",
|
|
60
|
-
"@middy/core": "7.3.
|
|
60
|
+
"@middy/core": "7.3.1",
|
|
61
61
|
"@types/aws-lambda": "^8.0.0",
|
|
62
62
|
"@types/node": "^22.0.0",
|
|
63
63
|
"aws-xray-sdk": "^3.3.3"
|