@azure-net/node-kit 0.0.0-stage → 0.1.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/CHANGELOG.md +11 -0
- package/LICENSE +21 -0
- package/README.md +301 -2
- package/VALIDATION.md +866 -0
- package/dist/delivery/http/ResponseBuilder.d.ts +44 -0
- package/dist/delivery/http/ResponseBuilder.js +135 -0
- package/dist/delivery/http/index.d.ts +1 -0
- package/dist/delivery/http/index.js +1 -0
- package/dist/delivery/index.d.ts +1 -0
- package/dist/delivery/index.js +1 -0
- package/dist/domain/Ensure.d.ts +2 -0
- package/dist/domain/Ensure.js +7 -0
- package/dist/domain/EnsureError.d.ts +4 -0
- package/dist/domain/EnsureError.js +7 -0
- package/dist/domain/index.d.ts +2 -0
- package/dist/domain/index.js +2 -0
- package/dist/infrastructure/background/BackgroundTask.d.ts +12 -0
- package/dist/infrastructure/background/BackgroundTask.js +41 -0
- package/dist/infrastructure/background/index.d.ts +1 -0
- package/dist/infrastructure/background/index.js +1 -0
- package/dist/infrastructure/environment/Env.d.ts +19 -0
- package/dist/infrastructure/environment/Env.js +33 -0
- package/dist/infrastructure/environment/index.d.ts +1 -0
- package/dist/infrastructure/environment/index.js +1 -0
- package/dist/infrastructure/index.d.ts +2 -0
- package/dist/infrastructure/index.js +2 -0
- package/dist/tools/DateHelper.d.ts +13 -0
- package/dist/tools/DateHelper.js +77 -0
- package/dist/tools/DurationHelper.d.ts +6 -0
- package/dist/tools/DurationHelper.js +31 -0
- package/dist/tools/EnumHelper.d.ts +26 -0
- package/dist/tools/EnumHelper.js +40 -0
- package/dist/tools/ErrorHelper.d.ts +3 -0
- package/dist/tools/ErrorHelper.js +31 -0
- package/dist/tools/NumberHelper.d.ts +19 -0
- package/dist/tools/NumberHelper.js +49 -0
- package/dist/tools/ObjectHelper.d.ts +28 -0
- package/dist/tools/ObjectHelper.js +90 -0
- package/dist/tools/PromiseHelper.d.ts +3 -0
- package/dist/tools/PromiseHelper.js +7 -0
- package/dist/tools/StringHelper.d.ts +75 -0
- package/dist/tools/StringHelper.js +127 -0
- package/dist/tools/UidHelper.d.ts +56 -0
- package/dist/tools/UidHelper.js +132 -0
- package/dist/tools/index.d.ts +9 -0
- package/dist/tools/index.js +9 -0
- package/dist/validation/Engine.d.ts +6 -0
- package/dist/validation/Engine.js +406 -0
- package/dist/validation/Result.d.ts +31 -0
- package/dist/validation/Result.js +264 -0
- package/dist/validation/Symbols.d.ts +1 -0
- package/dist/validation/Symbols.js +1 -0
- package/dist/validation/Types.d.ts +105 -0
- package/dist/validation/Types.js +1 -0
- package/dist/validation/Validator.d.ts +26 -0
- package/dist/validation/Validator.js +95 -0
- package/dist/validation/index.d.ts +5 -0
- package/dist/validation/index.js +5 -0
- package/dist/validation/messages/En.d.ts +3 -0
- package/dist/validation/messages/En.js +118 -0
- package/dist/validation/messages/Ru.d.ts +3 -0
- package/dist/validation/messages/Ru.js +118 -0
- package/dist/validation/messages/index.d.ts +7 -0
- package/dist/validation/messages/index.js +7 -0
- package/dist/validation/rules/CustomRules.d.ts +8 -0
- package/dist/validation/rules/CustomRules.js +64 -0
- package/dist/validation/rules/Factory.d.ts +4 -0
- package/dist/validation/rules/Factory.js +21 -0
- package/dist/validation/rules/StringRules.d.ts +105 -0
- package/dist/validation/rules/StringRules.js +481 -0
- package/dist/validation/rules/StructureRules.d.ts +30 -0
- package/dist/validation/rules/StructureRules.js +145 -0
- package/dist/validation/rules/ValueRules.d.ts +82 -0
- package/dist/validation/rules/ValueRules.js +365 -0
- package/dist/validation/rules/index.d.ts +151 -0
- package/dist/validation/rules/index.js +19 -0
- package/package.json +77 -3
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
import { ValidationResult } from "./Result.js";
|
|
2
|
+
import { validationMessagesEn, validationMessagesI18n, } from "./messages/index.js";
|
|
3
|
+
const plans = new WeakMap();
|
|
4
|
+
function planFor(rules) {
|
|
5
|
+
let plan = plans.get(rules);
|
|
6
|
+
if (plan)
|
|
7
|
+
return plan;
|
|
8
|
+
const descriptors = rules.filter((rule) => typeof rule !== "function");
|
|
9
|
+
plan = {
|
|
10
|
+
optional: descriptors.some((rule) => rule.kind === "optional"),
|
|
11
|
+
nullable: descriptors.some((rule) => rule.kind === "nullable" ||
|
|
12
|
+
(rule.kind === "optional" && rule.params.nullable === true)),
|
|
13
|
+
structural: descriptors.some((rule) => rule.structural && rule.kind !== "optional" && rule.kind !== "nullable"),
|
|
14
|
+
guards: descriptors.filter((rule) => rule.kind === "test" && rule.structural),
|
|
15
|
+
children: descriptors.filter((rule) => rule.kind !== "test" &&
|
|
16
|
+
rule.kind !== "optional" &&
|
|
17
|
+
rule.kind !== "nullable"),
|
|
18
|
+
checks: [
|
|
19
|
+
...descriptors.filter((rule) => rule.kind === "test" && !rule.structural),
|
|
20
|
+
...rules.filter((rule) => typeof rule === "function"),
|
|
21
|
+
],
|
|
22
|
+
};
|
|
23
|
+
plans.set(rules, plan);
|
|
24
|
+
return plan;
|
|
25
|
+
}
|
|
26
|
+
function record(value) {
|
|
27
|
+
return (value !== null &&
|
|
28
|
+
typeof value === "object" &&
|
|
29
|
+
!Array.isArray(value) &&
|
|
30
|
+
!(value instanceof Date) &&
|
|
31
|
+
!(value instanceof Map) &&
|
|
32
|
+
!(value instanceof Set) &&
|
|
33
|
+
!(value instanceof RegExp) &&
|
|
34
|
+
!ArrayBuffer.isView(value) &&
|
|
35
|
+
!(value instanceof ArrayBuffer));
|
|
36
|
+
}
|
|
37
|
+
function thenable(value) {
|
|
38
|
+
return (value !== null &&
|
|
39
|
+
(typeof value === "object" || typeof value === "function") &&
|
|
40
|
+
typeof value.then === "function");
|
|
41
|
+
}
|
|
42
|
+
function issue(state, path, code, params = {}, options = {}) {
|
|
43
|
+
const { runtime, issues } = state;
|
|
44
|
+
if (issues.length >= runtime.maxIssues)
|
|
45
|
+
return;
|
|
46
|
+
const key = options.messageKey ?? code;
|
|
47
|
+
const config = runtime.config;
|
|
48
|
+
const own = (object, key) => object && Object.hasOwn(object, key) ? object[key] : undefined;
|
|
49
|
+
const messageContext = {
|
|
50
|
+
code: options.code ?? code,
|
|
51
|
+
messageKey: key,
|
|
52
|
+
params,
|
|
53
|
+
path,
|
|
54
|
+
locale: runtime.locale,
|
|
55
|
+
label: options.label ?? String(path.at(-1) ?? "value"),
|
|
56
|
+
};
|
|
57
|
+
const catalog = own(config.locales ?? validationMessagesI18n, runtime.locale);
|
|
58
|
+
const fallback = own(config.locales ?? validationMessagesI18n, config.fallbackLocale ?? "en");
|
|
59
|
+
const template = options.message ??
|
|
60
|
+
own(config.messages, key) ??
|
|
61
|
+
own(catalog, key) ??
|
|
62
|
+
own(own(validationMessagesI18n, runtime.locale), key) ??
|
|
63
|
+
own(fallback, key) ??
|
|
64
|
+
own(validationMessagesEn, key) ??
|
|
65
|
+
"Invalid value";
|
|
66
|
+
const raw = typeof template === "function" ? template(messageContext) : template;
|
|
67
|
+
const message = raw.replace(/\{([\w]+)\}/g, (match, name) => {
|
|
68
|
+
if (name === "label")
|
|
69
|
+
return messageContext.label;
|
|
70
|
+
return Object.hasOwn(params, name) ? String(params[name]) : match;
|
|
71
|
+
});
|
|
72
|
+
issues.push({ path: [...path], code: messageContext.code, message, params });
|
|
73
|
+
}
|
|
74
|
+
function consume(state, result, context, rule) {
|
|
75
|
+
if (result === true)
|
|
76
|
+
return true;
|
|
77
|
+
if (result === false) {
|
|
78
|
+
issue(state, context.path, rule?.code ?? "custom.invalid", rule?.params, rule?.options);
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
const failures = Array.isArray(result) ? result : [result];
|
|
82
|
+
for (const failure of failures) {
|
|
83
|
+
if (!failure || typeof failure !== "object" || thenable(failure)) {
|
|
84
|
+
throw new TypeError("Validation rules must return true, false, a failure object or an array of failures.");
|
|
85
|
+
}
|
|
86
|
+
issue(state, [...context.path, ...(failure.path ?? [])], failure.code ?? rule?.code ?? "custom.invalid", { ...rule?.params, ...failure.params }, { ...rule?.options, ...failure });
|
|
87
|
+
}
|
|
88
|
+
return failures.length === 0;
|
|
89
|
+
}
|
|
90
|
+
function failStructure(state, context, code, params, options) {
|
|
91
|
+
state.structuralValid = false;
|
|
92
|
+
issue(state, context.path, code, params, options);
|
|
93
|
+
}
|
|
94
|
+
function fork(state) {
|
|
95
|
+
return {
|
|
96
|
+
runtime: state.runtime,
|
|
97
|
+
issues: [],
|
|
98
|
+
jobs: [],
|
|
99
|
+
structuralValid: true,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function prepare(state, value, data, path, rules, depth) {
|
|
103
|
+
const runtime = state.runtime;
|
|
104
|
+
runtime.signal?.throwIfAborted();
|
|
105
|
+
const context = {
|
|
106
|
+
value,
|
|
107
|
+
data,
|
|
108
|
+
root: runtime.root,
|
|
109
|
+
context: runtime.context,
|
|
110
|
+
path,
|
|
111
|
+
signal: runtime.signal,
|
|
112
|
+
};
|
|
113
|
+
if (runtime.budget.exhausted) {
|
|
114
|
+
state.structuralValid = false;
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (++runtime.budget.nodes > runtime.maxNodes) {
|
|
118
|
+
runtime.budget.exhausted = true;
|
|
119
|
+
failStructure(state, context, "validation.nodes", {
|
|
120
|
+
max: runtime.maxNodes,
|
|
121
|
+
});
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (depth > runtime.maxDepth) {
|
|
125
|
+
failStructure(state, context, "validation.depth", {
|
|
126
|
+
max: runtime.maxDepth,
|
|
127
|
+
});
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const { optional, nullable, guards, children, checks, structural } = planFor(rules);
|
|
131
|
+
if ((value === undefined && optional) || (value === null && nullable))
|
|
132
|
+
return;
|
|
133
|
+
if (!structural) {
|
|
134
|
+
throw new TypeError(`Missing structural rule at ${JSON.stringify(path)}. Add string(), number(), object(), unknown(), etc.`);
|
|
135
|
+
}
|
|
136
|
+
if (value === null || value === undefined) {
|
|
137
|
+
for (const rule of checks) {
|
|
138
|
+
if (typeof rule !== "function" &&
|
|
139
|
+
["required", "notNull", "notEmpty"].includes(rule.code) &&
|
|
140
|
+
!consume(state, rule.test(value, context), context, rule)) {
|
|
141
|
+
state.structuralValid = false;
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const initial = state.issues.length;
|
|
147
|
+
// Type guards always precede constraints, regardless of their position in the array.
|
|
148
|
+
for (const rule of guards) {
|
|
149
|
+
if (!consume(state, rule.test(value, context), context, rule)) {
|
|
150
|
+
state.structuralValid = false;
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
const ownJob = { kind: "node", context, checks };
|
|
155
|
+
for (const rule of children) {
|
|
156
|
+
if (rule.kind === "intersection") {
|
|
157
|
+
for (const branch of rule.alternatives)
|
|
158
|
+
prepare(state, value, data, path, branch, depth + 1);
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
if (rule.kind === "lazy" || rule.kind === "validator") {
|
|
162
|
+
if (rule.kind === "validator") {
|
|
163
|
+
const child = {
|
|
164
|
+
...state,
|
|
165
|
+
runtime: {
|
|
166
|
+
...runtime,
|
|
167
|
+
root: value,
|
|
168
|
+
config: { ...runtime.config, unknownKeys: rule.unknownKeys },
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
prepare(child, value, data, path, rule.resolve(), depth + 1);
|
|
172
|
+
if (!child.structuralValid)
|
|
173
|
+
state.structuralValid = false;
|
|
174
|
+
}
|
|
175
|
+
else
|
|
176
|
+
prepare(state, value, data, path, rule.resolve(), depth + 1);
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
if (rule.kind === "union") {
|
|
180
|
+
let alternatives = rule.alternatives;
|
|
181
|
+
if (rule.discriminator !== undefined) {
|
|
182
|
+
const property = record(value)
|
|
183
|
+
? Object.getOwnPropertyDescriptor(value, rule.discriminator)
|
|
184
|
+
: undefined;
|
|
185
|
+
const key = property && Object.hasOwn(property, "value")
|
|
186
|
+
? property.value
|
|
187
|
+
: undefined;
|
|
188
|
+
const variant = (typeof key === "string" ||
|
|
189
|
+
typeof key === "number" ||
|
|
190
|
+
typeof key === "boolean") &&
|
|
191
|
+
Object.hasOwn(rule.variants, String(key))
|
|
192
|
+
? rule.variants[String(key)]
|
|
193
|
+
: undefined;
|
|
194
|
+
if (variant)
|
|
195
|
+
prepare(state, value, data, path, variant, depth + 1);
|
|
196
|
+
else
|
|
197
|
+
failStructure(state, context, rule.code, rule.params, rule.options);
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
const candidates = alternatives.map((alternative) => {
|
|
201
|
+
const candidate = fork(state);
|
|
202
|
+
prepare(candidate, value, data, path, alternative, depth + 1);
|
|
203
|
+
return candidate;
|
|
204
|
+
});
|
|
205
|
+
const valid = candidates.filter((candidate) => candidate.structuralValid);
|
|
206
|
+
if (!valid.length || runtime.budget.exhausted) {
|
|
207
|
+
failStructure(state, context, rule.code, rule.params, rule.options);
|
|
208
|
+
}
|
|
209
|
+
else
|
|
210
|
+
state.jobs.push({ kind: "union", context, rule, candidates: valid });
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
const isObject = rule.kind === "object" || rule.kind === "record";
|
|
214
|
+
const isArray = rule.kind === "array" || rule.kind === "tuple";
|
|
215
|
+
if ((isObject && !record(value)) ||
|
|
216
|
+
(isArray && !Array.isArray(value)) ||
|
|
217
|
+
(rule.kind === "map" && !(value instanceof Map)) ||
|
|
218
|
+
(rule.kind === "set" && !(value instanceof Set))) {
|
|
219
|
+
failStructure(state, context, rule.code, rule.params, rule.options);
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
const container = value;
|
|
223
|
+
if (runtime.active.has(container)) {
|
|
224
|
+
failStructure(state, context, "validation.cycle");
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
runtime.active.add(container);
|
|
228
|
+
try {
|
|
229
|
+
if (isObject) {
|
|
230
|
+
const object = value;
|
|
231
|
+
const shape = rule.shape;
|
|
232
|
+
const keys = shape ? Object.keys(shape) : Object.keys(object);
|
|
233
|
+
for (const key of keys) {
|
|
234
|
+
if (runtime.budget.exhausted ||
|
|
235
|
+
state.issues.length >= runtime.maxIssues)
|
|
236
|
+
break;
|
|
237
|
+
const property = Object.getOwnPropertyDescriptor(object, key);
|
|
238
|
+
if (property && !Object.hasOwn(property, "value")) {
|
|
239
|
+
if (++runtime.budget.nodes > runtime.maxNodes) {
|
|
240
|
+
runtime.budget.exhausted = true;
|
|
241
|
+
failStructure(state, { ...context, path: [...path, key] }, "validation.nodes", { max: runtime.maxNodes });
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
failStructure(state, { ...context, path: [...path, key] }, "object.accessor");
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
prepare(state, property?.value, object, [...path, key], shape?.[key] ?? rule.items, depth + 1);
|
|
248
|
+
if (rule.keys)
|
|
249
|
+
prepare(state, key, object, [...path, key], rule.keys, depth + 1);
|
|
250
|
+
}
|
|
251
|
+
if (shape &&
|
|
252
|
+
(rule.unknownKeys ?? runtime.config.unknownKeys ?? "reject") ===
|
|
253
|
+
"reject") {
|
|
254
|
+
for (const key of Object.keys(object)) {
|
|
255
|
+
if (!Object.hasOwn(shape, key))
|
|
256
|
+
failStructure(state, { ...context, path: [...path, key] }, "object.unknownKeys");
|
|
257
|
+
if (state.issues.length >= runtime.maxIssues)
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
else if (isArray) {
|
|
263
|
+
const array = value;
|
|
264
|
+
if (rule.kind === "tuple" && array.length !== rule.tuple.length) {
|
|
265
|
+
failStructure(state, context, "array.length", { length: rule.tuple.length }, rule.options);
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
for (let index = 0; index < array.length; index++) {
|
|
269
|
+
if (runtime.budget.exhausted ||
|
|
270
|
+
state.issues.length >= runtime.maxIssues)
|
|
271
|
+
break;
|
|
272
|
+
const property = Object.getOwnPropertyDescriptor(array, String(index));
|
|
273
|
+
if (property && !Object.hasOwn(property, "value")) {
|
|
274
|
+
if (++runtime.budget.nodes > runtime.maxNodes) {
|
|
275
|
+
runtime.budget.exhausted = true;
|
|
276
|
+
failStructure(state, { ...context, path: [...path, index] }, "validation.nodes", { max: runtime.maxNodes });
|
|
277
|
+
break;
|
|
278
|
+
}
|
|
279
|
+
failStructure(state, { ...context, path: [...path, index] }, "object.accessor");
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
prepare(state, property?.value, array, [...path, index], rule.items ?? rule.tuple[index], depth + 1);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
else if (rule.kind === "set") {
|
|
287
|
+
let index = 0;
|
|
288
|
+
for (const item of value) {
|
|
289
|
+
if (runtime.budget.exhausted ||
|
|
290
|
+
state.issues.length >= runtime.maxIssues)
|
|
291
|
+
break;
|
|
292
|
+
prepare(state, item, value, [...path, index++], rule.items, depth + 1);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
else if (rule.kind === "map") {
|
|
296
|
+
let index = 0;
|
|
297
|
+
for (const [key, item] of value) {
|
|
298
|
+
if (runtime.budget.exhausted ||
|
|
299
|
+
state.issues.length >= runtime.maxIssues)
|
|
300
|
+
break;
|
|
301
|
+
prepare(state, key, value, [...path, index, "key"], rule.keys, depth + 1);
|
|
302
|
+
prepare(state, item, value, [...path, index++, "value"], rule.items, depth + 1);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
finally {
|
|
307
|
+
runtime.active.delete(container);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
if (state.issues.length === initial && checks.length)
|
|
311
|
+
state.jobs.push(ownJob);
|
|
312
|
+
}
|
|
313
|
+
function* execute(state, allowCustom) {
|
|
314
|
+
for (const job of state.jobs) {
|
|
315
|
+
state.runtime.signal?.throwIfAborted();
|
|
316
|
+
if (state.issues.length >= state.runtime.maxIssues ||
|
|
317
|
+
state.runtime.budget.exhausted)
|
|
318
|
+
break;
|
|
319
|
+
if (job.kind === "union") {
|
|
320
|
+
let success = false;
|
|
321
|
+
for (const candidate of job.candidates) {
|
|
322
|
+
yield* execute(candidate, allowCustom);
|
|
323
|
+
if (!candidate.issues.length) {
|
|
324
|
+
success = true;
|
|
325
|
+
break;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
if (!success)
|
|
329
|
+
issue(state, job.context.path, job.rule.code, job.rule.params, job.rule.options);
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
let failed = false;
|
|
333
|
+
// Built-in constraints precede user callbacks, avoiding I/O for invalid fields.
|
|
334
|
+
for (const rule of job.checks) {
|
|
335
|
+
if (typeof rule === "function" &&
|
|
336
|
+
(!allowCustom ||
|
|
337
|
+
failed ||
|
|
338
|
+
state.issues.some((issue) => issue.path.length >= job.context.path.length &&
|
|
339
|
+
job.context.path.every((part, index) => issue.path[index] === part))))
|
|
340
|
+
continue;
|
|
341
|
+
let result = typeof rule === "function"
|
|
342
|
+
? rule(job.context)
|
|
343
|
+
: rule.test(job.context.value, job.context);
|
|
344
|
+
if (thenable(result))
|
|
345
|
+
result = yield result;
|
|
346
|
+
state.runtime.signal?.throwIfAborted();
|
|
347
|
+
if (!consume(state, result, job.context, typeof rule === "function" ? undefined : rule)) {
|
|
348
|
+
failed = true;
|
|
349
|
+
if ((state.runtime.config.cascade ?? "stop") === "stop")
|
|
350
|
+
break;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
function initialize(input, shape, config, options) {
|
|
356
|
+
options.signal?.throwIfAborted();
|
|
357
|
+
const runtime = {
|
|
358
|
+
config,
|
|
359
|
+
locale: options.locale ?? config.getLocale?.() ?? config.defaultLocale ?? "en",
|
|
360
|
+
root: input,
|
|
361
|
+
context: options.context,
|
|
362
|
+
signal: options.signal,
|
|
363
|
+
maxDepth: config.maxDepth ?? 64,
|
|
364
|
+
maxNodes: config.maxNodes ?? 10000,
|
|
365
|
+
maxIssues: config.maxIssues ?? 100,
|
|
366
|
+
budget: { nodes: 0, exhausted: false },
|
|
367
|
+
active: new WeakSet(),
|
|
368
|
+
};
|
|
369
|
+
const state = { runtime, issues: [], jobs: [], structuralValid: true };
|
|
370
|
+
prepare(state, input, input, [], [
|
|
371
|
+
{
|
|
372
|
+
kind: "object",
|
|
373
|
+
code: "type.object",
|
|
374
|
+
structural: true,
|
|
375
|
+
options: {},
|
|
376
|
+
params: {},
|
|
377
|
+
shape,
|
|
378
|
+
},
|
|
379
|
+
], 0);
|
|
380
|
+
return state;
|
|
381
|
+
}
|
|
382
|
+
export function validateSync(input, shape, config, options) {
|
|
383
|
+
const state = initialize(input, shape, config, options);
|
|
384
|
+
const iterator = execute(state, state.structuralValid);
|
|
385
|
+
const step = iterator.next();
|
|
386
|
+
if (!step.done) {
|
|
387
|
+
// Observe rejection even on API misuse; the user's already-started I/O cannot be undone.
|
|
388
|
+
void Promise.resolve(step.value).catch(() => { });
|
|
389
|
+
iterator.return();
|
|
390
|
+
throw new TypeError("Async rule encountered during validate(). Use validateAsync().");
|
|
391
|
+
}
|
|
392
|
+
return new ValidationResult(input, state.issues);
|
|
393
|
+
}
|
|
394
|
+
export async function validateAsync(input, shape, config, options) {
|
|
395
|
+
const state = initialize(input, shape, config, options);
|
|
396
|
+
const iterator = execute(state, state.structuralValid);
|
|
397
|
+
let step = iterator.next();
|
|
398
|
+
try {
|
|
399
|
+
while (!step.done)
|
|
400
|
+
step = iterator.next(await step.value);
|
|
401
|
+
}
|
|
402
|
+
finally {
|
|
403
|
+
iterator.return();
|
|
404
|
+
}
|
|
405
|
+
return new ValidationResult(input, state.issues);
|
|
406
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ValidationErrors, ValidationIssue } from "./Types.js";
|
|
2
|
+
/** A snapshot of validation diagnostics. Failed results never retain `data`. */
|
|
3
|
+
export declare class ValidationResult<T> {
|
|
4
|
+
#private;
|
|
5
|
+
readonly isValid: boolean;
|
|
6
|
+
readonly issues: readonly ValidationIssue[];
|
|
7
|
+
readonly data: T | undefined;
|
|
8
|
+
/** No issues means success (including when data is undefined); otherwise data is discarded. */
|
|
9
|
+
constructor(data: T | undefined, issues: readonly ValidationIssue[]);
|
|
10
|
+
get errors(): ValidationErrors<T>;
|
|
11
|
+
get formErrors(): readonly string[];
|
|
12
|
+
/**
|
|
13
|
+
* Flat RFC 6901 JSON Pointer keys: ~ becomes ~0, / becomes ~1.
|
|
14
|
+
* Root issues are excluded (see formErrors). String "0" and numeric 0
|
|
15
|
+
* share a pointer; the typed tree preserves their separate namespaces.
|
|
16
|
+
*/
|
|
17
|
+
toFieldErrors(): Readonly<Record<string, readonly string[]>>;
|
|
18
|
+
}
|
|
19
|
+
/** An Error wrapper whose JSON representation excludes validated input and stack. */
|
|
20
|
+
export declare class ValidationError<T = unknown> extends Error {
|
|
21
|
+
readonly result: ValidationResult<T>;
|
|
22
|
+
constructor(result: ValidationResult<T>, message?: string);
|
|
23
|
+
get issues(): readonly ValidationIssue[];
|
|
24
|
+
get errors(): ValidationErrors<T>;
|
|
25
|
+
toJSON(): {
|
|
26
|
+
name: string;
|
|
27
|
+
message: string;
|
|
28
|
+
issues: readonly ValidationIssue[];
|
|
29
|
+
errors: ValidationErrors<T>;
|
|
30
|
+
};
|
|
31
|
+
}
|