ivl 0.4.5 → 0.5.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/README.md CHANGED
@@ -4,7 +4,7 @@ Main focus is on speed and flexibility of the validation rules.
4
4
 
5
5
  By default `getValueErrors` and `getSchemaErrors` automatically detects and chooses the most performant checking method for your rule set.
6
6
 
7
- If your rule set and/or schema are very large or complex, you may want to directly use `getValueErrorsSync`/`getSchemaErrorsSync` or `getValueErrorsAsync`/`getSchemaErrorsAsync` for improved performance on synchronous and asynchronous rule sets respectively.
7
+ If you validate against the same rule set or schema repeatedly, compile it once with `compileRules`/`compileSchema` (see [Compiled validators](#compiled-validators)) - that does the sync/async detection up front and is the fastest option.
8
8
 
9
9
  Use synchronous versions of the functions for better performance if you don't need to support asynchronous checks on your inputs.
10
10
 
@@ -51,6 +51,80 @@ const inDatabase: RULE<string, [ctx: { db: Database }]> = async (value, ctx) =>
51
51
  > "may be sync or async", so the return type becomes `string[] | Promise<string[]>`. Prefer
52
52
  > `satisfies RULES` / `satisfies SCHEMA`, which validates the shape without losing the inferred types.
53
53
 
54
+ # Compiled validators
55
+ `compileRules` and `compileSchema` turn a rule set or schema into a reusable validator. They return
56
+ exactly what `getValueErrors`/`getSchemaErrors` would, with the same sync/async type inference, but the
57
+ work those functions repeat on every call - finding the rule keys and checking for async rules - is
58
+ done once up front:
59
+
60
+ ```typescript
61
+ import { compileSchema } from 'ivl';
62
+ import { EMAIL_RULES, STRONG_PASSWORD_RULES } from 'ivl/patterns';
63
+
64
+ // Compile once, at module level
65
+ const validateRegistration = compileSchema({
66
+ email: EMAIL_RULES,
67
+ password: STRONG_PASSWORD_RULES,
68
+ }, { strict: true });
69
+
70
+ // ...then call it per request
71
+ const errors = validateRegistration(body); // { email: string[], password: string[] }
72
+ ```
73
+
74
+ The rule sets and options are captured when compiling, so changing them afterwards has no effect on an
75
+ existing validator.
76
+
77
+ # Options
78
+ `getSchemaErrors`, `getSchemaErrorsSync`, `getSchemaErrorsAsync` and `compileSchema` take an options object;
79
+ `compileRules` takes `break_early`. Both options are off by default.
80
+
81
+ - **`strict`** - also report keys of the object that the schema doesn't have, each as `['Key not allowed']`.
82
+ - **`break_early`** - report only the first failing rule of each rule set instead of all of them. Rules
83
+ that aren't `async` run first, in order, and stop at the first failure; `async` rules only run if all of
84
+ those passed. So a cheap check that fails skips the expensive ones, like a long regex or a database lookup:
85
+
86
+ ```typescript
87
+ const validateEmail = compileRules({
88
+ ...EMAIL_RULES, // cheap checks first
89
+ "Email already registered": async (i: unknown) => !(await emailExists(i)),
90
+ }, { break_early: true });
91
+
92
+ await validateEmail('not an email'); // ['Must be a valid email address'] - the lookup never ran
93
+ ```
94
+
95
+ Every field of a schema is still validated with `break_early`; each just reports at most one error. Leave
96
+ it off when you want to show users every problem at once, e.g. all unmet password requirements.
97
+
98
+ # Ready-made rule sets
99
+ `ivl/patterns` exports rule sets for common formats, ready to drop into a schema:
100
+
101
+ ```typescript
102
+ import { getSchemaErrors } from 'ivl';
103
+ import { EMAIL_RULES, STRONG_PASSWORD_RULES, IPV4_RULES, IPV6_RULES } from 'ivl/patterns';
104
+
105
+ const errors = getSchemaErrors(input, {
106
+ email: EMAIL_RULES,
107
+ password: STRONG_PASSWORD_RULES,
108
+ ip: [IPV4_RULES, IPV6_RULES], // passes if either rule set passes
109
+ });
110
+ ```
111
+
112
+ Available: `EMAIL_RULES`, `URL_RULES`, `UUID_RULES`, `UUID_V4_RULES`, `IPV4_RULES`, `IPV6_RULES`,
113
+ `MAC_ADDRESS_RULES`, `HEX_COLOR_RULES`, `SLUG_RULES`, `E164_PHONE_RULES`, `SEMVER_RULES`, `BASE64_RULES`,
114
+ `JWT_RULES`, `ISO_8601_DATE_RULES`, `ISO_8601_DATETIME_RULES`, `ISO_8601_TIME_RULES`, `PASSWORD_RULES`
115
+ and `STRONG_PASSWORD_RULES`.
116
+
117
+ All of them are synchronous and frozen. To customise one, spread it into a new object:
118
+
119
+ ```typescript
120
+ const COMPANY_EMAIL_RULES = {
121
+ ...EMAIL_RULES,
122
+ "Must be a company address": (i: unknown) => typeof i === 'string' && i.endsWith('@example.com'),
123
+ };
124
+ ```
125
+
126
+ The date rule sets also check the calendar, so `2024-02-30` fails even though it matches the pattern.
127
+
54
128
  # Installing
55
129
  ```typescript
56
130
  bun add ivl // bun.js
@@ -209,4 +283,231 @@ app.put('/register/:invitation_code',
209
283
  (c)=> c.text("Success"));
210
284
 
211
285
  export default app;
212
- ```
286
+ ```
287
+
288
+ <!-- benchmark:start -->
289
+ # Benchmarks
290
+
291
+ Generated by CI on a GitHub-hosted runner, so absolute numbers vary between runs.
292
+ Compare rows within a table rather than across runs. Read the caveats at the end before
293
+ drawing conclusions: the libraries do different amounts of work in some scenarios.
294
+
295
+ ## Bun (JavaScriptCore)
296
+
297
+ - **clk**: ~4.30 GHz
298
+ - **cpu**: AMD EPYC 9V45 96-Core Processor
299
+ - **runtime**: bun 1.4.2 (x64-linux)
300
+
301
+ | • ivl execution paths | avg | min | p75 | p99 | max |
302
+ | ------------------ | ---------------- | ----------- | ----------- | ----------- | ----------- |
303
+ | pure regex helper | ` 13.52 ns/iter` | ` 12.84 ns` | ` 13.64 ns` | ` 16.26 ns` | ` 35.29 ns` |
304
+ | async regex helper | ` 82.30 ns/iter` | ` 73.32 ns` | ` 80.07 ns` | `156.46 ns` | `265.68 ns` |
305
+ | smart regex helper | ` 26.02 ns/iter` | ` 22.43 ns` | ` 24.92 ns` | ` 82.75 ns` | `180.55 ns` |
306
+ | sync regex helper | ` 24.25 ns/iter` | ` 19.55 ns` | ` 23.57 ns` | ` 75.54 ns` | `210.71 ns` |
307
+
308
+ | • single regex rule, report all errors (valid input) | avg | min | p75 | p99 | max |
309
+ | ------------ | ---------------- | ----------- | ----------- | ----------- | ----------- |
310
+ | ivl | ` 34.12 ns/iter` | ` 28.45 ns` | ` 34.15 ns` | ` 90.83 ns` | `133.14 ns` |
311
+ | ivl compiled | ` 38.29 ns/iter` | ` 34.44 ns` | ` 37.92 ns` | ` 95.95 ns` | `122.56 ns` |
312
+ | zod | ` 61.64 ns/iter` | ` 50.50 ns` | ` 54.90 ns` | `197.04 ns` | `418.33 ns` |
313
+ | valibot | ` 36.60 ns/iter` | ` 33.16 ns` | ` 36.12 ns` | ` 69.51 ns` | `129.53 ns` |
314
+ | yup | `705.02 ns/iter` | `618.97 ns` | `732.24 ns` | `846.00 ns` | ` 1.25 µs` |
315
+
316
+ | • single regex rule, report all errors (invalid input) | avg | min | p75 | p99 | max |
317
+ | ------------ | ---------------- | ----------- | ----------- | ----------- | ----------- |
318
+ | ivl | ` 21.34 ns/iter` | ` 18.91 ns` | ` 20.92 ns` | ` 80.84 ns` | `109.01 ns` |
319
+ | ivl compiled | ` 29.84 ns/iter` | ` 26.05 ns` | ` 28.86 ns` | ` 97.03 ns` | `148.36 ns` |
320
+ | zod | ` 1.11 µs/iter` | ` 1.03 µs` | ` 1.11 µs` | ` 1.36 µs` | ` 1.45 µs` |
321
+ | valibot | ` 59.58 ns/iter` | ` 50.78 ns` | ` 57.61 ns` | `127.88 ns` | `184.43 ns` |
322
+ | yup | ` 5.00 µs/iter` | ` 4.78 µs` | ` 5.05 µs` | ` 5.35 µs` | ` 5.42 µs` |
323
+
324
+ | • single regex rule, pass/fail (valid input) | avg | min | p75 | p99 | max |
325
+ | ------------ | ---------------- | ----------- | ----------- | ----------- | ----------- |
326
+ | ivl compiled | ` 26.07 ns/iter` | ` 23.87 ns` | ` 25.92 ns` | ` 40.28 ns` | `113.68 ns` |
327
+ | zod | ` 58.58 ns/iter` | ` 50.32 ns` | ` 56.24 ns` | `133.43 ns` | `150.19 ns` |
328
+ | valibot | ` 31.14 ns/iter` | ` 27.63 ns` | ` 29.65 ns` | ` 90.41 ns` | `151.00 ns` |
329
+ | yup | `722.17 ns/iter` | `639.21 ns` | `770.56 ns` | `816.36 ns` | `901.42 ns` |
330
+
331
+ | • single regex rule, pass/fail (invalid input) | avg | min | p75 | p99 | max |
332
+ | ------------ | ---------------- | ----------- | ----------- | ----------- | ----------- |
333
+ | ivl compiled | ` 16.51 ns/iter` | ` 14.88 ns` | ` 16.05 ns` | ` 28.92 ns` | ` 97.51 ns` |
334
+ | zod | ` 1.05 µs/iter` | `992.50 ns` | ` 1.06 µs` | ` 1.19 µs` | ` 1.21 µs` |
335
+ | valibot | ` 56.66 ns/iter` | ` 46.98 ns` | ` 53.78 ns` | `136.47 ns` | `341.59 ns` |
336
+ | yup | ` 3.40 µs/iter` | ` 3.26 µs` | ` 3.46 µs` | ` 3.59 µs` | ` 3.61 µs` |
337
+
338
+ | • built-in email validator (regexes differ) (valid input) | avg | min | p75 | p99 | max |
339
+ | ------------ | ---------------- | ----------- | ----------- | ----------- | ----------- |
340
+ | ivl | ` 51.80 ns/iter` | ` 47.35 ns` | ` 50.80 ns` | `127.91 ns` | `194.98 ns` |
341
+ | ivl compiled | ` 41.66 ns/iter` | ` 37.96 ns` | ` 41.18 ns` | `103.09 ns` | `136.03 ns` |
342
+ | zod | ` 70.53 ns/iter` | ` 63.73 ns` | ` 69.40 ns` | `141.86 ns` | `294.39 ns` |
343
+ | valibot | ` 43.41 ns/iter` | ` 40.13 ns` | ` 43.64 ns` | ` 72.32 ns` | `143.37 ns` |
344
+ | yup | `832.47 ns/iter` | `751.96 ns` | `880.36 ns` | `961.46 ns` | `981.26 ns` |
345
+
346
+ | • built-in email validator (regexes differ) (invalid input) | avg | min | p75 | p99 | max |
347
+ | ------------ | ---------------- | ----------- | ----------- | ----------- | ----------- |
348
+ | ivl | ` 45.57 ns/iter` | ` 40.58 ns` | ` 44.95 ns` | `124.01 ns` | `155.78 ns` |
349
+ | ivl compiled | ` 36.58 ns/iter` | ` 31.31 ns` | ` 33.96 ns` | `111.54 ns` | `215.61 ns` |
350
+ | zod | ` 1.32 µs/iter` | ` 1.18 µs` | ` 1.29 µs` | ` 2.22 µs` | ` 2.77 µs` |
351
+ | valibot | ` 70.15 ns/iter` | ` 62.03 ns` | ` 68.73 ns` | `137.41 ns` | `170.45 ns` |
352
+ | yup | ` 4.30 µs/iter` | ` 4.04 µs` | ` 4.41 µs` | ` 4.60 µs` | ` 4.65 µs` |
353
+
354
+ | • object schema, report all errors (valid input) | avg | min | p75 | p99 | max |
355
+ | ------------ | ---------------- | ----------- | ----------- | ----------- | ----------- |
356
+ | ivl | `327.50 ns/iter` | `299.90 ns` | `320.39 ns` | `492.24 ns` | `668.48 ns` |
357
+ | ivl compiled | `203.15 ns/iter` | `185.28 ns` | `197.36 ns` | `349.93 ns` | `545.51 ns` |
358
+ | zod | `348.37 ns/iter` | `309.78 ns` | `339.21 ns` | `617.57 ns` | `879.07 ns` |
359
+ | valibot | `321.00 ns/iter` | `306.99 ns` | `322.20 ns` | `425.95 ns` | `498.46 ns` |
360
+ | yup | ` 5.37 µs/iter` | ` 5.17 µs` | ` 5.49 µs` | ` 5.57 µs` | ` 5.62 µs` |
361
+
362
+ | • object schema, report all errors (invalid input) | avg | min | p75 | p99 | max |
363
+ | ------------ | ---------------- | ----------- | ----------- | ----------- | ----------- |
364
+ | ivl | `351.10 ns/iter` | `327.31 ns` | `353.17 ns` | `441.63 ns` | `531.51 ns` |
365
+ | ivl compiled | `198.54 ns/iter` | `184.70 ns` | `195.92 ns` | `283.96 ns` | `316.35 ns` |
366
+ | zod | ` 2.12 µs/iter` | ` 1.87 µs` | ` 2.28 µs` | ` 2.59 µs` | ` 2.60 µs` |
367
+ | valibot | `545.94 ns/iter` | `497.96 ns` | `555.42 ns` | `665.79 ns` | ` 1.13 µs` |
368
+ | yup | ` 17.34 µs/iter` | ` 8.43 µs` | ` 16.94 µs` | ` 47.33 µs` | `636.65 µs` |
369
+
370
+ | • object schema, pass/fail (valid input) | avg | min | p75 | p99 | max |
371
+ | ------------ | ---------------- | ----------- | ----------- | ----------- | ----------- |
372
+ | ivl compiled | `212.72 ns/iter` | `198.92 ns` | `210.15 ns` | `296.15 ns` | `305.23 ns` |
373
+ | zod | `379.33 ns/iter` | `330.83 ns` | `355.65 ns` | `790.78 ns` | `972.48 ns` |
374
+ | valibot | `335.73 ns/iter` | `325.24 ns` | `335.79 ns` | `419.19 ns` | `429.04 ns` |
375
+ | yup | ` 5.53 µs/iter` | ` 5.33 µs` | ` 5.60 µs` | ` 5.83 µs` | ` 5.83 µs` |
376
+
377
+ | • object schema, pass/fail (invalid input) | avg | min | p75 | p99 | max |
378
+ | ------------ | ---------------- | ----------- | ----------- | ----------- | ----------- |
379
+ | ivl compiled | `165.02 ns/iter` | `146.93 ns` | `154.26 ns` | `372.14 ns` | `539.38 ns` |
380
+ | zod | ` 2.05 µs/iter` | ` 1.93 µs` | ` 2.09 µs` | ` 2.36 µs` | ` 2.40 µs` |
381
+ | valibot | `219.92 ns/iter` | `190.17 ns` | `206.55 ns` | `470.12 ns` | `573.75 ns` |
382
+ | yup | ` 6.74 µs/iter` | ` 6.35 µs` | ` 6.82 µs` | ` 7.05 µs` | ` 7.32 µs` |
383
+
384
+ | • object schema with an async rule, report all errors (valid input) | avg | min | p75 | p99 | max |
385
+ | ------------ | ---------------- | ----------- | ----------- | ----------- | ----------- |
386
+ | ivl | ` 1.49 µs/iter` | `991.00 ns` | ` 1.55 µs` | ` 3.67 µs` | `407.89 µs` |
387
+ | ivl compiled | `972.10 ns/iter` | `892.33 ns` | ` 1.01 µs` | ` 1.22 µs` | ` 1.50 µs` |
388
+ | zod | ` 2.04 µs/iter` | ` 1.45 µs` | ` 2.13 µs` | ` 4.55 µs` | `421.99 µs` |
389
+ | valibot | ` 2.06 µs/iter` | ` 1.42 µs` | ` 2.17 µs` | ` 4.55 µs` | `413.39 µs` |
390
+ | yup | ` 6.34 µs/iter` | ` 6.05 µs` | ` 6.48 µs` | ` 6.68 µs` | ` 6.72 µs` |
391
+
392
+ | • object schema with an async rule, report all errors (invalid input) | avg | min | p75 | p99 | max |
393
+ | ------------ | ---------------- | ----------- | ----------- | ----------- | ----------- |
394
+ | ivl | ` 1.15 µs/iter` | ` 1.08 µs` | ` 1.20 µs` | ` 1.25 µs` | ` 1.45 µs` |
395
+ | ivl compiled | `988.52 ns/iter` | `926.70 ns` | ` 1.04 µs` | ` 1.09 µs` | ` 1.09 µs` |
396
+ | zod | ` 3.25 µs/iter` | ` 3.03 µs` | ` 3.34 µs` | ` 3.50 µs` | ` 3.51 µs` |
397
+ | valibot | ` 1.76 µs/iter` | ` 1.60 µs` | ` 1.81 µs` | ` 1.96 µs` | ` 1.99 µs` |
398
+ | yup | ` 12.97 µs/iter` | ` 12.54 µs` | ` 13.12 µs` | ` 13.27 µs` | ` 13.37 µs` |
399
+
400
+ ## Node (V8)
401
+
402
+ - **clk**: ~4.25 GHz
403
+ - **cpu**: AMD EPYC 9V45 96-Core Processor
404
+ - **runtime**: node 24.21.0 (x64-linux)
405
+
406
+ | • ivl execution paths | avg | min | p75 | p99 | max |
407
+ | ------------------ | ---------------- | ----------- | ----------- | ----------- | ----------- |
408
+ | pure regex helper | ` 24.51 ns/iter` | ` 20.15 ns` | ` 23.01 ns` | ` 59.01 ns` | `141.77 ns` |
409
+ | async regex helper | `115.24 ns/iter` | `103.07 ns` | `116.37 ns` | `244.47 ns` | `565.06 ns` |
410
+ | smart regex helper | ` 57.60 ns/iter` | ` 53.07 ns` | ` 56.42 ns` | `113.03 ns` | `173.16 ns` |
411
+ | sync regex helper | ` 37.14 ns/iter` | ` 34.47 ns` | ` 37.31 ns` | ` 54.91 ns` | ` 88.52 ns` |
412
+
413
+ | • single regex rule, report all errors (valid input) | avg | min | p75 | p99 | max |
414
+ | ------------ | ---------------- | ----------- | ----------- | ----------- | ----------- |
415
+ | ivl | ` 62.56 ns/iter` | ` 57.85 ns` | ` 61.46 ns` | `121.22 ns` | `259.12 ns` |
416
+ | ivl compiled | ` 33.48 ns/iter` | ` 30.43 ns` | ` 33.13 ns` | ` 53.45 ns` | `104.86 ns` |
417
+ | zod | ` 83.41 ns/iter` | ` 68.60 ns` | `103.02 ns` | `216.69 ns` | `618.68 ns` |
418
+ | valibot | ` 43.02 ns/iter` | ` 40.24 ns` | ` 43.45 ns` | ` 60.00 ns` | `136.12 ns` |
419
+ | yup | `698.06 ns/iter` | `638.67 ns` | `710.79 ns` | ` 1.00 µs` | ` 1.24 µs` |
420
+
421
+ | • single regex rule, report all errors (invalid input) | avg | min | p75 | p99 | max |
422
+ | ------------ | ---------------- | ----------- | ----------- | ----------- | ----------- |
423
+ | ivl | ` 49.52 ns/iter` | ` 46.45 ns` | ` 49.37 ns` | ` 68.94 ns` | ` 95.38 ns` |
424
+ | ivl compiled | ` 23.19 ns/iter` | ` 21.80 ns` | ` 22.96 ns` | ` 38.54 ns` | `113.55 ns` |
425
+ | zod | ` 1.53 µs/iter` | ` 1.28 µs` | ` 1.36 µs` | ` 3.73 µs` | ` 3.76 µs` |
426
+ | valibot | `184.48 ns/iter` | ` 71.54 ns` | ` 78.01 ns` | ` 4.15 µs` | ` 4.90 µs` |
427
+ | yup | ` 23.65 µs/iter` | ` 22.82 µs` | ` 23.81 µs` | ` 23.96 µs` | ` 25.73 µs` |
428
+
429
+ | • single regex rule, pass/fail (valid input) | avg | min | p75 | p99 | max |
430
+ | ------------ | ---------------- | ----------- | ----------- | ----------- | ----------- |
431
+ | ivl compiled | ` 32.47 ns/iter` | ` 29.39 ns` | ` 33.25 ns` | ` 38.61 ns` | ` 83.76 ns` |
432
+ | zod | ` 72.01 ns/iter` | ` 66.45 ns` | ` 70.41 ns` | `122.20 ns` | `604.70 ns` |
433
+ | valibot | ` 40.36 ns/iter` | ` 38.46 ns` | ` 40.81 ns` | ` 47.20 ns` | `100.89 ns` |
434
+ | yup | `696.18 ns/iter` | `668.23 ns` | `705.27 ns` | `760.27 ns` | `810.10 ns` |
435
+
436
+ | • single regex rule, pass/fail (invalid input) | avg | min | p75 | p99 | max |
437
+ | ------------ | ---------------- | ----------- | ----------- | ----------- | ----------- |
438
+ | ivl compiled | ` 22.68 ns/iter` | ` 21.30 ns` | ` 22.63 ns` | ` 36.02 ns` | `127.04 ns` |
439
+ | zod | ` 1.50 µs/iter` | ` 1.30 µs` | ` 1.33 µs` | ` 3.32 µs` | ` 3.41 µs` |
440
+ | valibot | `188.62 ns/iter` | ` 71.83 ns` | ` 76.80 ns` | ` 4.18 µs` | ` 5.15 µs` |
441
+ | yup | ` 13.65 µs/iter` | ` 13.58 µs` | ` 13.70 µs` | ` 13.70 µs` | ` 13.72 µs` |
442
+
443
+ | • built-in email validator (regexes differ) (valid input) | avg | min | p75 | p99 | max |
444
+ | ------------ | ---------------- | ----------- | ----------- | ----------- | ----------- |
445
+ | ivl | `118.83 ns/iter` | `107.39 ns` | `114.09 ns` | `295.92 ns` | `390.04 ns` |
446
+ | ivl compiled | ` 43.08 ns/iter` | ` 40.58 ns` | ` 43.35 ns` | ` 68.22 ns` | `145.20 ns` |
447
+ | zod | `117.66 ns/iter` | `100.59 ns` | `106.00 ns` | `346.64 ns` | `654.21 ns` |
448
+ | valibot | ` 46.13 ns/iter` | ` 43.45 ns` | ` 46.57 ns` | ` 67.45 ns` | `142.49 ns` |
449
+ | yup | `675.60 ns/iter` | `639.19 ns` | `683.40 ns` | `785.82 ns` | ` 1.21 µs` |
450
+
451
+ | • built-in email validator (regexes differ) (invalid input) | avg | min | p75 | p99 | max |
452
+ | ------------ | ---------------- | ----------- | ----------- | ----------- | ----------- |
453
+ | ivl | `103.16 ns/iter` | ` 97.03 ns` | `104.03 ns` | `132.85 ns` | `181.27 ns` |
454
+ | ivl compiled | ` 34.96 ns/iter` | ` 31.56 ns` | ` 34.17 ns` | ` 73.88 ns` | `179.74 ns` |
455
+ | zod | ` 1.61 µs/iter` | ` 1.37 µs` | ` 1.45 µs` | ` 3.51 µs` | ` 3.71 µs` |
456
+ | valibot | `170.76 ns/iter` | ` 71.09 ns` | ` 76.18 ns` | ` 3.93 µs` | ` 5.23 µs` |
457
+ | yup | ` 22.65 µs/iter` | ` 21.50 µs` | ` 22.21 µs` | ` 24.04 µs` | ` 28.84 µs` |
458
+
459
+ | • object schema, report all errors (valid input) | avg | min | p75 | p99 | max |
460
+ | ------------ | ---------------- | ----------- | ----------- | ----------- | ----------- |
461
+ | ivl | `725.21 ns/iter` | `700.77 ns` | `724.11 ns` | `830.31 ns` | ` 1.17 µs` |
462
+ | ivl compiled | `261.26 ns/iter` | `251.23 ns` | `260.07 ns` | `318.92 ns` | `555.87 ns` |
463
+ | zod | `748.41 ns/iter` | `641.54 ns` | `681.16 ns` | ` 1.57 µs` | ` 1.62 µs` |
464
+ | valibot | `423.50 ns/iter` | `404.05 ns` | `417.93 ns` | `734.86 ns` | `893.94 ns` |
465
+ | yup | ` 4.93 µs/iter` | ` 4.82 µs` | ` 4.93 µs` | ` 5.27 µs` | ` 5.52 µs` |
466
+
467
+ | • object schema, report all errors (invalid input) | avg | min | p75 | p99 | max |
468
+ | ------------ | ---------------- | ----------- | ----------- | ----------- | ----------- |
469
+ | ivl | `695.66 ns/iter` | `681.21 ns` | `694.45 ns` | `751.23 ns` | `918.42 ns` |
470
+ | ivl compiled | `256.14 ns/iter` | `246.15 ns` | `258.42 ns` | `291.06 ns` | `329.57 ns` |
471
+ | zod | ` 3.57 µs/iter` | ` 1.95 µs` | ` 3.13 µs` | ` 8.54 µs` | ` 8.68 ms` |
472
+ | valibot | ` 1.34 µs/iter` | `725.76 ns` | `769.83 ns` | ` 6.39 µs` | ` 6.67 µs` |
473
+ | yup | ` 56.17 µs/iter` | ` 26.44 µs` | ` 72.63 µs` | `132.72 µs` | `363.54 µs` |
474
+
475
+ | • object schema, pass/fail (valid input) | avg | min | p75 | p99 | max |
476
+ | ------------ | ---------------- | ----------- | ----------- | ----------- | ----------- |
477
+ | ivl compiled | `290.00 ns/iter` | `280.18 ns` | `289.45 ns` | `334.74 ns` | `694.99 ns` |
478
+ | zod | `743.37 ns/iter` | `654.72 ns` | `701.12 ns` | ` 1.25 µs` | ` 1.27 µs` |
479
+ | valibot | `402.39 ns/iter` | `391.71 ns` | `402.53 ns` | `444.24 ns` | `753.31 ns` |
480
+ | yup | ` 4.98 µs/iter` | ` 4.90 µs` | ` 5.00 µs` | ` 5.09 µs` | ` 5.24 µs` |
481
+
482
+ | • object schema, pass/fail (invalid input) | avg | min | p75 | p99 | max |
483
+ | ------------ | ---------------- | ----------- | ----------- | ----------- | ----------- |
484
+ | ivl compiled | `220.63 ns/iter` | `212.62 ns` | `219.76 ns` | `252.80 ns` | `478.33 ns` |
485
+ | zod | ` 3.33 µs/iter` | ` 2.62 µs` | ` 4.45 µs` | ` 5.82 µs` | ` 6.13 µs` |
486
+ | valibot | `472.12 ns/iter` | `286.54 ns` | `300.30 ns` | ` 5.70 µs` | ` 5.85 µs` |
487
+ | yup | ` 15.55 µs/iter` | ` 14.94 µs` | ` 15.32 µs` | ` 15.95 µs` | ` 19.16 µs` |
488
+
489
+ | • object schema with an async rule, report all errors (valid input) | avg | min | p75 | p99 | max |
490
+ | ------------ | ---------------- | ----------- | ----------- | ----------- | ----------- |
491
+ | ivl | ` 1.86 µs/iter` | ` 1.37 µs` | ` 1.96 µs` | ` 4.55 µs` | ` 4.88 µs` |
492
+ | ivl compiled | ` 2.08 µs/iter` | ` 1.20 µs` | ` 2.54 µs` | ` 4.50 µs` | ` 4.60 µs` |
493
+ | zod | ` 1.83 µs/iter` | ` 1.71 µs` | ` 1.92 µs` | ` 2.10 µs` | ` 2.22 µs` |
494
+ | valibot | ` 2.36 µs/iter` | ` 1.83 µs` | ` 2.69 µs` | ` 2.92 µs` | ` 3.12 µs` |
495
+ | yup | ` 5.48 µs/iter` | ` 5.35 µs` | ` 5.48 µs` | ` 5.72 µs` | ` 6.15 µs` |
496
+
497
+ | • object schema with an async rule, report all errors (invalid input) | avg | min | p75 | p99 | max |
498
+ | ------------ | ---------------- | ----------- | ----------- | ----------- | ----------- |
499
+ | ivl | ` 1.85 µs/iter` | ` 1.37 µs` | ` 2.28 µs` | ` 3.41 µs` | ` 3.68 µs` |
500
+ | ivl compiled | ` 1.55 µs/iter` | ` 1.14 µs` | ` 2.14 µs` | ` 2.27 µs` | ` 2.53 µs` |
501
+ | zod | ` 4.05 µs/iter` | ` 3.42 µs` | ` 4.66 µs` | ` 4.77 µs` | ` 4.80 µs` |
502
+ | valibot | ` 3.10 µs/iter` | ` 2.04 µs` | ` 3.62 µs` | ` 4.11 µs` | ` 4.99 µs` |
503
+ | yup | ` 40.79 µs/iter` | ` 16.19 µs` | ` 62.34 µs` | ` 84.30 µs` | `705.87 µs` |
504
+
505
+ **Caveats**
506
+
507
+
508
+ - Error reports differ in detail: ivl returns the names of the failing rules, zod and valibot build an issue object per failure and copy the parsed value, and yup throws a ValidationError. The "report all errors" results on invalid input largely measure that cost; the "pass/fail" scenarios compare validation alone.
509
+ - After a failed type check, zod and valibot skip the rest of that field, while ivl still runs every rule unless break_early is set.
510
+ - In "pass/fail", valibot (v.is) and yup (isValidSync) stop at the first failure in the whole object; ivl (break_early) still checks every field, stopping within each; zod has no early-exit mode, so it runs safeParse and reads .success.
511
+ - The "built-in email" scenario uses each library's own email regex, so it compares what you get out of the box rather than equal work; ivl's regex is simpler and looser than zod's.
512
+ - Each benchmark cycles through 8 valid or 8 invalid inputs. The async rule resolves immediately, so the async scenario measures promise overhead, not I/O.
513
+ <!-- benchmark:end -->
package/lib/core.d.ts CHANGED
@@ -1,16 +1,35 @@
1
- import type { RULES, RULES_SYNC, SCHEMA, SCHEMA_SYNC, SCHEMA_OPTIONS, CHECKABLE_OBJECT, CHECKED_SCHEMA, CHECKED_SCHEMA_SYNC, RULES_KIND, SCHEMA_KIND, MAYBE_ASYNC } from './types';
2
1
  /**
3
- * Detect if a function is async or not
2
+ * Core validation functions: run rule sets against single values (`getValueErrors*`), run
3
+ * schemas against objects (`getSchemaErrors*`), and compile either into reusable validators
4
+ * (`compileRules`, `compileSchema`).
4
5
  *
5
- * @param {unknown} fn the function to inspect
6
- * @returns {boolean} true if `fn` is declared with the `async` keyword, otherwise false
6
+ * Shared semantics:
7
+ * - a rule passes when it returns a truthy value and fails when it returns a falsy value,
8
+ * throws, or (on an async path) rejects - a failing rule never throws out of these functions
9
+ * - errors are the keys of the failing rules, in the rule set's key order
10
+ * - a rule is async only if it is declared with the `async` keyword; the plain `getValueErrors`
11
+ * / `getSchemaErrors` / `compile*` functions use that to pick the sync or async path
12
+ * - any extra `overload` arguments are passed through to every rule after the value
13
+ *
14
+ * @module
15
+ */
16
+ import type { RULES, RULES_SYNC, SCHEMA, SCHEMA_SYNC, SCHEMA_OPTIONS, RULES_OPTIONS, CHECKABLE_OBJECT, CHECKED_SCHEMA, CHECKED_SCHEMA_SYNC, RULES_KIND, SCHEMA_KIND, MAYBE_ASYNC, ANY_RULES, COMPILED_RULES, COMPILED_SCHEMA } from './types';
17
+ /**
18
+ * Detect whether a value is a function declared with the `async` keyword.
19
+ *
20
+ * Only the declaration is inspected: a plain function that returns a promise is not async
21
+ * by this definition. Works across realms, since it compares the constructor's name.
22
+ *
23
+ * @param {unknown} fn the value to inspect
24
+ * @returns {boolean} `true` if `fn` is an `async` function, otherwise `false`
7
25
  */
8
26
  export declare const isAsyncFunction: (fn: unknown) => fn is (...args: never[]) => Promise<unknown>;
9
27
  /**
10
- * Detect if a RULES object has any async rules in it
28
+ * Detect whether a rule set, or any rule set in an array of alternatives, contains an
29
+ * `async` rule. This is the check `getValueErrors`/`getSchemaErrors` use to choose a path.
11
30
  *
12
- * @param {RULES|RULES[]} rules a rules object
13
- * @returns {boolean} true if any of the rules is an async function otherwise false
31
+ * @param {RULES|RULES[]} rules a rule set, or an array of alternative rule sets
32
+ * @returns {boolean} `true` if any rule is an `async` function, otherwise `false`
14
33
  */
15
34
  export declare const hasAsyncFunction: (rules: {
16
35
  [key: string]: unknown;
@@ -18,69 +37,136 @@ export declare const hasAsyncFunction: (rules: {
18
37
  [key: string]: unknown;
19
38
  }[]) => boolean;
20
39
  /**
21
- * Check the input against all provided validation rules asynchronously
40
+ * Validate a value against a rule set, always asynchronously.
22
41
  *
23
- * @param {V} value the value to check
24
- * @param {RULES<V>} rules rules object to use for validating the provided value
25
- * @param {unknown[]} overload array of extra values the validator rules might need to properly validate the value
26
- * @returns {Promise<string[]>} a list of rule names which failed validation
42
+ * Sync rules run inline and `async` rules run concurrently; a plain rule returning a promise
43
+ * is awaited too. A rule that returns a falsy value, throws or rejects fails. The returned
44
+ * promise never rejects.
45
+ *
46
+ * @template V type of the value being validated
47
+ * @param {V} value the value to validate
48
+ * @param {RULES<V>} rules the rule set; each key is the error reported when its rule fails
49
+ * @param {...unknown} overload extra arguments passed to every rule after the value
50
+ * @returns {Promise<string[]>} the keys of the failing rules, in rule order; empty if all passed
27
51
  */
28
52
  export declare const getValueErrorsAsync: <V>(value: V, rules: RULES<V>, ...overload: unknown[]) => Promise<string[]>;
29
53
  /**
30
- * Check a schema against all provided validation rules asynchronously
54
+ * Validate an object against a schema, always asynchronously.
55
+ *
56
+ * Each schema key's rule set is run against `object_to_check[key]` (a missing key is
57
+ * validated as `undefined`). An entry given as an array of alternative rule sets passes if
58
+ * any alternative passes. Fields whose rules are all sync are filled in immediately; only
59
+ * fields with pending promises are awaited, concurrently. The returned promise never rejects.
31
60
  *
32
- * @param {CHECKABLE_OBJECT} object_to_check object to be validated
33
- * @param {S} schema schema to validate against
34
- * @param {SCHEMA_OPTIONS} options options affecting how the rules are run
35
- * @param {unknown[]} overload array of extra values the validator rules might need to properly validate the value
36
- * @returns {CHECKED_SCHEMA<S>} an object containing keys and their respective lists of failed rule names
61
+ * @template S the schema type, used to infer the result's keys and shapes
62
+ * @param {CHECKABLE_OBJECT} object_to_check the object to validate
63
+ * @param {S} schema maps each key to a rule set, or to an array of alternative rule sets
64
+ * @param {SCHEMA_OPTIONS} [options] `strict: true` also reports keys of the object that the schema doesn't
65
+ * have; `break_early: true` reports only the first failing rule of each rule set
66
+ * @param {...unknown} overload extra arguments passed to every rule after the value
67
+ * @returns {CHECKED_SCHEMA<S>} an object with every schema key, in schema order: `string[]` of
68
+ * failing rule keys for a rule set, or for alternatives `[]` if any passed and otherwise one
69
+ * error list per alternative (`string[][]`). In strict mode, each unknown key follows with
70
+ * `['Key not allowed']`.
37
71
  */
38
72
  export declare const getSchemaErrorsAsync: <S extends SCHEMA>(object_to_check: CHECKABLE_OBJECT, schema: S, options?: SCHEMA_OPTIONS, ...overload: unknown[]) => CHECKED_SCHEMA<S>;
39
73
  /**
40
- * Check the input against all provided validation rules synchronously
74
+ * Validate a value against a rule set synchronously.
75
+ *
76
+ * A rule that returns a falsy value or throws fails. A rule that returns a promise also
77
+ * fails, because it can't be awaited here - use `getValueErrorsAsync` for those.
41
78
  *
42
- * @param {V} value the value to check
43
- * @param {RULES_SYNC<V>} rules rules object to use for validating the provided value
44
- * @param {unknown[]} overload array of extra values the validator rules might need to properly validate the value
45
- * @returns {string[]} a list of rule names which failed validation
79
+ * @template V type of the value being validated
80
+ * @param {V} value the value to validate
81
+ * @param {RULES_SYNC<V>} rules the rule set; each key is the error reported when its rule fails
82
+ * @param {...unknown} overload extra arguments passed to every rule after the value
83
+ * @returns {string[]} the keys of the failing rules, in rule order; empty if all passed
46
84
  */
47
85
  export declare const getValueErrorsSync: <V>(value: V, rules: RULES_SYNC<V>, ...overload: unknown[]) => string[];
48
86
  /**
49
- * Check a schema against all provided validation rules synchronously
87
+ * Validate an object against a schema synchronously.
50
88
  *
51
- * @param {CHECKABLE_OBJECT} object object to be validated
52
- * @param {S} schema schema to validate against
53
- * @param {SCHEMA_OPTIONS} options options affecting how the rules are run
54
- * @param {unknown[]} overload array of extra values the validator rules might need to properly validate the value
55
- * @returns {CHECKED_SCHEMA_SYNC<S>} an object containing keys and their respective lists of failed rule names
89
+ * Each schema key's rule set is run against `object[key]` (a missing key is validated as
90
+ * `undefined`), with the same rule semantics as `getValueErrorsSync`: a rule returning a
91
+ * promise fails. An entry given as an array of alternative rule sets passes if any
92
+ * alternative passes.
93
+ *
94
+ * @template S the schema type, used to infer the result's keys and shapes
95
+ * @param {CHECKABLE_OBJECT} object the object to validate
96
+ * @param {S} schema maps each key to a rule set, or to an array of alternative rule sets
97
+ * @param {SCHEMA_OPTIONS} [options] `strict: true` also reports keys of the object that the schema doesn't
98
+ * have; `break_early: true` reports only the first failing rule of each rule set
99
+ * @param {...unknown} overload extra arguments passed to every rule after the value
100
+ * @returns {CHECKED_SCHEMA_SYNC<S>} an object with every schema key, in schema order: `string[]`
101
+ * of failing rule keys for a rule set, or for alternatives `[]` if any passed and otherwise
102
+ * one error list per alternative (`string[][]`). In strict mode, each unknown key follows
103
+ * with `['Key not allowed']`.
56
104
  */
57
105
  export declare const getSchemaErrorsSync: <S extends SCHEMA_SYNC>(object: CHECKABLE_OBJECT, schema: S, options?: SCHEMA_OPTIONS, ...overload: unknown[]) => CHECKED_SCHEMA_SYNC<S>;
58
106
  /**
59
- * Check the input against all provided validation rules, choosing the sync or async
60
- * implementation based on whether any rule is an `async` function.
107
+ * Validate a value against a rule set, synchronously if no rule is an `async` function and
108
+ * asynchronously otherwise (see `getValueErrorsSync` / `getValueErrorsAsync`).
61
109
  *
62
- * The return type is inferred from the rules: all-sync rules yield `string[]`,
63
- * any `async` rule yields `Promise<string[]>`. Rules whose return type is declared
64
- * as `boolean | Promise<boolean>` (e.g. annotated as `RULE`) yield the union.
110
+ * The return type is inferred from the rules: all-sync rules give `string[]`, any `async`
111
+ * rule gives `Promise<string[]>`, and rules typed as returning `boolean | Promise<boolean>`
112
+ * (e.g. annotated as `RULE`) give the union. For a rule set used repeatedly,
113
+ * `compileRules` is faster.
65
114
  *
66
- * @param {V} value the value to check
67
- * @param {R} rules rules object to use for validating the provided value
68
- * @param {unknown[]} overload array of extra values the validator rules might need to properly validate the value
69
- * @returns {Promise<string[]>|string[]} a list of rule names which failed validation
115
+ * @template V type of the value being validated
116
+ * @template R the rule set type, used to infer sync or async
117
+ * @param {V} value the value to validate
118
+ * @param {R} rules the rule set; each key is the error reported when its rule fails
119
+ * @param {...unknown} overload extra arguments passed to every rule after the value
120
+ * @returns {string[]|Promise<string[]>} the keys of the failing rules, in rule order; empty if all passed
70
121
  */
71
122
  export declare const getValueErrors: <V, R extends RULES<V>>(value: V, rules: R, ...overload: unknown[]) => MAYBE_ASYNC<RULES_KIND<R>, string[]>;
72
123
  /**
73
- * Check a schema against all provided validation rules, choosing the sync or async
74
- * implementation based on whether any rule is an `async` function.
75
- *
76
- * The return type is inferred from the schema: all-sync rules yield the result object
77
- * directly, any `async` rule yields a `Promise` of it. Rules whose return type is declared
78
- * as `boolean | Promise<boolean>` (e.g. a schema annotated as `SCHEMA`) yield the union.
79
- *
80
- * @param {CHECKABLE_OBJECT} object object to be validated
81
- * @param {S} schema schema to validate against
82
- * @param {SCHEMA_OPTIONS} options options affecting how the rules are run
83
- * @param {unknown[]} overload array of extra values the validator rules might need to properly validate the value
84
- * @returns {CHECKED_SCHEMA<S>|CHECKED_SCHEMA_SYNC<S>} an object containing keys and their respective lists of failed rule names
124
+ * Validate an object against a schema, synchronously if no rule in the schema is an
125
+ * `async` function and asynchronously otherwise (see `getSchemaErrorsSync` /
126
+ * `getSchemaErrorsAsync` for the details and result shape).
127
+ *
128
+ * The return type is inferred from the schema: all-sync rules give the result object
129
+ * directly, any `async` rule gives a `Promise` of it, and rules typed as returning
130
+ * `boolean | Promise<boolean>` (e.g. a schema annotated as `SCHEMA`) give the union. For a
131
+ * schema used repeatedly, `compileSchema` is faster.
132
+ *
133
+ * @template S the schema type, used to infer sync or async and the result's keys and shapes
134
+ * @param {CHECKABLE_OBJECT} object the object to validate
135
+ * @param {S} schema maps each key to a rule set, or to an array of alternative rule sets
136
+ * @param {SCHEMA_OPTIONS} [options] `strict: true` also reports keys of the object that the schema doesn't
137
+ * have; `break_early: true` reports only the first failing rule of each rule set
138
+ * @param {...unknown} overload extra arguments passed to every rule after the value
139
+ * @returns {CHECKED_SCHEMA_SYNC<S>|CHECKED_SCHEMA<S>} the errors per key, or a promise of them
85
140
  */
86
141
  export declare const getSchemaErrors: <S extends SCHEMA>(object: CHECKABLE_OBJECT, schema: S, options?: SCHEMA_OPTIONS, ...overload: unknown[]) => MAYBE_ASYNC<SCHEMA_KIND<S>, CHECKED_SCHEMA_SYNC<S>>;
142
+ /**
143
+ * Compile a rule set into a reusable validator that gives the same results as
144
+ * `getValueErrors(value, rules, ...overload)`, but does the key lookup and the sync/async
145
+ * check once here instead of on every call.
146
+ *
147
+ * The rule set is copied, so changes made to `rules` after compiling have no effect on the
148
+ * returned validator.
149
+ *
150
+ * @template R the rule set type; the validator's value parameter and sync/async return type come from it
151
+ * @param {R} rules the rule set; each key is the error reported when its rule fails
152
+ * @param {RULES_OPTIONS} [options] `break_early: true` reports only the first failing rule
153
+ * @returns {COMPILED_RULES<R>} `(value, ...overload) => string[]` if every rule is sync, otherwise
154
+ * an async function returning `Promise<string[]>`
155
+ */
156
+ export declare const compileRules: <R extends ANY_RULES>(rules: R, options?: RULES_OPTIONS) => COMPILED_RULES<R>;
157
+ /**
158
+ * Compile a schema into a reusable validator that gives the same results as
159
+ * `getSchemaErrors(object, schema, options, ...overload)`, but does the key lookups and the
160
+ * sync/async check once here instead of on every call.
161
+ *
162
+ * The schema, its rule sets and the options are copied, so changes made to them after
163
+ * compiling have no effect on the returned validator.
164
+ *
165
+ * @template S the schema type; the validator's sync/async return type and result shape come from it
166
+ * @param {S} schema maps each key to a rule set, or to an array of alternative rule sets
167
+ * @param {SCHEMA_OPTIONS} [options] `strict: true` also reports keys of the object that the schema doesn't
168
+ * have; `break_early: true` reports only the first failing rule of each rule set
169
+ * @returns {COMPILED_SCHEMA<S>} `(object, ...overload) => errors` if every rule is sync, otherwise
170
+ * an async function returning a promise of them; see `getSchemaErrorsSync` for the result shape
171
+ */
172
+ export declare const compileSchema: <S extends SCHEMA>(schema: S, options?: SCHEMA_OPTIONS) => COMPILED_SCHEMA<S>;
@@ -1 +1 @@
1
- var E=(n)=>typeof n==="string"||n!==null&&typeof n==="object"&&("length"in n)&&typeof n.length==="number",a=(n)=>(e)=>typeof e===n,i=(n)=>(e)=>typeof e==="string"&&n.test(e),S=(n=1/0)=>(e)=>typeof e==="number"&&e<=n,l=(n=-1/0)=>(e)=>typeof e==="number"&&e>=n,C=(n=1/0)=>(e)=>E(e)&&e.length<=n,R=(n=0)=>(e)=>E(e)&&e.length>=n,f=(n=1/0,e=0)=>(t)=>typeof t==="string"&&t.length>=e&&t.length<=n,y=(n=1/0,e=-1/0)=>(t)=>typeof t==="number"&&t>=e&&t<=n,w=(n)=>(e)=>e===n||Number.isNaN(n)&&Number.isNaN(e),p=(n=[])=>{let e=new Set(n);return(t)=>e.has(t)},k=()=>(n)=>Number.isInteger(n),_=()=>(n)=>Array.isArray(n),N=(n)=>(e)=>e instanceof n,O=(n)=>(e,...t)=>{try{return!n(e,...t)}catch{return!0}},m=(n)=>async(e,...t)=>{try{return!await n(e,...t)}catch{return!0}},A=(n)=>(e,...t)=>{if(!Array.isArray(e))return!1;for(let o=0;o<e.length;o++)if(!n(e[o],...t))return!1;return!0},U=(n)=>async(e,...t)=>{if(!Array.isArray(e))return!1;for(let o=0;o<e.length;o++)if(!await n(e[o],...t))return!1;return!0},L=(n=[])=>async(e,...t)=>(await Promise.all(n.map((o)=>o(e,...t)))).some((o)=>o),g=(n=[])=>(e,...t)=>n.map((o)=>o(e,...t)).some((o)=>o);var c=(n)=>typeof n==="function"&&n.constructor.name==="AsyncFunction";var u=(n,e)=>Object.fromEntries(Object.entries(n).map(([t,o])=>[t,c(o)?async(s,...r)=>e(o,s,r):(s,...r)=>e(o,s,r)])),d=(n)=>u(n,(e,t,o)=>typeof t>"u"?!0:e(t,...o)),b=(n)=>u(n,(e,t,o)=>t===null?!0:e(t,...o)),H=(n,e)=>u(e,(t,o,s)=>t(n(o),...s));export{L as acceptAnyAsync,g as acceptAnySync,b as allowNull,d as allowUndefined,w as equals,U as everyElementAsync,A as everyElementSync,_ as isArray,N as isInstanceOf,k as isInteger,p as isOneOf,a as isType,i as matchesRegex,S as max,C as maxLength,l as min,R as minLength,m as notAsync,O as notSync,y as numberBetween,H as preprocess,f as stringBetween};
1
+ var u=(n)=>typeof n==="string"||n!==null&&typeof n==="object"&&("length"in n)&&typeof n.length==="number",c=(n)=>(e)=>typeof e===n,l=(n)=>(e)=>typeof e==="string"&&n.test(e),a=(n=1/0)=>(e)=>typeof e==="number"&&e<=n,S=(n=-1/0)=>(e)=>typeof e==="number"&&e>=n,f=(n=1/0)=>(e)=>u(e)&&e.length<=n,C=(n=0)=>(e)=>u(e)&&e.length>=n,y=(n=1/0,e=0)=>(t)=>typeof t==="string"&&t.length>=e&&t.length<=n,k=(n=1/0,e=-1/0)=>(t)=>typeof t==="number"&&t>=e&&t<=n,R=(n)=>(e)=>e===n||Number.isNaN(n)&&Number.isNaN(e),_=(n=[])=>{let e=new Set(n);return(t)=>e.has(t)},w=()=>(n)=>Number.isInteger(n),g=()=>(n)=>Array.isArray(n),L=(n)=>(e)=>e instanceof n,A=(n)=>(e,...t)=>{try{return!n(e,...t)}catch{return!0}},p=(n)=>async(e,...t)=>{try{return!await n(e,...t)}catch{return!0}},O=(n)=>(e,...t)=>{if(!Array.isArray(e))return!1;for(let r=0;r<e.length;r++)if(!n(e[r],...t))return!1;return!0},m=(n)=>async(e,...t)=>{if(!Array.isArray(e))return!1;for(let r=0;r<e.length;r++)if(!await n(e[r],...t))return!1;return!0},U=(n=[])=>async(e,...t)=>(await Promise.all(n.map((r)=>r(e,...t)))).some((r)=>r),N=(n=[])=>(e,...t)=>n.map((r)=>r(e,...t)).some((r)=>r);var E=(n)=>typeof n==="function"&&n.constructor.name==="AsyncFunction";var i=(n,e)=>Object.fromEntries(Object.entries(n).map(([t,r])=>[t,E(r)?async(s,...o)=>e(r,s,o):(s,...o)=>e(r,s,o)])),M=(n)=>i(n,(e,t,r)=>typeof t>"u"?!0:e(t,...r)),P=(n)=>i(n,(e,t,r)=>t===null?!0:e(t,...r)),x=(n,e)=>i(e,(t,r,s)=>t(n(r),...s));export{U as acceptAnyAsync,N as acceptAnySync,P as allowNull,M as allowUndefined,R as equals,m as everyElementAsync,O as everyElementSync,g as isArray,L as isInstanceOf,w as isInteger,_ as isOneOf,c as isType,l as matchesRegex,a as max,f as maxLength,S as min,C as minLength,p as notAsync,A as notSync,k as numberBetween,x as preprocess,y as stringBetween};
@@ -1,39 +1,189 @@
1
+ /**
2
+ * Rule helpers: factories that build common rules, and combinators that modify or combine
3
+ * existing rules. Every factory returns a new rule, so the result can be used directly as a
4
+ * value in a rule set, e.g. `{ "Too long": maxLength(100) }`.
5
+ *
6
+ * @module
7
+ */
1
8
  import type { RULE, RULE_ASYNC, RULE_SYNC } from '../types';
9
+ /** Every string the `typeof` operator can return. */
2
10
  export type TYPEOF_RESULT = "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function";
11
+ /**
12
+ * Rule: passes if `typeof value === type`. Note that `typeof null` is `'object'`, so
13
+ * `isType('object')` passes `null`, and `typeof NaN` is `'number'`.
14
+ *
15
+ * @param {TYPEOF_RESULT} type the expected `typeof` result
16
+ * @returns {RULE_SYNC} the rule
17
+ */
3
18
  export declare const isType: (type: TYPEOF_RESULT) => RULE_SYNC;
19
+ /**
20
+ * Rule: passes if the value is a string that `regex` matches. Non-strings fail.
21
+ *
22
+ * The regex is used as given, so one with the `g` or `y` flag is stateful: `test` moves its
23
+ * `lastIndex`, and repeated calls can alternate between passing and failing. Use regexes
24
+ * without those flags.
25
+ *
26
+ * @param {RegExp} regex the pattern to test strings against
27
+ * @returns {RULE_SYNC} the rule
28
+ */
4
29
  export declare const matchesRegex: (regex: RegExp) => RULE_SYNC;
30
+ /**
31
+ * Rule: passes if the value is a number less than or equal to `max_value`. Non-numbers
32
+ * (including bigints) and `NaN` fail.
33
+ *
34
+ * @param {number} [max_value=Infinity] the inclusive upper bound
35
+ * @returns {RULE_SYNC} the rule
36
+ */
5
37
  export declare const max: (max_value?: number) => RULE_SYNC;
38
+ /**
39
+ * Rule: passes if the value is a number greater than or equal to `min_value`. Non-numbers
40
+ * (including bigints) and `NaN` fail.
41
+ *
42
+ * @param {number} [min_value=-Infinity] the inclusive lower bound
43
+ * @returns {RULE_SYNC} the rule
44
+ */
6
45
  export declare const min: (min_value?: number) => RULE_SYNC;
46
+ /**
47
+ * Rule: passes if the value has a numeric `length` (a string, array, or object with a
48
+ * numeric `length`) of at most `max_length`. Values without one fail.
49
+ *
50
+ * @param {number} [max_length=Infinity] the inclusive maximum length
51
+ * @returns {RULE_SYNC} the rule
52
+ */
7
53
  export declare const maxLength: (max_length?: number) => RULE_SYNC;
54
+ /**
55
+ * Rule: passes if the value has a numeric `length` (a string, array, or object with a
56
+ * numeric `length`) of at least `min_length`. Values without one fail, even with the
57
+ * default of `0`.
58
+ *
59
+ * @param {number} [min_length=0] the inclusive minimum length
60
+ * @returns {RULE_SYNC} the rule
61
+ */
8
62
  export declare const minLength: (min_length?: number) => RULE_SYNC;
63
+ /**
64
+ * Rule: passes if the value is a string whose length is between `min` and `max`, inclusive.
65
+ * Note the argument order: the maximum comes first.
66
+ *
67
+ * @param {number} [max=Infinity] the inclusive maximum length
68
+ * @param {number} [min=0] the inclusive minimum length
69
+ * @returns {RULE_SYNC} the rule
70
+ */
9
71
  export declare const stringBetween: (max?: number, min?: number) => RULE_SYNC;
72
+ /**
73
+ * Rule: passes if the value is a number between `min` and `max`, inclusive. Non-numbers
74
+ * and `NaN` fail. Note the argument order: the maximum comes first.
75
+ *
76
+ * @param {number} [max=Infinity] the inclusive upper bound
77
+ * @param {number} [min=-Infinity] the inclusive lower bound
78
+ * @returns {RULE_SYNC} the rule
79
+ */
10
80
  export declare const numberBetween: (max?: number, min?: number) => RULE_SYNC;
11
- /** Equality using SameValueZero semantics (`===`, except `NaN` equals `NaN`) - the same comparison `isOneOf` uses. */
81
+ /**
82
+ * Rule: passes if the value equals `expected` under SameValueZero semantics (`===`, except
83
+ * `NaN` equals `NaN`) - the same comparison `isOneOf` uses. Objects compare by reference.
84
+ *
85
+ * @param {unknown} expected the value to compare against
86
+ * @returns {RULE_SYNC} the rule
87
+ */
12
88
  export declare const equals: (expected: unknown) => RULE_SYNC;
13
- /** Membership in `allowed_values`, using SameValueZero semantics - the same comparison `equals` uses. */
89
+ /**
90
+ * Rule: passes if the value is one of `allowed_values`, using SameValueZero semantics - the
91
+ * same comparison `equals` uses. The values are copied into a `Set` when the rule is created,
92
+ * so later changes to the array have no effect.
93
+ *
94
+ * @param {unknown[]} [allowed_values=[]] the accepted values; with none, every value fails
95
+ * @returns {RULE_SYNC} the rule
96
+ */
14
97
  export declare const isOneOf: (allowed_values?: unknown[]) => RULE_SYNC;
98
+ /**
99
+ * Rule: passes if the value is a number with no fractional part (`Number.isInteger`).
100
+ * Bigints, numeric strings, `NaN` and `Infinity` fail.
101
+ *
102
+ * @returns {RULE_SYNC} the rule
103
+ */
15
104
  export declare const isInteger: () => RULE_SYNC;
105
+ /**
106
+ * Rule: passes if the value is an array (`Array.isArray`).
107
+ *
108
+ * @returns {RULE_SYNC} the rule
109
+ */
16
110
  export declare const isArray: () => RULE_SYNC;
111
+ /**
112
+ * Rule: passes if `value instanceof constructor`. Like `instanceof`, this checks the
113
+ * prototype chain, so it fails for objects from another realm (e.g. an iframe).
114
+ *
115
+ * @param {Function} constructor the class or constructor function to check against
116
+ * @returns {RULE_SYNC} the rule
117
+ */
17
118
  export declare const isInstanceOf: (constructor: abstract new (...args: never[]) => unknown) => RULE_SYNC;
18
- /** Inverts the given rule; a throwing rule counts as failed, so its negation passes. The rule must be synchronous. */
119
+ /**
120
+ * Combinator: inverts a synchronous rule. A throwing rule counts as failed, so its negation
121
+ * passes. The rule must be synchronous: a returned promise is truthy, so the negation fails.
122
+ *
123
+ * @template V type of the value being validated
124
+ * @template O tuple type of the overload arguments, passed through to `rule`
125
+ * @param {RULE_SYNC<V, O>} rule the rule to invert
126
+ * @returns {RULE_SYNC<V, O>} the inverted rule
127
+ */
19
128
  export declare const notSync: <V = unknown, O extends unknown[] = unknown[]>(rule: RULE_SYNC<V, O>) => RULE_SYNC<V, O>;
20
129
  /**
21
- * Inverts the given rule; a throwing/rejecting rule counts as failed, so its negation passes.
22
- * Always async - a single async rule switches the whole rule set onto the async execution
23
- * path, so prefer `notSync` unless the wrapped rule is genuinely async.
130
+ * Combinator: inverts a sync or async rule. A throwing or rejecting rule counts as failed, so
131
+ * its negation passes. Always async - a single async rule switches the whole rule set onto the
132
+ * async execution path, so prefer `notSync` unless the wrapped rule is genuinely async.
133
+ *
134
+ * @template V type of the value being validated
135
+ * @template O tuple type of the overload arguments, passed through to `rule`
136
+ * @param {RULE<V, O>} rule the rule to invert
137
+ * @returns {RULE_ASYNC<V, O>} the inverted rule
24
138
  */
25
139
  export declare const notAsync: <V = unknown, O extends unknown[] = unknown[]>(rule: RULE<V, O>) => RULE_ASYNC<V, O>;
26
- /** Passes if the value is an array and every element (holes included) passes the given rule. Stops at the first failure. The rule must be synchronous. */
140
+ /**
141
+ * Combinator: passes if the value is an array and every element (holes included, as
142
+ * `undefined`) passes `rule`. Stops at the first failing element; an empty array passes.
143
+ * A throwing element rule makes the whole rule fail. The rule must be synchronous: a
144
+ * returned promise is truthy, so that element passes.
145
+ *
146
+ * @template E type of the array elements the rule accepts
147
+ * @template O tuple type of the overload arguments, passed through to `rule`
148
+ * @param {RULE_SYNC<E, O>} rule the rule every element must pass
149
+ * @returns {RULE_SYNC<unknown, O>} the array rule
150
+ */
27
151
  export declare const everyElementSync: <E = unknown, O extends unknown[] = unknown[]>(rule: RULE_SYNC<E, O>) => RULE_SYNC<unknown, O>;
28
152
  /**
29
- * Passes if the value is an array and every element (holes included) passes the given rule.
30
- * Elements are checked sequentially, stopping at the first failure, so an early invalid
31
- * element in an attacker-sized array doesn't fan out rule calls for the rest of it.
153
+ * Combinator: passes if the value is an array and every element (holes included, as
154
+ * `undefined`) passes `rule`, which may be sync or async; an empty array passes. Elements are
155
+ * checked one at a time, stopping at the first failure, so an early invalid element in an
156
+ * attacker-sized array doesn't fan out rule calls for the rest of it. A throwing or rejecting
157
+ * element rule makes the whole rule fail.
32
158
  * Always async - a single async rule switches the whole rule set onto the async execution
33
159
  * path, so prefer `everyElementSync` unless the element rule is genuinely async.
160
+ *
161
+ * @template E type of the array elements the rule accepts
162
+ * @template O tuple type of the overload arguments, passed through to `rule`
163
+ * @param {RULE<E, O>} rule the rule every element must pass
164
+ * @returns {RULE_ASYNC<unknown, O>} the array rule
34
165
  */
35
166
  export declare const everyElementAsync: <E = unknown, O extends unknown[] = unknown[]>(rule: RULE<E, O>) => RULE_ASYNC<unknown, O>;
36
- /** Passes if *any* of the given rules passes. Always async. */
167
+ /**
168
+ * Combinator: passes if *any* of the given rules passes. All rules run concurrently and every
169
+ * one is awaited, even after one has passed. If any rule throws or rejects, the combined rule
170
+ * fails, even when another rule passed. With no rules, it fails. Always async.
171
+ *
172
+ * @template V type of the value being validated
173
+ * @template O tuple type of the overload arguments, passed through to every rule
174
+ * @param {RULE<V, O>[]} [rules=[]] the alternatives, sync or async
175
+ * @returns {RULE_ASYNC<V, O>} the combined rule
176
+ */
37
177
  export declare const acceptAnyAsync: <V = unknown, O extends unknown[] = unknown[]>(rules?: RULE<V, O>[]) => RULE_ASYNC<V, O>;
38
- /** Passes if *any* of the given rules passes. All rules must be synchronous. */
178
+ /**
179
+ * Combinator: passes if *any* of the given rules passes. Every rule runs, even after one has
180
+ * passed. If any rule throws, the combined rule fails, even when another rule passed. With no
181
+ * rules, it fails. All rules must be synchronous: a returned promise is truthy, so it counts
182
+ * as passing.
183
+ *
184
+ * @template V type of the value being validated
185
+ * @template O tuple type of the overload arguments, passed through to every rule
186
+ * @param {RULE_SYNC<V, O>[]} [rules=[]] the alternatives
187
+ * @returns {RULE_SYNC<V, O>} the combined rule
188
+ */
39
189
  export declare const acceptAnySync: <V = unknown, O extends unknown[] = unknown[]>(rules?: RULE_SYNC<V, O>[]) => RULE_SYNC<V, O>;
@@ -1,29 +1,59 @@
1
+ /**
2
+ * Rule set helpers: wrap every rule in a rule set at once, typically for use as a schema
3
+ * entry, e.g. `{ website: allowUndefined(URL_RULES) }`.
4
+ *
5
+ * @module
6
+ */
1
7
  import type { RULES } from '../types';
2
8
  /**
3
- * Re-declares each rule so that the parameters of the returned rule are `V` instead of the
4
- * original rule's parameters, while keeping the original return type (sync stays sync,
5
- * async stays async).
9
+ * Re-declares each rule so that the value parameter of the returned rule is `V` instead of
10
+ * the original rule's, while keeping its overload parameters and its return type (sync stays
11
+ * sync, async stays async).
12
+ *
13
+ * @template R the original rule set
14
+ * @template V the new value type
6
15
  */
7
16
  type REWRAPPED<R extends RULES<never>, V> = {
8
17
  [K in keyof R]: R[K] extends (value: never, ...overload: infer O) => infer Ret ? (value: V, ...overload: O) => Ret : never;
9
18
  };
10
19
  /**
11
- * Wraps every rule so that an `undefined` value always passes.
20
+ * Wrap every rule in a rule set so that an `undefined` value passes without calling it,
21
+ * making the field optional. Other values are validated as before.
22
+ *
12
23
  * Sync rules stay sync and async rules stay async, so `getValueErrors`/`getSchemaErrors`
13
24
  * still pick the correct execution path.
25
+ *
26
+ * @template R the rule set type, preserved in the result
27
+ * @param {R} rules the rule set to wrap; it is not modified
28
+ * @returns {R} a new rule set with the same keys
14
29
  */
15
30
  export declare const allowUndefined: <R extends RULES>(rules: R) => R;
16
31
  /**
17
- * Wraps every rule so that a `null` value always passes.
32
+ * Wrap every rule in a rule set so that a `null` value passes without calling it. Other
33
+ * values (including `undefined`) are validated as before.
34
+ *
18
35
  * Sync rules stay sync and async rules stay async, so `getValueErrors`/`getSchemaErrors`
19
36
  * still pick the correct execution path.
37
+ *
38
+ * @template R the rule set type, preserved in the result
39
+ * @param {R} rules the rule set to wrap; it is not modified
40
+ * @returns {R} a new rule set with the same keys
20
41
  */
21
42
  export declare const allowNull: <R extends RULES>(rules: R) => R;
22
43
  /**
23
- * Wraps every rule so that the value is passed through `fn` before being validated.
24
- * The rules may be typed against the *output* of `fn`; the returned rules accept `unknown`.
25
- * Sync rules stay sync and async rules stay async, so `getValueErrors`/`getSchemaErrors`
26
- * still pick the correct execution path.
44
+ * Wrap every rule in a rule set so that each rule validates `fn(value)` instead of the value,
45
+ * e.g. to trim or parse it first. The original value is not changed.
46
+ *
47
+ * `fn` is called separately for each rule, so it should be cheap and free of side effects.
48
+ * If it throws, every rule it was called for fails. The rules may be typed against the
49
+ * *output* of `fn`; the returned rules accept `unknown`. Sync rules stay sync and async
50
+ * rules stay async.
51
+ *
52
+ * @template V the type `fn` returns
53
+ * @template R the rule set type
54
+ * @param {Function} fn transforms the value before each rule sees it
55
+ * @param {R} rules the rule set to wrap; it is not modified
56
+ * @returns {REWRAPPED<R, unknown>} a new rule set with the same keys, accepting `unknown`
27
57
  */
28
58
  export declare const preprocess: <V, R extends RULES<V>>(fn: (value: unknown) => V, rules: R) => REWRAPPED<R, unknown>;
29
59
  export {};
package/lib/index.js CHANGED
@@ -1 +1 @@
1
- var f={strict:!1};var C=(e)=>typeof e==="function"&&e.constructor.name==="AsyncFunction",A=(e)=>{if(Array.isArray(e))return e.some((r)=>Object.values(r).some(C));return Object.values(e).some(C)},l=async(e,r,...E)=>{let t={},s=[];for(let[n,S]of Object.entries(r)){if(t[n]=!1,C(S)){s.push(S(e,...E).then((o)=>{t[n]=Boolean(o)}).catch(()=>{t[n]=!1}));continue}try{let o=S(e,...E);if(o!==null&&(typeof o==="object"||typeof o==="function")&&typeof o.then==="function")s.push(Promise.resolve(o).then((i)=>{t[n]=Boolean(i)}).catch(()=>{t[n]=!1}));else t[n]=Boolean(o)}catch{t[n]=!1}}if(s.length)await Promise.allSettled(s);let c=[];for(let[n,S]of Object.entries(t))if(!S)c.push(n);return c},y=async(e,r,E=f,...t)=>{let s={},c=[];if(Object.entries(r).forEach(([n,S])=>{let o;if(Array.isArray(S))o=Promise.allSettled(S.map((i)=>l(e[n],i,...t))).then((i)=>{let _=i.map((a)=>{if(a.status==="fulfilled")return a.value;return["Unknown error occurred"]});if(_.some((a)=>!a.length))s[n]=[];else s[n]=_}).catch((i)=>{if(i instanceof Error)s[n]=[i.message];else s[n]=["Unknown error occurred"]});else o=l(e[n],S,...t).then((i)=>{s[n]=i}).catch((i)=>{s[n]=[i instanceof Error?i.message:"Unknown error occurred"]});c.push(o)}),E.strict){let n=Object.keys(e),S=new Set(Object.keys(r));n.filter((i)=>!S.has(i)).forEach((i)=>{s[i]=["Key not allowed"]})}return await Promise.allSettled(c),s},u=(e,r,...E)=>{let t=[];for(let[s,c]of Object.entries(r))try{let n=c(e,...E);if(n!==null&&(typeof n==="object"||typeof n==="function")&&typeof n.then==="function")t.push(s);else if(!n)t.push(s)}catch(n){t.push(s)}return t},N=(e,r,E=f,...t)=>{let s={};for(let[c,n]of Object.entries(r))if(Array.isArray(n)){let S=n.map((o)=>u(e[c],o,...t));s[c]=S.some((o)=>!o.length)?[]:S}else s[c]=u(e[c],n,...t);if(E.strict){let c=Object.keys(e),n=new Set(Object.keys(r));c.filter((o)=>!n.has(o)).forEach((o)=>{s[o]=["Key not allowed"]})}return s},O=(e,r,...E)=>A(r)?l(e,r,...E):u(e,r,...E),H=(e)=>Object.values(e).some(A),m=(e,r,E=f,...t)=>H(r)?y(e,r,E,...t):N(e,r,E,...t);export{m as getSchemaErrors,y as getSchemaErrorsAsync,N as getSchemaErrorsSync,O as getValueErrors,l as getValueErrorsAsync,u as getValueErrorsSync,A as hasAsyncFunction,C as isAsyncFunction};
1
+ var A={strict:!1,break_early:!1},w=(n)=>typeof n==="function"&&n.constructor.name==="AsyncFunction",L=(n)=>{let e=Object.keys(n);for(let t=0;t<e.length;t++)if(w(n[e[t]]))return!0;return!1},R=(n)=>{if(Array.isArray(n)){for(let e=0;e<n.length;e++)if(L(n[e]))return!0;return!1}return L(n)},f=(n)=>n!==null&&(typeof n==="object"||typeof n==="function")&&typeof n.then==="function",k=(n,e,t)=>t.length?n(e,...t):n(e),M=(n,e,t,i=Object.keys(e),E=!1)=>{let s=[];for(let l=0;l<i.length;l++){let r=i[l];try{let o=k(e[r],n,t);if(!o||f(o))s.push(r)}catch{s.push(r)}if(E&&s.length)return s}return s},y=(n,e,t,i=Object.keys(e),E=!1)=>{if(E)return N(n,e,t,i);let s=Array(i.length),l,r=(S,u)=>{s[S]=!1,(l??=[]).push(Promise.resolve(u).then((c)=>{s[S]=Boolean(c)},()=>{s[S]=!1}))};for(let S=0;S<i.length;S++){let u=e[i[S]];try{let c=k(u,n,t);if(f(c))r(S,c);else s[S]=Boolean(c)}catch{s[S]=!1}}let o=()=>{let S=[];for(let u=0;u<i.length;u++)if(!s[u])S.push(i[u]);return S};return l?Promise.all(l).then(o):o()},p=(n)=>{Promise.resolve(n).then(void 0,()=>{})},N=(n,e,t,i)=>{let E,s;for(let r=0;r<i.length;r++){let o=e[i[r]];if(w(o)){(E??=[]).push(r);continue}let S;try{S=k(o,n,t)}catch{S=!1}if(f(S))(s??=[]).push([r,S]);else if(!S){if(s)s.forEach(([,u])=>p(u));return[i[r]]}}if(!E&&!s)return[];let l=s??[];if(E)for(let r of E)l.push([r,k(e[i[r]],n,t)]);return Promise.all(l.map(([r,o])=>Promise.resolve(o).then((S)=>S?-1:r,()=>r))).then((r)=>{let o=-1;for(let S of r)if(S!==-1&&(o===-1||S<o))o=S;return o===-1?[]:[i[o]]})},g=(n)=>{for(let e=0;e<n.length;e++)if(!n[e].length)return[];return n},O=(n,e,t)=>{let i=Object.keys(e);for(let E=0;E<i.length;E++){let s=i[E];if(!Object.hasOwn(t,s))n[s]=["Key not allowed"]}},m=async(n,e,...t)=>y(n,e,t),D=async(n,e,t=A,...i)=>{let E={},s=[],l=Object.keys(e);for(let r=0;r<l.length;r++){let o=l[r],S=e[o],u=n[o];if(Array.isArray(S)){let c=S.map((a)=>y(u,a,i,void 0,t.break_early));if(c.some(f))E[o]=[],s.push(Promise.all(c).then((a)=>{E[o]=g(a)}));else E[o]=g(c)}else{let c=y(u,S,i,void 0,t.break_early);if(f(c))E[o]=[],s.push(c.then((a)=>{E[o]=a}));else E[o]=c}}if(t.strict)O(E,n,e);if(s.length)await Promise.all(s);return E},U=(n,e,...t)=>M(n,e,t),d=(n,e,t,i)=>{let E={},s=Object.keys(e);for(let l=0;l<s.length;l++){let r=s[l],o=e[r];if(Array.isArray(o)){let S=Array(o.length);for(let u=0;u<o.length;u++)S[u]=M(n[r],o[u],i,void 0,t.break_early);E[r]=g(S)}else E[r]=M(n[r],o,i,void 0,t.break_early)}if(t.strict)O(E,n,e);return E},Y=(n,e,t=A,...i)=>d(n,e,t,i),I=(n,e,...t)=>L(e)?m(n,e,...t):M(n,e,t),P=(n)=>{let e=Object.keys(n);for(let t=0;t<e.length;t++)if(R(n[e[t]]))return!0;return!1},T=(n,e,t=A,...i)=>P(e)?D(n,e,t,...i):d(n,e,t,i),h=(n,e,t,i)=>{let E=[],{keys:s,fns:l}=e;for(let r=0;r<l.length;r++){try{let o=k(l[r],n,t);if(!o||f(o))E.push(s[r])}catch{E.push(s[r])}if(i&&E.length)return E}return E},H=(n)=>{let e={...n},t=Object.keys(e);return{keys:t,rules:e,fns:t.map((i)=>e[i])}},B=(n,e=A)=>{let t=H(n),i=Boolean(e.break_early);if(L(t.rules))return async(E,...s)=>y(E,t.rules,s,t.keys,i);return(E,...s)=>h(E,t,s,i)},K=(n,e=A)=>{let t=Object.keys(n).map((s)=>{let l=n[s];return Array.isArray(l)?{key:s,set:void 0,alternatives:l.map(H)}:{key:s,set:H(l),alternatives:void 0}}),i=e.strict?{...n}:void 0,E=Boolean(e.break_early);if(!P(n))return(s,...l)=>{let r={};for(let o=0;o<t.length;o++){let{key:S,set:u,alternatives:c}=t[o];if(u)r[S]=h(s[S],u,l,E);else{let a=Array(c.length);for(let C=0;C<c.length;C++)a[C]=h(s[S],c[C],l,E);r[S]=g(a)}}if(i)O(r,s,i);return r};return async(s,...l)=>{let r={},o=[];for(let S=0;S<t.length;S++){let{key:u,set:c,alternatives:a}=t[S];if(c){let C=y(s[u],c.rules,l,c.keys,E);if(f(C))r[u]=[],o.push(C.then((_)=>{r[u]=_}));else r[u]=C}else{let C=a.map((_)=>y(s[u],_.rules,l,_.keys,E));if(C.some(f))r[u]=[],o.push(Promise.all(C).then((_)=>{r[u]=g(_)}));else r[u]=g(C)}}if(i)O(r,s,i);if(o.length)await Promise.all(o);return r}};export{B as compileRules,K as compileSchema,T as getSchemaErrors,D as getSchemaErrorsAsync,Y as getSchemaErrorsSync,I as getValueErrors,m as getValueErrorsAsync,U as getValueErrorsSync,R as hasAsyncFunction,w as isAsyncFunction};
@@ -1 +1,2 @@
1
1
  export * from './patterns';
2
+ export * from './rule-sets';
@@ -1 +1 @@
1
- var E=/^[\w.%+-]+@[\w.-]+\.[a-zA-Z]{1,}$/,t=/^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/,_=/^(https?:\/\/)?([^\s$.?#].[^\s]*)\.[a-z]{2,}(\/[^ \t\r\n\v\f]*)?$/i,o=/^(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d(?:\.\d+)?(?:[+-][0-2]\d:[0-5]\d|Z)?)$/,R=/^(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d(?::[0-5]\d(?:\.\d+)?)?)$/,a=/^(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(Z|[+-](?:2[0-3]|[01][0-9]):([0-5][0-9]))?$/,d=/^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/,T=(A)=>`[0-9a-f]{8}-[0-9a-f]{4}-${A}[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}`;var f=new RegExp(`^(?:${T("[1-8]")}|0{8}-0{4}-0{4}-0{4}-0{12}|f{8}-f{4}-f{4}-f{4}-f{12})$`,"i"),e=new RegExp(`^${T("4")}$`,"i"),n=new RegExp("^(?:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.){3}(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)$"),p=new RegExp("^(?:"+["(?:[0-9a-f]{1,4}:){7}[0-9a-f]{1,4}","(?:[0-9a-f]{1,4}:){1,7}:","(?:[0-9a-f]{1,4}:){1,6}:[0-9a-f]{1,4}","(?:[0-9a-f]{1,4}:){1,5}(?::[0-9a-f]{1,4}){1,2}","(?:[0-9a-f]{1,4}:){1,4}(?::[0-9a-f]{1,4}){1,3}","(?:[0-9a-f]{1,4}:){1,3}(?::[0-9a-f]{1,4}){1,4}","(?:[0-9a-f]{1,4}:){1,2}(?::[0-9a-f]{1,4}){1,5}","[0-9a-f]{1,4}:(?::[0-9a-f]{1,4}){1,6}",":(?:(?::[0-9a-f]{1,4}){1,7}|:)","::(?:ffff(?::0{1,4})?:)?(?:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.){3}(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)","(?:[0-9a-f]{1,4}:){1,4}:(?:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.){3}(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)"].join("|")+")$","i"),P=/^[0-9a-f]{2}([:-])(?:[0-9a-f]{2}\1){4}[0-9a-f]{2}$/i,s=/^#([0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i,x=/^[a-z0-9]+(-[a-z0-9]+)*$/,N=/^\+[1-9]\d{1,14}$/,r=/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/,c=/^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)$/,I=/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/,S=/[a-z]/,C=/[A-Z]/,z=/\d/,O=/[^\w\s]/;export{c as BASE64_PATTERN,z as CONTAINS_DIGIT_CHARACTER_PATTERN,S as CONTAINS_LOWERCASE_CHARACTER_PATTERN,O as CONTAINS_SYMBOL_CHARACTER_PATTERN,C as CONTAINS_UPPERCASE_CHARACTER_PATTERN,N as E164_PHONE_PATTERN,E as EMAIL_PATTERN,s as HEX_COLOR_PATTERN,n as IPV4_PATTERN,p as IPV6_PATTERN,R as ISO_8601_DATETIME_PATTERN,o as ISO_8601_DATETIME_PATTERN_STRICT,d as ISO_8601_DATE_PATTERN,a as ISO_8601_TIME_PATTERN,I as JWT_PATTERN,P as MAC_ADDRESS_PATTERN,t as MYSQL_TIMESTAMP_PATTERN,r as SEMVER_PATTERN,x as SLUG_PATTERN,_ as URL_PATTERN,f as UUID_PATTERN,e as UUID_V4_PATTERN};
1
+ var _=/^[\w.%+-]+@[\w.-]+\.[a-zA-Z]{1,}$/,z=/^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/,S=/^(https?:\/\/)?([^\s$.?#].[^\s]*)\.[a-z]{2,}(\/[^ \t\r\n\v\f]*)?$/i,T=/^(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d(?:\.\d+)?(?:[+-][0-2]\d:[0-5]\d|Z)?)$/,v=/^(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d(?::[0-5]\d(?:\.\d+)?)?)$/,A=/^(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(Z|[+-](?:2[0-3]|[01][0-9]):([0-5][0-9]))?$/,c=/^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/,u=(n)=>`[0-9a-f]{8}-[0-9a-f]{4}-${n}[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}`;var N=new RegExp(`^(?:${u("[1-8]")}|0{8}-0{4}-0{4}-0{4}-0{12}|f{8}-f{4}-f{4}-f{4}-f{12})$`,"i"),d=new RegExp(`^${u("4")}$`,"i"),p=new RegExp("^(?:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.){3}(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)$"),U=new RegExp("^(?:"+["(?:[0-9a-f]{1,4}:){7}[0-9a-f]{1,4}","(?:[0-9a-f]{1,4}:){1,7}:","(?:[0-9a-f]{1,4}:){1,6}:[0-9a-f]{1,4}","(?:[0-9a-f]{1,4}:){1,5}(?::[0-9a-f]{1,4}){1,2}","(?:[0-9a-f]{1,4}:){1,4}(?::[0-9a-f]{1,4}){1,3}","(?:[0-9a-f]{1,4}:){1,3}(?::[0-9a-f]{1,4}){1,4}","(?:[0-9a-f]{1,4}:){1,2}(?::[0-9a-f]{1,4}){1,5}","[0-9a-f]{1,4}:(?::[0-9a-f]{1,4}){1,6}",":(?:(?::[0-9a-f]{1,4}){1,7}|:)","::(?:ffff(?::0{1,4})?:)?(?:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.){3}(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)","(?:[0-9a-f]{1,4}:){1,4}:(?:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.){3}(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)"].join("|")+")$","i"),i=/^[0-9a-f]{2}([:-])(?:[0-9a-f]{2}\1){4}[0-9a-f]{2}$/i,L=/^#([0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i,C=/^[a-z0-9]+(-[a-z0-9]+)*$/,f=/^\+[1-9]\d{1,14}$/,x=/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/,l=/^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)$/,O=/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/,I=/[a-z]/,P=/[A-Z]/,y=/\d/,m=/[^\w\s]/;var b=(n)=>typeof n==="string"||n!==null&&typeof n==="object"&&("length"in n)&&typeof n.length==="number",s=(n)=>(e)=>typeof e===n,o=(n)=>(e)=>typeof e==="string"&&n.test(e);var R=(n=1/0)=>(e)=>b(e)&&e.length<=n,w=(n=0)=>(e)=>b(e)&&e.length>=n;var a="Must be a string",t=(n,e)=>Object.freeze({[a]:s("string"),[n]:o(e)}),k=(n,e,E)=>{let r=new Date(0);return r.setUTCFullYear(n,e-1,E),r.getUTCFullYear()===n&&r.getUTCMonth()===e-1&&r.getUTCDate()===E},Y=(n)=>{let[e,E,r]=n.slice(0,10).split("-").map(Number);return k(e,E,r)},V=/([+-])(\d\d):\d\d$/,h=(n)=>{let e=V.exec(n.slice(19))?.[2];return Y(n)&&Number(n.slice(11,13))<24&&(e===void 0||Number(e)<24)},g=254,M=128,B=Object.freeze({[a]:s("string"),[`Must be at most ${g} characters`]:R(g),"Must be a valid email address":o(_)}),j=t("Must be a valid URL",S),F=t("Must be a valid UUID",N),X=t("Must be a valid version 4 UUID",d),J=t("Must be a valid IPv4 address",p),q=t("Must be a valid IPv6 address",U),Q=t("Must be a valid MAC address",i),K=t("Must be a valid hex color",L),nn=t("Must be a valid slug",C),en=t("Must be a valid E.164 phone number",f),tn=t("Must be a valid semantic version",x),on=t("Must be valid base64",l),rn=t("Must be a valid JWT",O),sn=t("Must be a valid ISO 8601 time",A),an=Object.freeze({[a]:s("string"),"Must be a valid ISO 8601 date":(n)=>typeof n==="string"&&c.test(n)&&Y(n)}),En=Object.freeze({[a]:s("string"),"Must be a valid ISO 8601 date and time":(n)=>typeof n==="string"&&T.test(n)&&h(n)}),D=Object.freeze({[a]:s("string"),"Must be at least 8 characters":w(8),[`Must be at most ${M} characters`]:R(M),"Must contain at least one lower case character":o(I),"Must contain at least one upper case character":o(P)}),Rn=Object.freeze({...D,"Must contain at least one digit":o(y),"Must contain at least one symbol":o(m)});export{l as BASE64_PATTERN,on as BASE64_RULES,y as CONTAINS_DIGIT_CHARACTER_PATTERN,I as CONTAINS_LOWERCASE_CHARACTER_PATTERN,m as CONTAINS_SYMBOL_CHARACTER_PATTERN,P as CONTAINS_UPPERCASE_CHARACTER_PATTERN,f as E164_PHONE_PATTERN,en as E164_PHONE_RULES,_ as EMAIL_PATTERN,B as EMAIL_RULES,L as HEX_COLOR_PATTERN,K as HEX_COLOR_RULES,p as IPV4_PATTERN,J as IPV4_RULES,U as IPV6_PATTERN,q as IPV6_RULES,v as ISO_8601_DATETIME_PATTERN,T as ISO_8601_DATETIME_PATTERN_STRICT,En as ISO_8601_DATETIME_RULES,c as ISO_8601_DATE_PATTERN,an as ISO_8601_DATE_RULES,A as ISO_8601_TIME_PATTERN,sn as ISO_8601_TIME_RULES,O as JWT_PATTERN,rn as JWT_RULES,i as MAC_ADDRESS_PATTERN,Q as MAC_ADDRESS_RULES,z as MYSQL_TIMESTAMP_PATTERN,D as PASSWORD_RULES,x as SEMVER_PATTERN,tn as SEMVER_RULES,C as SLUG_PATTERN,nn as SLUG_RULES,Rn as STRONG_PASSWORD_RULES,S as URL_PATTERN,j as URL_RULES,N as UUID_PATTERN,F as UUID_RULES,d as UUID_V4_PATTERN,X as UUID_V4_RULES};
@@ -0,0 +1,19 @@
1
+ import type { RULES_SYNC } from '../types';
2
+ export declare const EMAIL_RULES: Readonly<RULES_SYNC>;
3
+ export declare const URL_RULES: Readonly<RULES_SYNC>;
4
+ export declare const UUID_RULES: Readonly<RULES_SYNC>;
5
+ export declare const UUID_V4_RULES: Readonly<RULES_SYNC>;
6
+ export declare const IPV4_RULES: Readonly<RULES_SYNC>;
7
+ export declare const IPV6_RULES: Readonly<RULES_SYNC>;
8
+ export declare const MAC_ADDRESS_RULES: Readonly<RULES_SYNC>;
9
+ export declare const HEX_COLOR_RULES: Readonly<RULES_SYNC>;
10
+ export declare const SLUG_RULES: Readonly<RULES_SYNC>;
11
+ export declare const E164_PHONE_RULES: Readonly<RULES_SYNC>;
12
+ export declare const SEMVER_RULES: Readonly<RULES_SYNC>;
13
+ export declare const BASE64_RULES: Readonly<RULES_SYNC>;
14
+ export declare const JWT_RULES: Readonly<RULES_SYNC>;
15
+ export declare const ISO_8601_TIME_RULES: Readonly<RULES_SYNC>;
16
+ export declare const ISO_8601_DATE_RULES: Readonly<RULES_SYNC>;
17
+ export declare const ISO_8601_DATETIME_RULES: Readonly<RULES_SYNC>;
18
+ export declare const PASSWORD_RULES: Readonly<RULES_SYNC>;
19
+ export declare const STRONG_PASSWORD_RULES: Readonly<RULES_SYNC>;
package/lib/types.d.ts CHANGED
@@ -1,48 +1,93 @@
1
1
  /**
2
- * A synchronous validation rule.
2
+ * Types for rules, rule sets, schemas and validation results, plus the type-level machinery
3
+ * that infers whether validation is sync or async from the rules' declared return types.
4
+ *
5
+ * @module
6
+ */
7
+ /**
8
+ * A synchronous validation rule. It passes by returning `true` (any truthy value counts)
9
+ * and fails by returning `false` or throwing.
3
10
  *
4
11
  * @template V type of the value being validated (defaults to `unknown`)
5
12
  * @template O tuple type of any extra "overload" arguments passed through by the caller
6
13
  */
7
14
  export type RULE_SYNC<V = unknown, O extends unknown[] = unknown[]> = (value: V, ...overload: O) => boolean;
8
15
  /**
9
- * An asynchronous validation rule.
16
+ * An asynchronous validation rule. It passes by resolving to `true` (any truthy value counts)
17
+ * and fails by resolving to `false` or rejecting.
10
18
  *
11
19
  * @template V type of the value being validated (defaults to `unknown`)
12
20
  * @template O tuple type of any extra "overload" arguments passed through by the caller
13
21
  */
14
22
  export type RULE_ASYNC<V = unknown, O extends unknown[] = unknown[]> = (value: V, ...overload: O) => Promise<boolean>;
15
23
  /**
16
- * A validation rule which may be either synchronous or asynchronous.
24
+ * A validation rule which may be either synchronous or asynchronous. Annotating a rule with
25
+ * this type means its sync/async-ness can't be inferred, so results are typed as a union.
17
26
  *
18
27
  * @template V type of the value being validated (defaults to `unknown`)
19
28
  * @template O tuple type of any extra "overload" arguments passed through by the caller
20
29
  */
21
30
  export type RULE<V = unknown, O extends unknown[] = unknown[]> = (value: V, ...overload: O) => boolean | Promise<boolean>;
22
- /** A named set of synchronous rules. Keys are the error messages returned on failure. */
31
+ /**
32
+ * A set of synchronous rules. Each key is the error reported when its rule fails, and
33
+ * errors are reported in key order.
34
+ *
35
+ * @template V type of the value being validated (defaults to `unknown`)
36
+ * @template O tuple type of any extra "overload" arguments passed through by the caller
37
+ */
23
38
  export type RULES_SYNC<V = unknown, O extends unknown[] = unknown[]> = {
24
39
  [rule_name: string]: RULE_SYNC<V, O>;
25
40
  };
26
- /** A named set of rules, any of which may be asynchronous. Keys are the error messages returned on failure. */
41
+ /**
42
+ * A set of rules, any of which may be asynchronous. Each key is the error reported when its
43
+ * rule fails, and errors are reported in key order.
44
+ *
45
+ * @template V type of the value being validated (defaults to `unknown`)
46
+ * @template O tuple type of any extra "overload" arguments passed through by the caller
47
+ */
27
48
  export type RULES<V = unknown, O extends unknown[] = unknown[]> = {
28
49
  [rule_name: string]: RULE<V, O>;
29
50
  };
30
51
  /**
31
- * Maps object keys to a rule set, or to an array of alternative rule sets
32
- * (the value passes if it satisfies *any* of the alternatives).
52
+ * A schema whose rules are all synchronous. Maps each object key to a rule set, or to an
53
+ * array of alternative rule sets (the value passes if it satisfies *any* of the alternatives).
33
54
  */
34
55
  export type SCHEMA_SYNC = {
35
56
  [key: string]: RULES_SYNC | RULES_SYNC[];
36
57
  };
58
+ /**
59
+ * A schema whose rules may be asynchronous. Maps each object key to a rule set, or to an
60
+ * array of alternative rule sets (the value passes if it satisfies *any* of the alternatives).
61
+ */
37
62
  export type SCHEMA = {
38
63
  [key: string]: RULES | RULES[];
39
64
  };
65
+ /** Options for running a rule set. */
66
+ export type RULES_OPTIONS = {
67
+ /**
68
+ * Stop at the first failing rule and report only that error, instead of running every rule
69
+ * and reporting all of them. Defaults to `false`.
70
+ *
71
+ * Sync rules run first, in rule order, stopping at the first failure. `async` rules only run
72
+ * if every sync rule passed; they then run concurrently, and the first failure in rule
73
+ * order is reported. This skips expensive checks (long regexes, database lookups) on input
74
+ * that has already failed a cheap one, so put cheap rules first. It doesn't change valid
75
+ * input, where every rule has to run anyway.
76
+ */
77
+ break_early?: boolean;
78
+ };
40
79
  /**
41
- * @arg {boolean} strict --- Don't allow object to have keys not included in the schema
42
- */
43
- export type SCHEMA_OPTIONS = {
80
+ * Options for validating an object against a schema. `break_early` applies to each field
81
+ * separately: every field is still validated, each reporting at most one error per rule set.
82
+ */
83
+ export type SCHEMA_OPTIONS = RULES_OPTIONS & {
84
+ /**
85
+ * Report keys of the object that the schema doesn't have, each with the error
86
+ * `['Key not allowed']`. Defaults to `false`, which ignores them.
87
+ */
44
88
  strict?: boolean;
45
89
  };
90
+ /** An object that can be validated against a schema: any string-keyed object. */
46
91
  export type CHECKABLE_OBJECT = {
47
92
  [key: string]: unknown;
48
93
  };
@@ -71,12 +116,41 @@ export type CHECKED_SCHEMA<S extends SCHEMA = SCHEMA> = Promise<CHECKED_SCHEMA_S
71
116
  * so it can't be determined statically
72
117
  */
73
118
  export type RULE_KIND<R> = R extends (...args: never[]) => infer Ret ? [Ret] extends [boolean] ? 'sync' : [Ret] extends [Promise<boolean>] ? 'async' : 'either' : never;
74
- /** Union of the kinds of every rule in a rule set (or in an array of rule sets). */
119
+ /** Union of the `RULE_KIND`s of every rule in a rule set (or in an array of rule sets). */
75
120
  export type RULES_KIND<R> = R extends readonly (infer E)[] ? RULES_KIND<E> : R extends object ? RULE_KIND<R[keyof R]> : never;
76
- /** Union of the kinds of every rule in a schema. */
121
+ /** Union of the `RULE_KIND`s of every rule in a schema. */
77
122
  export type SCHEMA_KIND<S> = RULES_KIND<S[keyof S]>;
78
123
  /**
79
124
  * Wraps a result type according to the kinds found in a rule set / schema:
80
125
  * any definitely-async rule => `Promise<X>`; all sync => `X`; otherwise `X | Promise<X>`.
126
+ *
127
+ * @template K union of rule kinds, from `RULES_KIND` or `SCHEMA_KIND`
128
+ * @template X the result type to wrap
81
129
  */
82
130
  export type MAYBE_ASYNC<K, X> = 'async' extends K ? Promise<X> : 'either' extends K ? X | Promise<X> : X;
131
+ /**
132
+ * Any rule set, whatever value and overload types its rules accept. Used as the constraint
133
+ * for `compileRules`, so rules with narrowed parameter types can be compiled.
134
+ */
135
+ export type ANY_RULES = {
136
+ [rule_name: string]: (value: never, ...overload: never[]) => boolean | Promise<boolean>;
137
+ };
138
+ /**
139
+ * The value type a rule set accepts: the intersection of what each of its rules accepts,
140
+ * e.g. `string` for a set mixing `(value: unknown)` and `(value: string)` rules.
141
+ */
142
+ export type RULES_VALUE<R> = R[keyof R] extends (value: infer V, ...overload: never[]) => unknown ? V : unknown;
143
+ /**
144
+ * A validator returned by `compileRules`. Returns the keys of the failing rules, as
145
+ * `string[]` when every rule is sync and `Promise<string[]>` when any is async.
146
+ *
147
+ * @template R the compiled rule set's type
148
+ */
149
+ export type COMPILED_RULES<R> = (value: RULES_VALUE<R>, ...overload: unknown[]) => MAYBE_ASYNC<RULES_KIND<R>, string[]>;
150
+ /**
151
+ * A validator returned by `compileSchema`. Returns the same result as `getSchemaErrors`,
152
+ * directly when every rule is sync and as a promise when any is async.
153
+ *
154
+ * @template S the compiled schema's type
155
+ */
156
+ export type COMPILED_SCHEMA<S extends SCHEMA> = (object: CHECKABLE_OBJECT, ...overload: unknown[]) => MAYBE_ASYNC<SCHEMA_KIND<S>, CHECKED_SCHEMA_SYNC<S>>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ivl",
3
- "version": "0.4.5",
3
+ "version": "0.5.0",
4
4
  "author": {
5
5
  "name": "Oskar Voorel",
6
6
  "email": "oskar@voorel.com"
@@ -15,6 +15,7 @@
15
15
  "@types/bun": "^1.4.0",
16
16
  "mitata": "^1.0.34",
17
17
  "typescript": "^7.0.2",
18
+ "valibot": "^1.5.0",
18
19
  "yup": "^1.7.1",
19
20
  "zod": "^4.5.2"
20
21
  },
@@ -51,6 +52,7 @@
51
52
  "build": "rm -rf ./lib && bun run build.mjs && tsc -p tsconfig.build.json",
52
53
  "prepublishOnly": "bun run build",
53
54
  "benchmark": "bun run ./benchmark/index.ts",
55
+ "benchmark:node": "bun build ./benchmark/index.ts --target=node --packages=external --outfile=./benchmark/.dist/index.mjs 1>&2 && node ./benchmark/.dist/index.mjs",
54
56
  "typecheck": "tsc --noEmit -p tsconfig.test.json",
55
57
  "test": "bun test && bun run typecheck",
56
58
  "release": "bun run ./scripts/release.ts"