@warlock.js/seal 5.30.0 → 5.32.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 +46 -7
- package/README.md +57 -9
- package/cjs/index.cjs +41 -29
- package/cjs/index.cjs.map +1 -1
- package/cjs/{object--LXRt5RF.cjs → object-BoVGJ68E.cjs} +43 -24
- package/cjs/object-BoVGJ68E.cjs.map +1 -0
- package/cjs/object.cjs +2 -2
- package/cjs/{required-methods-DYsRO9aZ.cjs → required-methods-BJlnLnwa.cjs} +343 -258
- package/cjs/required-methods-BJlnLnwa.cjs.map +1 -0
- package/cjs/{string-DsqnKoSB.cjs → string-CM8x0u2w.cjs} +46 -44
- package/cjs/string-CM8x0u2w.cjs.map +1 -0
- package/cjs/string.cjs +2 -2
- package/esm/config.d.mts +7 -8
- package/esm/config.d.mts.map +1 -1
- package/esm/config.mjs.map +1 -1
- package/esm/factory/index.d.mts +2 -1
- package/esm/factory/index.mjs +1 -1
- package/esm/factory/validate.d.mts +3 -6
- package/esm/factory/validate.d.mts.map +1 -1
- package/esm/factory/validators.d.mts +16 -13
- package/esm/factory/validators.d.mts.map +1 -1
- package/esm/factory/validators.mjs +7 -1
- package/esm/factory/validators.mjs.map +1 -1
- package/esm/helpers/date-helpers.d.mts +1 -2
- package/esm/helpers/date-helpers.d.mts.map +1 -1
- package/esm/helpers/file.utils.d.mts +2 -4
- package/esm/helpers/file.utils.d.mts.map +1 -1
- package/esm/helpers/get-field-value.d.mts +2 -3
- package/esm/helpers/get-field-value.d.mts.map +1 -1
- package/esm/helpers/get-field-value.mjs +2 -1
- package/esm/helpers/get-field-value.mjs.map +1 -1
- package/esm/helpers/index.d.mts +6 -0
- package/esm/helpers/index.mjs +1 -1
- package/esm/helpers/path-helpers.d.mts +1 -2
- package/esm/helpers/path-helpers.d.mts.map +1 -1
- package/esm/helpers/validation-helpers.d.mts +4 -10
- package/esm/helpers/validation-helpers.d.mts.map +1 -1
- package/esm/index.d.mts +7 -0
- package/esm/mutators/array-mutators.d.mts +6 -7
- package/esm/mutators/array-mutators.d.mts.map +1 -1
- package/esm/mutators/boolean-mutators.d.mts +3 -4
- package/esm/mutators/boolean-mutators.d.mts.map +1 -1
- package/esm/mutators/date-mutators.d.mts +18 -19
- package/esm/mutators/date-mutators.d.mts.map +1 -1
- package/esm/mutators/index.d.mts +8 -0
- package/esm/mutators/index.mjs +1 -1
- package/esm/mutators/number-mutators.d.mts +10 -11
- package/esm/mutators/number-mutators.d.mts.map +1 -1
- package/esm/mutators/object-mutators.d.mts +4 -5
- package/esm/mutators/object-mutators.d.mts.map +1 -1
- package/esm/mutators/object-mutators.mjs +5 -4
- package/esm/mutators/object-mutators.mjs.map +1 -1
- package/esm/mutators/scalar-mutators.d.mts +2 -3
- package/esm/mutators/scalar-mutators.d.mts.map +1 -1
- package/esm/mutators/string-mutators.d.mts +37 -38
- package/esm/mutators/string-mutators.d.mts.map +1 -1
- package/esm/mutators/string-mutators.mjs +5 -3
- package/esm/mutators/string-mutators.mjs.map +1 -1
- package/esm/object.d.mts +3 -5
- package/esm/object.d.mts.map +1 -1
- package/esm/object.mjs.map +1 -1
- package/esm/plugins/plugin-system.d.mts +11 -9
- package/esm/plugins/plugin-system.d.mts.map +1 -1
- package/esm/rules/array/array-rules.d.mts +3 -4
- package/esm/rules/array/array-rules.d.mts.map +1 -1
- package/esm/rules/array/index.d.mts +2 -0
- package/esm/rules/array/index.mjs +1 -1
- package/esm/rules/color/color-rules.d.mts +8 -9
- package/esm/rules/color/color-rules.d.mts.map +1 -1
- package/esm/rules/color/index.d.mts +2 -0
- package/esm/rules/color/index.mjs +1 -1
- package/esm/rules/common/enum.d.mts +5 -6
- package/esm/rules/common/enum.d.mts.map +1 -1
- package/esm/rules/common/equals-field-rules.d.mts +3 -4
- package/esm/rules/common/equals-field-rules.d.mts.map +1 -1
- package/esm/rules/common/index.d.mts +7 -0
- package/esm/rules/common/index.mjs +1 -1
- package/esm/rules/common/instanceof.d.mts +2 -3
- package/esm/rules/common/instanceof.d.mts.map +1 -1
- package/esm/rules/common/literal.d.mts +2 -3
- package/esm/rules/common/literal.d.mts.map +1 -1
- package/esm/rules/common/literal.mjs.map +1 -1
- package/esm/rules/common/type-rules.d.mts +10 -11
- package/esm/rules/common/type-rules.d.mts.map +1 -1
- package/esm/rules/common/unknown-key.d.mts +2 -3
- package/esm/rules/common/unknown-key.d.mts.map +1 -1
- package/esm/rules/conditional/forbidden-if-rules.d.mts +7 -8
- package/esm/rules/conditional/forbidden-if-rules.d.mts.map +1 -1
- package/esm/rules/conditional/index.d.mts +11 -0
- package/esm/rules/conditional/index.mjs +1 -1
- package/esm/rules/conditional/present-if-rules.d.mts +6 -7
- package/esm/rules/conditional/present-if-rules.d.mts.map +1 -1
- package/esm/rules/conditional/present-unless-rules.d.mts +2 -3
- package/esm/rules/conditional/present-unless-rules.d.mts.map +1 -1
- package/esm/rules/conditional/present-with-rules.d.mts +4 -5
- package/esm/rules/conditional/present-with-rules.d.mts.map +1 -1
- package/esm/rules/conditional/present-without-rules.d.mts +4 -5
- package/esm/rules/conditional/present-without-rules.d.mts.map +1 -1
- package/esm/rules/conditional/required-if-rules.d.mts +10 -11
- package/esm/rules/conditional/required-if-rules.d.mts.map +1 -1
- package/esm/rules/conditional/required-unless-rules.d.mts +2 -3
- package/esm/rules/conditional/required-unless-rules.d.mts.map +1 -1
- package/esm/rules/conditional/required-when-rule.d.mts +2 -3
- package/esm/rules/conditional/required-when-rule.d.mts.map +1 -1
- package/esm/rules/conditional/required-with-rules.d.mts +4 -5
- package/esm/rules/conditional/required-with-rules.d.mts.map +1 -1
- package/esm/rules/conditional/required-without-rules.d.mts +4 -5
- package/esm/rules/conditional/required-without-rules.d.mts.map +1 -1
- package/esm/rules/core/equal.d.mts +2 -3
- package/esm/rules/core/equal.d.mts.map +1 -1
- package/esm/rules/core/forbidden.d.mts +2 -3
- package/esm/rules/core/forbidden.d.mts.map +1 -1
- package/esm/rules/core/index.d.mts +6 -0
- package/esm/rules/core/index.mjs +1 -1
- package/esm/rules/core/required.d.mts +3 -4
- package/esm/rules/core/required.d.mts.map +1 -1
- package/esm/rules/core/union.d.mts +10 -4
- package/esm/rules/core/union.d.mts.map +1 -1
- package/esm/rules/core/union.mjs +56 -10
- package/esm/rules/core/union.mjs.map +1 -1
- package/esm/rules/core/when.d.mts +2 -3
- package/esm/rules/core/when.d.mts.map +1 -1
- package/esm/rules/core/when.mjs.map +1 -1
- package/esm/rules/date/date-comparison-rules.d.mts +6 -7
- package/esm/rules/date/date-comparison-rules.d.mts.map +1 -1
- package/esm/rules/date/date-day-rules.d.mts +5 -6
- package/esm/rules/date/date-day-rules.d.mts.map +1 -1
- package/esm/rules/date/date-field-comparison-rules.d.mts +4 -5
- package/esm/rules/date/date-field-comparison-rules.d.mts.map +1 -1
- package/esm/rules/date/date-field-comparison-rules.mjs +6 -3
- package/esm/rules/date/date-field-comparison-rules.mjs.map +1 -1
- package/esm/rules/date/date-period-rules.d.mts +16 -16
- package/esm/rules/date/date-period-rules.d.mts.map +1 -1
- package/esm/rules/date/date-period-rules.mjs +24 -12
- package/esm/rules/date/date-period-rules.mjs.map +1 -1
- package/esm/rules/date/date-relative-rules.d.mts +4 -5
- package/esm/rules/date/date-relative-rules.d.mts.map +1 -1
- package/esm/rules/date/date-relative-rules.mjs +3 -3
- package/esm/rules/date/date-relative-rules.mjs.map +1 -1
- package/esm/rules/date/date-special-rules.d.mts +4 -5
- package/esm/rules/date/date-special-rules.d.mts.map +1 -1
- package/esm/rules/date/date.d.mts +16 -17
- package/esm/rules/date/date.d.mts.map +1 -1
- package/esm/rules/date/date.mjs +4 -2
- package/esm/rules/date/date.mjs.map +1 -1
- package/esm/rules/date/index.d.mts +8 -0
- package/esm/rules/date/index.mjs +1 -1
- package/esm/rules/file/dimensions.d.mts +5 -6
- package/esm/rules/file/dimensions.d.mts.map +1 -1
- package/esm/rules/file/file-size.d.mts +3 -4
- package/esm/rules/file/file-size.d.mts.map +1 -1
- package/esm/rules/file/index.d.mts +3 -0
- package/esm/rules/file/index.mjs +1 -1
- package/esm/rules/index.d.mts +56 -0
- package/esm/rules/index.mjs +1 -1
- package/esm/rules/length/index.d.mts +2 -0
- package/esm/rules/length/index.mjs +1 -1
- package/esm/rules/length/length-rules.d.mts +8 -9
- package/esm/rules/length/length-rules.d.mts.map +1 -1
- package/esm/rules/number/index.d.mts +2 -0
- package/esm/rules/number/index.mjs +1 -1
- package/esm/rules/number/number-rules.d.mts +11 -12
- package/esm/rules/number/number-rules.d.mts.map +1 -1
- package/esm/rules/number/number-rules.mjs +12 -6
- package/esm/rules/number/number-rules.mjs.map +1 -1
- package/esm/rules/scalar/index.mjs +1 -1
- package/esm/rules/string/alpha.d.mts +4 -5
- package/esm/rules/string/alpha.d.mts.map +1 -1
- package/esm/rules/string/credit-card.d.mts +2 -3
- package/esm/rules/string/credit-card.d.mts.map +1 -1
- package/esm/rules/string/email.d.mts +2 -3
- package/esm/rules/string/email.d.mts.map +1 -1
- package/esm/rules/string/id-formats.d.mts +6 -7
- package/esm/rules/string/id-formats.d.mts.map +1 -1
- package/esm/rules/string/index.d.mts +12 -0
- package/esm/rules/string/index.mjs +1 -1
- package/esm/rules/string/ip.d.mts +7 -8
- package/esm/rules/string/ip.d.mts.map +1 -1
- package/esm/rules/string/matches.d.mts +2 -3
- package/esm/rules/string/matches.d.mts.map +1 -1
- package/esm/rules/string/pattern.d.mts +2 -3
- package/esm/rules/string/pattern.d.mts.map +1 -1
- package/esm/rules/string/string-comparison.d.mts +5 -6
- package/esm/rules/string/string-comparison.d.mts.map +1 -1
- package/esm/rules/string/strong-password-rule.d.mts +2 -3
- package/esm/rules/string/strong-password-rule.d.mts.map +1 -1
- package/esm/rules/string/url.d.mts +2 -3
- package/esm/rules/string/url.d.mts.map +1 -1
- package/esm/rules/string/without-whitespace.d.mts +2 -3
- package/esm/rules/string/without-whitespace.d.mts.map +1 -1
- package/esm/standard-schema/index.d.mts +4 -0
- package/esm/standard-schema/json-schema.d.mts +5 -7
- package/esm/standard-schema/json-schema.d.mts.map +1 -1
- package/esm/standard-schema/json-schema.mjs.map +1 -1
- package/esm/standard-schema/map-result.d.mts +2 -3
- package/esm/standard-schema/map-result.d.mts.map +1 -1
- package/esm/standard-schema/map-result.mjs.map +1 -1
- package/esm/standard-schema/types.d.mts +4 -5
- package/esm/standard-schema/types.d.mts.map +1 -1
- package/esm/string.d.mts +2 -3
- package/esm/string.d.mts.map +1 -1
- package/esm/types/conditional-types.d.mts +8 -6
- package/esm/types/conditional-types.d.mts.map +1 -1
- package/esm/types/context-types.d.mts +24 -15
- package/esm/types/context-types.d.mts.map +1 -1
- package/esm/types/data-transformer-types.d.mts +3 -5
- package/esm/types/data-transformer-types.d.mts.map +1 -1
- package/esm/types/date-types.d.mts +2 -3
- package/esm/types/date-types.d.mts.map +1 -1
- package/esm/types/file.types.d.mts +1 -2
- package/esm/types/file.types.d.mts.map +1 -1
- package/esm/types/index.d.mts +11 -0
- package/esm/types/inference-types.d.mts +15 -14
- package/esm/types/inference-types.d.mts.map +1 -1
- package/esm/types/mutator-types.d.mts +9 -7
- package/esm/types/mutator-types.d.mts.map +1 -1
- package/esm/types/result-types.d.mts +12 -7
- package/esm/types/result-types.d.mts.map +1 -1
- package/esm/types/rule-types.d.mts +33 -15
- package/esm/types/rule-types.d.mts.map +1 -1
- package/esm/types/schema-types.d.mts +1 -3
- package/esm/types/schema-types.d.mts.map +1 -1
- package/esm/validators/any-validator.d.mts +1 -3
- package/esm/validators/any-validator.d.mts.map +1 -1
- package/esm/validators/array-validator.d.mts +2 -3
- package/esm/validators/array-validator.d.mts.map +1 -1
- package/esm/validators/array-validator.mjs +3 -3
- package/esm/validators/array-validator.mjs.map +1 -1
- package/esm/validators/base-validator.d.mts +10 -11
- package/esm/validators/base-validator.d.mts.map +1 -1
- package/esm/validators/base-validator.mjs +52 -21
- package/esm/validators/base-validator.mjs.map +1 -1
- package/esm/validators/boolean-validator.d.mts +1 -3
- package/esm/validators/boolean-validator.d.mts.map +1 -1
- package/esm/validators/computed-validator.d.mts +3 -4
- package/esm/validators/computed-validator.d.mts.map +1 -1
- package/esm/validators/computed-validator.mjs +4 -4
- package/esm/validators/computed-validator.mjs.map +1 -1
- package/esm/validators/date-validator.d.mts +2 -3
- package/esm/validators/date-validator.d.mts.map +1 -1
- package/esm/validators/discriminated-union-validator.d.mts +3 -3
- package/esm/validators/discriminated-union-validator.d.mts.map +1 -1
- package/esm/validators/discriminated-union-validator.mjs +1 -1
- package/esm/validators/discriminated-union-validator.mjs.map +1 -1
- package/esm/validators/float-validator.d.mts +1 -3
- package/esm/validators/float-validator.d.mts.map +1 -1
- package/esm/validators/index.d.mts +2 -1
- package/esm/validators/index.mjs +1 -1
- package/esm/validators/instanceof-validator.d.mts +1 -3
- package/esm/validators/instanceof-validator.d.mts.map +1 -1
- package/esm/validators/int-validator.d.mts +1 -3
- package/esm/validators/int-validator.d.mts.map +1 -1
- package/esm/validators/lazy-validator.d.mts +2 -3
- package/esm/validators/lazy-validator.d.mts.map +1 -1
- package/esm/validators/lazy-validator.mjs +1 -1
- package/esm/validators/lazy-validator.mjs.map +1 -1
- package/esm/validators/literal-validator.d.mts +1 -3
- package/esm/validators/literal-validator.d.mts.map +1 -1
- package/esm/validators/managed-validator.d.mts +3 -4
- package/esm/validators/managed-validator.d.mts.map +1 -1
- package/esm/validators/methods/equality-conditional-methods.d.mts +3 -4
- package/esm/validators/methods/equality-conditional-methods.d.mts.map +1 -1
- package/esm/validators/methods/forbidden-methods.d.mts +2 -3
- package/esm/validators/methods/forbidden-methods.d.mts.map +1 -1
- package/esm/validators/methods/present-methods.d.mts +2 -3
- package/esm/validators/methods/present-methods.d.mts.map +1 -1
- package/esm/validators/methods/required-methods.d.mts +3 -4
- package/esm/validators/methods/required-methods.d.mts.map +1 -1
- package/esm/validators/number-validator.d.mts +1 -3
- package/esm/validators/number-validator.d.mts.map +1 -1
- package/esm/validators/number-validator.mjs +12 -8
- package/esm/validators/number-validator.mjs.map +1 -1
- package/esm/validators/numeric-validator.d.mts +1 -3
- package/esm/validators/numeric-validator.d.mts.map +1 -1
- package/esm/validators/object-validator.d.mts +2 -3
- package/esm/validators/object-validator.d.mts.map +1 -1
- package/esm/validators/object-validator.mjs +9 -7
- package/esm/validators/object-validator.mjs.map +1 -1
- package/esm/validators/primitive-validator.d.mts +1 -3
- package/esm/validators/primitive-validator.d.mts.map +1 -1
- package/esm/validators/record-validator.d.mts +2 -3
- package/esm/validators/record-validator.d.mts.map +1 -1
- package/esm/validators/record-validator.mjs +3 -3
- package/esm/validators/record-validator.mjs.map +1 -1
- package/esm/validators/scalar-validator.d.mts +1 -3
- package/esm/validators/scalar-validator.d.mts.map +1 -1
- package/esm/validators/scalar-validator.mjs.map +1 -1
- package/esm/validators/string-validator.d.mts +1 -3
- package/esm/validators/string-validator.d.mts.map +1 -1
- package/esm/validators/tuple-validator.d.mts +2 -3
- package/esm/validators/tuple-validator.d.mts.map +1 -1
- package/esm/validators/tuple-validator.mjs +3 -3
- package/esm/validators/tuple-validator.mjs.map +1 -1
- package/esm/validators/union-validator.d.mts +4 -4
- package/esm/validators/union-validator.d.mts.map +1 -1
- package/esm/validators/union-validator.mjs +3 -1
- package/esm/validators/union-validator.mjs.map +1 -1
- package/llms-full.txt +656 -527
- package/llms.txt +1 -1
- package/package.json +1 -1
- package/skills/bridge-standard-schema/SKILL.md +60 -36
- package/skills/compose-seal-modifiers/SKILL.md +89 -53
- package/skills/define-structural-shape/SKILL.md +41 -27
- package/skills/define-structural-shape/array-methods.md +27 -27
- package/skills/define-structural-shape/object-methods.md +38 -30
- package/skills/extend-seal-with-plugins/SKILL.md +39 -5
- package/skills/generate-json-schema/SKILL.md +13 -13
- package/skills/handle-seal-errors/SKILL.md +23 -23
- package/skills/index.md +4 -1
- package/skills/overview/SKILL.md +10 -2
- package/skills/pick-seal-primitive/SKILL.md +45 -45
- package/skills/pick-seal-primitive/date-methods.md +96 -96
- package/skills/pick-seal-primitive/number-methods.md +55 -55
- package/skills/pick-seal-primitive/string-methods.md +107 -102
- package/skills/seal-basics/SKILL.md +14 -14
- package/cjs/object--LXRt5RF.cjs.map +0 -1
- package/cjs/required-methods-DYsRO9aZ.cjs.map +0 -1
- package/cjs/string-DsqnKoSB.cjs.map +0 -1
|
@@ -4,41 +4,41 @@ The inner validator runs against each element. Failure on any element fails the
|
|
|
4
4
|
|
|
5
5
|
## Length
|
|
6
6
|
|
|
7
|
-
| Method
|
|
8
|
-
|
|
9
|
-
| `.min(n, msg?)`
|
|
10
|
-
| `.minLength(n, msg?)`
|
|
11
|
-
| `.maxLength(n, msg?)`
|
|
12
|
-
| `.length(n, msg?)`
|
|
13
|
-
| `.between(a, b, msg?)`
|
|
14
|
-
| `.lengthBetween(a, b, msg?)` | alias for `.between()` | `minItems: a, maxItems: b` | —
|
|
7
|
+
| Method | Args | JSON Schema | Example |
|
|
8
|
+
| ---------------------------- | ---------------------- | -------------------------- | ------------------------------------ |
|
|
9
|
+
| `.min(n, msg?)` | n/a — see note below | — | use `.minLength(n)` |
|
|
10
|
+
| `.minLength(n, msg?)` | inclusive lower bound | `minItems: n` | `v.array(v.string()).minLength(1)` |
|
|
11
|
+
| `.maxLength(n, msg?)` | inclusive upper bound | `maxItems: n` | `v.array(v.string()).maxLength(10)` |
|
|
12
|
+
| `.length(n, msg?)` | exact length | `minItems=maxItems=n` | `v.array(v.string()).length(3)` |
|
|
13
|
+
| `.between(a, b, msg?)` | inclusive range | `minItems: a, maxItems: b` | `v.array(v.string()).between(1, 10)` |
|
|
14
|
+
| `.lengthBetween(a, b, msg?)` | alias for `.between()` | `minItems: a, maxItems: b` | — |
|
|
15
15
|
|
|
16
16
|
**Note on `.min`/`.max`:** the array validator does not expose `.min`/`.max` directly — use `.minLength`/`.maxLength` (or `.between`/`.length`). This is intentional: `.min`/`.max` would conflict with primitive value-comparison semantics if they ever bled into here.
|
|
17
17
|
|
|
18
18
|
## Uniqueness & sort
|
|
19
19
|
|
|
20
|
-
| Method
|
|
21
|
-
|
|
22
|
-
| `.unique(msg?)`
|
|
20
|
+
| Method | Args | Effect |
|
|
21
|
+
| --------------------------- | ----------------------------- | ----------------------------- |
|
|
22
|
+
| `.unique(msg?)` | — | every element is distinct |
|
|
23
23
|
| `.sorted(direction?, msg?)` | `"asc"` (default) or `"desc"` | array is monotonically sorted |
|
|
24
24
|
|
|
25
25
|
## Mutators (pre-validation reshape)
|
|
26
26
|
|
|
27
|
-
| Method
|
|
28
|
-
|
|
29
|
-
| `.flip()`
|
|
30
|
-
| `.reverse()`
|
|
31
|
-
| `.onlyUnique()`
|
|
32
|
-
| `.sort(direction?, key?)` | `"asc"` (default) or `"desc"`; optional sort key for object items | sort before validation
|
|
27
|
+
| Method | Args | Effect |
|
|
28
|
+
| ------------------------- | ----------------------------------------------------------------- | ------------------------ |
|
|
29
|
+
| `.flip()` | — | reverse the array |
|
|
30
|
+
| `.reverse()` | — | alias for `.flip()` |
|
|
31
|
+
| `.onlyUnique()` | — | dedupe before validation |
|
|
32
|
+
| `.sort(direction?, key?)` | `"asc"` (default) or `"desc"`; optional sort key for object items | sort before validation |
|
|
33
33
|
|
|
34
34
|
`onlyUnique()` and `unique()` differ: the mutator silently dedupes; the rule fails when duplicates are present. Pick by intent — coerce vs reject.
|
|
35
35
|
|
|
36
36
|
## Inner validator notes
|
|
37
37
|
|
|
38
38
|
```ts
|
|
39
|
-
v.array(v.string().email())
|
|
40
|
-
v.array(userSchema)
|
|
41
|
-
v.array(v.array(v.int()))
|
|
39
|
+
v.array(v.string().email()); // every element must be a valid email
|
|
40
|
+
v.array(userSchema); // every element must satisfy userSchema
|
|
41
|
+
v.array(v.array(v.int())); // matrix-ish — Infer is recursive
|
|
42
42
|
```
|
|
43
43
|
|
|
44
44
|
Element validators run with element-level context. `path` becomes `${parent}.${index}` for error messages. Cross-field rules inside element schemas resolve to that element's fields, not the array.
|
|
@@ -56,23 +56,23 @@ Element validators run with element-level context. `path` becomes `${parent}.${i
|
|
|
56
56
|
|
|
57
57
|
```ts
|
|
58
58
|
// Tags
|
|
59
|
-
v.array(v.string().min(1)).unique().between(1, 10)
|
|
59
|
+
v.array(v.string().min(1)).unique().between(1, 10);
|
|
60
60
|
|
|
61
61
|
// User list
|
|
62
|
-
v.array(userSchema).minLength(1)
|
|
62
|
+
v.array(userSchema).minLength(1);
|
|
63
63
|
|
|
64
64
|
// Pre-sorted, deduped
|
|
65
|
-
v.array(v.string()).onlyUnique().sort("asc")
|
|
65
|
+
v.array(v.string()).onlyUnique().sort("asc");
|
|
66
66
|
|
|
67
67
|
// Reverse-emit
|
|
68
|
-
v.array(v.string()).flip()
|
|
68
|
+
v.array(v.string()).flip();
|
|
69
69
|
|
|
70
70
|
// Matrix
|
|
71
|
-
v.array(v.array(v.number())).minLength(1)
|
|
71
|
+
v.array(v.array(v.number())).minLength(1);
|
|
72
72
|
|
|
73
73
|
// Sorted ids
|
|
74
|
-
v.array(v.string().uuid()).sorted("asc").unique()
|
|
74
|
+
v.array(v.string().uuid()).sorted("asc").unique();
|
|
75
75
|
|
|
76
76
|
// Optional list with default
|
|
77
|
-
v.array(v.string()).default([]).optional()
|
|
77
|
+
v.array(v.string()).default([]).optional();
|
|
78
78
|
```
|
|
@@ -9,10 +9,12 @@ These return a new `ObjectValidator` with a transformed schema. The TypeScript i
|
|
|
9
9
|
### `.extend(schemaOrValidator)` — add fields, keep config
|
|
10
10
|
|
|
11
11
|
```ts
|
|
12
|
-
const baseUser = v
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
const baseUser = v
|
|
13
|
+
.object({
|
|
14
|
+
name: v.string(),
|
|
15
|
+
email: v.string().email(),
|
|
16
|
+
})
|
|
17
|
+
.allowUnknown();
|
|
16
18
|
|
|
17
19
|
const adminUser = baseUser.extend({
|
|
18
20
|
role: v.string().oneOf(["admin", "superadmin"]),
|
|
@@ -26,10 +28,12 @@ Accepts a plain schema object **or** another `ObjectValidator` (only its schema
|
|
|
26
28
|
|
|
27
29
|
```ts
|
|
28
30
|
const base = v.object({ name: v.string() }).allowUnknown();
|
|
29
|
-
const audit = v
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
const audit = v
|
|
32
|
+
.object({
|
|
33
|
+
createdAt: v.date(),
|
|
34
|
+
updatedAt: v.date(),
|
|
35
|
+
})
|
|
36
|
+
.stripUnknown();
|
|
33
37
|
|
|
34
38
|
const merged = base.merge(audit);
|
|
35
39
|
// type: { name; createdAt; updatedAt }
|
|
@@ -66,8 +70,8 @@ Returns `ObjectValidator<Omit<TSchema, K>>`. Inverse of `.pick`. Keeps original
|
|
|
66
70
|
### `.partial(...keys?)` — mark fields optional
|
|
67
71
|
|
|
68
72
|
```ts
|
|
69
|
-
fullUser.partial()
|
|
70
|
-
fullUser.partial("password")
|
|
73
|
+
fullUser.partial(); // every field becomes optional
|
|
74
|
+
fullUser.partial("password"); // only `password` becomes optional
|
|
71
75
|
```
|
|
72
76
|
|
|
73
77
|
Walks the schema and applies `.optional()` to each named field (or all if no keys given). `Infer<>` makes those keys optional.
|
|
@@ -75,7 +79,7 @@ Walks the schema and applies `.optional()` to each named field (or all if no key
|
|
|
75
79
|
### `.requiredFields(...keys?)` — mark fields required
|
|
76
80
|
|
|
77
81
|
```ts
|
|
78
|
-
const partialUser = fullUser.partial();
|
|
82
|
+
const partialUser = fullUser.partial(); // all optional
|
|
79
83
|
const updateUser = partialUser.requiredFields("id", "email");
|
|
80
84
|
// id and email are required again, others stay optional
|
|
81
85
|
```
|
|
@@ -86,29 +90,31 @@ Inverse of `.partial`.
|
|
|
86
90
|
|
|
87
91
|
By default, extra keys in input are silently dropped from `data` (no error, no forward).
|
|
88
92
|
|
|
89
|
-
| Method
|
|
90
|
-
|
|
91
|
-
| `.allowUnknown(allow = true)` | extra **direct-child** keys forward as-is
|
|
92
|
-
| `.stripUnknown()`
|
|
93
|
-
| `.allow(...keys)`
|
|
93
|
+
| Method | Effect |
|
|
94
|
+
| ----------------------------- | ------------------------------------------------------- |
|
|
95
|
+
| `.allowUnknown(allow = true)` | extra **direct-child** keys forward as-is |
|
|
96
|
+
| `.stripUnknown()` | explicit drop (mutator-based — affects `data` shape) |
|
|
97
|
+
| `.allow(...keys)` | whitelist specific extras to forward without validation |
|
|
94
98
|
|
|
95
99
|
`.allowUnknown()` only affects direct children — nested objects keep their own policies. For a fully permissive object including nested children, set `.allowUnknown()` on each level.
|
|
96
100
|
|
|
97
101
|
### Output DTOs — stripping extras instead of rejecting them
|
|
98
102
|
|
|
99
|
-
The default is to **reject** unknown keys, which is what you want for inbound payloads. For an
|
|
103
|
+
The default is to **reject** unknown keys, which is what you want for inbound payloads. For an _outbound_ DTO — where the record legitimately carries internal fields you simply don't want to expose — use `.stripUnknown()`, which drops them and returns the clean object:
|
|
100
104
|
|
|
101
105
|
```ts
|
|
102
|
-
const publicArticle = v
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
+
const publicArticle = v
|
|
107
|
+
.object({
|
|
108
|
+
id: v.string(),
|
|
109
|
+
title: v.string(),
|
|
110
|
+
})
|
|
111
|
+
.stripUnknown();
|
|
106
112
|
|
|
107
113
|
const { isValid, data } = await v.validate(publicArticle, record);
|
|
108
114
|
// record had version / authorId / status; data has only id + title
|
|
109
115
|
```
|
|
110
116
|
|
|
111
|
-
Without `.stripUnknown()` the same record fails with
|
|
117
|
+
Without `.stripUnknown()` the same record fails with _"The schema contains unknown keys: …"_ rather than being cleaned.
|
|
112
118
|
|
|
113
119
|
:::note[`data` is `undefined` on failure — since 4.9.2]
|
|
114
120
|
A failed validation returns `data: undefined`, never the input it rejected. Before 4.9.2 an `object` returned the raw input on failure (while `discriminatedUnion` returned `undefined`), so a caller who read `data` without branching on `isValid` forwarded the exact fields the schema existed to exclude. Always branch on `isValid`.
|
|
@@ -116,8 +122,8 @@ A failed validation returns `data: undefined`, never the input it rejected. Befo
|
|
|
116
122
|
|
|
117
123
|
## Object-level mutators
|
|
118
124
|
|
|
119
|
-
| Method
|
|
120
|
-
|
|
125
|
+
| Method | Args | Effect |
|
|
126
|
+
| ------------------- | -------------- | ------------------------------------------------ |
|
|
121
127
|
| `.trim(recursive?)` | default `true` | recursively trim string values across the object |
|
|
122
128
|
|
|
123
129
|
For per-field mutators, attach on the field validator instead.
|
|
@@ -130,7 +136,7 @@ The cross-field methods (`.sameAs`, `.requiredIf`, `.requiredWith`, etc.) are **
|
|
|
130
136
|
v.object({
|
|
131
137
|
password: v.string(),
|
|
132
138
|
passwordConfirm: v.string().sameAs("password"),
|
|
133
|
-
})
|
|
139
|
+
});
|
|
134
140
|
```
|
|
135
141
|
|
|
136
142
|
If you call cross-field rules outside a `v.object`, sibling resolution silently passes — there's nobody to compare against.
|
|
@@ -138,7 +144,7 @@ If you call cross-field rules outside a `v.object`, sibling resolution silently
|
|
|
138
144
|
## `Infer<>` semantics
|
|
139
145
|
|
|
140
146
|
```ts
|
|
141
|
-
Infer<typeof userSchema
|
|
147
|
+
Infer<typeof userSchema>;
|
|
142
148
|
// { reqField: T; optField?: T }
|
|
143
149
|
```
|
|
144
150
|
|
|
@@ -171,10 +177,12 @@ const timestamps = v.object({
|
|
|
171
177
|
const userWithAudit = baseUser.extend(timestamps);
|
|
172
178
|
|
|
173
179
|
// Permissive container that forwards extras
|
|
174
|
-
const eventEnvelope = v
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}).allowUnknown()
|
|
180
|
+
const eventEnvelope = v
|
|
181
|
+
.object({
|
|
182
|
+
type: v.literal("user.created", "user.updated"),
|
|
183
|
+
payload: v.object({}).allowUnknown(),
|
|
184
|
+
})
|
|
185
|
+
.allowUnknown();
|
|
178
186
|
|
|
179
187
|
// Confirmation pattern with omit
|
|
180
188
|
const signupSchema = v.object({
|
|
@@ -21,7 +21,7 @@ Seal exposes a plugin system so you can add validator methods without forking th
|
|
|
21
21
|
import type { SealPlugin } from "@warlock.js/seal";
|
|
22
22
|
|
|
23
23
|
type SealPlugin = {
|
|
24
|
-
name: string;
|
|
24
|
+
name: string; // unique identifier — duplicates warn and skip install
|
|
25
25
|
version?: string;
|
|
26
26
|
description?: string;
|
|
27
27
|
install: (context: { name: string; version?: string }) => void | Promise<void>;
|
|
@@ -59,8 +59,8 @@ import { registerPlugin, unregisterPlugin, hasPlugin, getInstalledPlugins } from
|
|
|
59
59
|
await registerPlugin(slugPlugin);
|
|
60
60
|
// console.warn if "slug" is already installed; otherwise install() runs and the plugin is tracked.
|
|
61
61
|
|
|
62
|
-
hasPlugin("slug");
|
|
63
|
-
getInstalledPlugins();
|
|
62
|
+
hasPlugin("slug"); // → true
|
|
63
|
+
getInstalledPlugins(); // → [slugPlugin]
|
|
64
64
|
|
|
65
65
|
await unregisterPlugin("slug");
|
|
66
66
|
// runs slugPlugin.uninstall?.(); removes from registry.
|
|
@@ -163,8 +163,8 @@ For a method that creates a **new validator** (not chained from an existing one)
|
|
|
163
163
|
## Introspection — checking what's loaded
|
|
164
164
|
|
|
165
165
|
```ts
|
|
166
|
-
hasPlugin("slug");
|
|
167
|
-
getInstalledPlugins();
|
|
166
|
+
hasPlugin("slug"); // boolean
|
|
167
|
+
getInstalledPlugins(); // SealPlugin[]
|
|
168
168
|
```
|
|
169
169
|
|
|
170
170
|
Use these in startup diagnostics or in tests that need to assert a plugin is registered before exercising a method that depends on it.
|
|
@@ -176,3 +176,37 @@ Use these in startup diagnostics or in tests that need to assert a plugin is reg
|
|
|
176
176
|
- Don't make the plugin's `install` function depend on shared state mutable from elsewhere. Plugins should be idempotent — installing twice (or registering across hot-reloads) should not break anything.
|
|
177
177
|
- Don't ship a plugin that overrides a built-in method without a clear reason. If you must, `uninstall` should restore the original — but the better path is a different method name.
|
|
178
178
|
- Don't author one plugin per method. Group related methods (e.g. "country-specific validators") into one plugin so the install/uninstall lifecycle is coherent.
|
|
179
|
+
|
|
180
|
+
## Returning validated rule output
|
|
181
|
+
|
|
182
|
+
A custom rule's success result can contain `data` to replace the current value:
|
|
183
|
+
`{ isValid: true, data: replacement }`. Later rules, transformers and refinements
|
|
184
|
+
receive that replacement. Omitting `data` preserves the current value; a present
|
|
185
|
+
`data: undefined` explicitly clears it. This is also how a union preserves the
|
|
186
|
+
selected branch's validated defaults, catches and child transformations.
|
|
187
|
+
|
|
188
|
+
## Reporting several errors from one rule
|
|
189
|
+
|
|
190
|
+
A failed rule result can carry `errors: [{ error, path }, ...]` to report more than
|
|
191
|
+
one error, each at its own path; every entry gets the rule's `name` as its `type`.
|
|
192
|
+
`v.union` uses this to relay the closest branch's errors. Return as many as fit the
|
|
193
|
+
caller's `context.configurations?.firstErrorOnly` (default `true`: one).
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
import { invalidRule, type SchemaRule } from "@warlock.js/seal";
|
|
197
|
+
|
|
198
|
+
const bothDates: SchemaRule = {
|
|
199
|
+
name: "bothDates",
|
|
200
|
+
async validate(value: { from?: unknown; to?: unknown }, context) {
|
|
201
|
+
const missing = (["from", "to"] as const).filter((key) => !value?.[key]);
|
|
202
|
+
if (missing.length === 0) return { isValid: true };
|
|
203
|
+
|
|
204
|
+
const failed = invalidRule(this, context);
|
|
205
|
+
const errors = missing.map((key) => ({
|
|
206
|
+
error: `The ${key} date is required`,
|
|
207
|
+
path: context.path ? `${context.path}.${key}` : key,
|
|
208
|
+
}));
|
|
209
|
+
return { ...failed, errors };
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
```
|
|
@@ -29,19 +29,19 @@ userSchema.toJsonSchema("draft-2020-12");
|
|
|
29
29
|
|
|
30
30
|
```ts
|
|
31
31
|
type JsonSchemaTarget =
|
|
32
|
-
| "draft-2020-12"
|
|
33
|
-
| "draft-07"
|
|
34
|
-
| "openapi-3.0"
|
|
35
|
-
| "openai-strict"
|
|
32
|
+
| "draft-2020-12" // default — modern JSON Schema
|
|
33
|
+
| "draft-07" // older tooling, Swagger 2.0
|
|
34
|
+
| "openapi-3.0" // uses { nullable: true } instead of type unions
|
|
35
|
+
| "openai-strict"; // OpenAI Structured Outputs strict mode
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
Pick by consumer:
|
|
39
39
|
|
|
40
|
-
| Consumer
|
|
41
|
-
|
|
|
42
|
-
| Modern tooling, no specific reason otherwise
|
|
43
|
-
| Swagger 2.0 / older OpenAPI / older form builders
|
|
44
|
-
| OpenAPI 3.0 spec (uses `nullable: true`)
|
|
40
|
+
| Consumer | Target |
|
|
41
|
+
| --------------------------------------------------------------- | ----------------- |
|
|
42
|
+
| Modern tooling, no specific reason otherwise | `"draft-2020-12"` |
|
|
43
|
+
| Swagger 2.0 / older OpenAPI / older form builders | `"draft-07"` |
|
|
44
|
+
| OpenAPI 3.0 spec (uses `nullable: true`) | `"openapi-3.0"` |
|
|
45
45
|
| OpenAI `response_format: { type: "json_schema", strict: true }` | `"openai-strict"` |
|
|
46
46
|
|
|
47
47
|
## OpenAI structured outputs (`openai-strict`)
|
|
@@ -122,8 +122,8 @@ Cleanly mapped:
|
|
|
122
122
|
|
|
123
123
|
Some seal constructs have no JSON Schema representation:
|
|
124
124
|
|
|
125
|
-
- **Cross-field rules** (`sameAs`, `requiredIf`, `requiredWith`, etc.) — runtime-only. The generated schema describes the
|
|
126
|
-
- **Transformers and mutators** — output reshaping doesn't appear in the schema; the schema reflects
|
|
125
|
+
- **Cross-field rules** (`sameAs`, `requiredIf`, `requiredWith`, etc.) — runtime-only. The generated schema describes the _shape_, not the inter-field invariants.
|
|
126
|
+
- **Transformers and mutators** — output reshaping doesn't appear in the schema; the schema reflects _post-mutator, pre-transformer_ shape (since that's what rules see and what the LLM is asked to produce for `openai-strict`).
|
|
127
127
|
- **`v.computed` / `v.managed`** — **skipped** entirely by the parent `v.object`; they never appear in `properties`. They aren't part of the data contract. (Calling `.toJsonSchema()` directly on one throws — the parent object is responsible for skipping them.)
|
|
128
128
|
- **`v.instanceof(Ctor)`** — produces `{}`. Class identity isn't expressible. For `File`, attach `{ type: "string", format: "binary" }` manually after generation if needed for OpenAPI.
|
|
129
129
|
- **`v.any()`** — produces `{}` deliberately (any value is valid).
|
|
@@ -132,10 +132,10 @@ Boolean rules `accepted` / `declined` and similar coercion-style rules are also
|
|
|
132
132
|
|
|
133
133
|
## When the generated schema rejects valid data
|
|
134
134
|
|
|
135
|
-
If the schema validator itself accepts data but the
|
|
135
|
+
If the schema validator itself accepts data but the _generated_ JSON Schema rejects the same data downstream, the cause is usually one of:
|
|
136
136
|
|
|
137
137
|
- **Cross-field rule.** The generated schema doesn't enforce it, but a separate consumer might. (Or the runtime check fired at a different stage.)
|
|
138
|
-
- **Transformer running on the wrong side.** The schema describes the input shape (or strict-mode normalized form). If your transformer reshapes `Date` to ISO string for `data`, the
|
|
138
|
+
- **Transformer running on the wrong side.** The schema describes the input shape (or strict-mode normalized form). If your transformer reshapes `Date` to ISO string for `data`, the _input_ still needs to be a Date-parseable thing.
|
|
139
139
|
- **`openai-strict` quirk.** Optional fields show as `["T", "null"]` rather than omitted — if the model omits them entirely (without sending `null`), strict mode fails. The fix is on the prompt side: tell the model to send `null` for unused fields.
|
|
140
140
|
|
|
141
141
|
## Cost note
|
|
@@ -9,13 +9,13 @@ description: 'Read `ValidationResult` — `isValid`, `errors[]`, `data`. Branch
|
|
|
9
9
|
|
|
10
10
|
```ts
|
|
11
11
|
type TypedValidationResult<TData> =
|
|
12
|
-
| { isValid: true; data: TData; errors: ValidationError[] }
|
|
12
|
+
| { isValid: true; data: TData; errors: ValidationError[] } // TData = Infer.Output<typeof schema>
|
|
13
13
|
| { isValid: false; data: undefined; errors: ValidationError[] };
|
|
14
14
|
|
|
15
15
|
type ValidationError = {
|
|
16
|
-
type: string;
|
|
17
|
-
error: string;
|
|
18
|
-
input: string;
|
|
16
|
+
type: string; // rule type that failed — "required", "email", "min", "string", ...
|
|
17
|
+
error: string; // resolved message (translated, with attribute substitution)
|
|
18
|
+
input: string; // input field name — "email" or "address.city"
|
|
19
19
|
};
|
|
20
20
|
```
|
|
21
21
|
|
|
@@ -34,7 +34,7 @@ const schema = v.object({
|
|
|
34
34
|
const result = await validate(schema, input);
|
|
35
35
|
|
|
36
36
|
if (result.isValid) {
|
|
37
|
-
return result.data;
|
|
37
|
+
return result.data; // typed as the inferred shape (post-transformer)
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
return {
|
|
@@ -49,15 +49,13 @@ return {
|
|
|
49
49
|
const result = await validate(schema, input);
|
|
50
50
|
|
|
51
51
|
if (!result.isValid) {
|
|
52
|
-
const emailMissing = result.errors.find(
|
|
53
|
-
e => e.input === "email" && e.type === "required",
|
|
54
|
-
);
|
|
52
|
+
const emailMissing = result.errors.find((e) => e.input === "email" && e.type === "required");
|
|
55
53
|
if (emailMissing) {
|
|
56
54
|
return { redirect: "/signup", reason: "no email" };
|
|
57
55
|
}
|
|
58
56
|
|
|
59
57
|
const ageInvalid = result.errors.find(
|
|
60
|
-
e => e.input === "age" && (e.type === "int" || e.type === "min"),
|
|
58
|
+
(e) => e.input === "age" && (e.type === "int" || e.type === "min"),
|
|
61
59
|
);
|
|
62
60
|
if (ageInvalid) {
|
|
63
61
|
return { error: "Age must be 13 or older" };
|
|
@@ -73,18 +71,20 @@ These are the strings you'll see in `error.type`:
|
|
|
73
71
|
|
|
74
72
|
The `type` is the rule's own `name` field — not the method you called. Several methods map to one shared rule name (e.g. `.min()` / `.minLength()` on a string both surface as `minLength`; `.sameAs()` surfaces as `equalsField`). The table below lists the actual `type` strings:
|
|
75
73
|
|
|
76
|
-
| Type
|
|
77
|
-
|
|
|
78
|
-
| `required`, `present`
|
|
79
|
-
| `requiredIf`, `requiredWith`, `requiredWithout`, `requiredUnless`
|
|
80
|
-
| `string`, `number`, `int`, `float`, `boolean`, `scalar`, `object`, `array`, `date`
|
|
81
|
-
| `minLength`, `maxLength`, `betweenLength`, `length`
|
|
82
|
-
| `min`, `max`, `betweenNumbers`
|
|
83
|
-
| `email`, `url`, `uuid`, `pattern`, `matches`
|
|
84
|
-
| `literal`, `enum`, `in`, `allowedValues`, `notAllowedValues`
|
|
85
|
-
| `instanceof`
|
|
86
|
-
| `equalsField`, `notEqualsField`
|
|
87
|
-
| `minDate`, `maxDate`, `beforeField`, `afterField`, `today`, `past`, `future`, `weekDay`, `weekend`, `businessDay`, `birthday` | date rules (`.min`/`.max` → `minDate`/`maxDate`; `.before`/`.after` → `beforeField`/`afterField`)
|
|
74
|
+
| Type | Produced by |
|
|
75
|
+
| ----------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
|
|
76
|
+
| `required`, `present` | `.required()`, `.present()` (note: `.optional()` has no error type — it clears the required rule) |
|
|
77
|
+
| `requiredIf`, `requiredWith`, `requiredWithout`, `requiredUnless` | conditional required methods |
|
|
78
|
+
| `string`, `number`, `int`, `float`, `boolean`, `scalar`, `object`, `array`, `date` | type guards from `matchesType` |
|
|
79
|
+
| `minLength`, `maxLength`, `betweenLength`, `length` | string / array length rules (`.min`/`.max` on a string alias to `minLength`/`maxLength`) |
|
|
80
|
+
| `min`, `max`, `betweenNumbers` | number range rules (`.min`/`.max`/`.between` on a number) |
|
|
81
|
+
| `email`, `url`, `uuid`, `pattern`, `matches` | string format rules (`.pattern()` → `pattern`) |
|
|
82
|
+
| `literal`, `enum`, `in`, `allowedValues`, `notAllowedValues` | value-membership rules (`.oneOf` aliases `in`; `.notIn`/`.forbids` → `notAllowedValues`; `.allowsOnly` → `allowedValues`) |
|
|
83
|
+
| `instanceof` | `v.instanceof(Ctor)` |
|
|
84
|
+
| `equalsField`, `notEqualsField` | cross-field equality (`.sameAs` → `equalsField`; `.differentFrom` → `notEqualsField`) |
|
|
85
|
+
| `minDate`, `maxDate`, `beforeField`, `afterField`, `today`, `past`, `future`, `weekDay`, `weekend`, `businessDay`, `birthday` | date rules (`.min`/`.max` → `minDate`/`maxDate`; `.before`/`.after` → `beforeField`/`afterField`) |
|
|
86
|
+
|
|
87
|
+
`v.union(...)` reports `union`: when no branch passes, it relays the closest branch's errors (fewest mismatched literals, then fewest errors, then deepest path) at their own paths, all typed `union`. A custom rule can do the same by returning `errors: [{ error, path }, ...]` with its failed result.
|
|
88
88
|
|
|
89
89
|
If you write custom rules, the `type` name you set on the rule object is what shows up here. Pick stable, kebab-or-camel-case names — they become a public API.
|
|
90
90
|
|
|
@@ -116,7 +116,7 @@ For attribute display names ("email" → "Email Address"), use `.attributes({ em
|
|
|
116
116
|
|
|
117
117
|
## When to throw
|
|
118
118
|
|
|
119
|
-
Don't wrap `validate()` in try/catch for
|
|
119
|
+
Don't wrap `validate()` in try/catch for _validation_ failures — those land in `result.errors`. The only thing that _throws_ is a programming bug:
|
|
120
120
|
|
|
121
121
|
- A rule's callback threw (e.g. you wrote `async validate() { throw new Error(...) }`).
|
|
122
122
|
- A transformer threw on output.
|
|
@@ -132,7 +132,7 @@ If you're surfacing seal errors through HTTP / RPC, the typical shape is:
|
|
|
132
132
|
if (!result.isValid) {
|
|
133
133
|
return reply.code(422).send({
|
|
134
134
|
error: "validation_failed",
|
|
135
|
-
fields: result.errors.map(e => ({
|
|
135
|
+
fields: result.errors.map((e) => ({
|
|
136
136
|
field: e.input,
|
|
137
137
|
type: e.type,
|
|
138
138
|
message: e.error,
|
package/skills/index.md
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
---
|
|
2
|
-
description:
|
|
2
|
+
description: 'Type-safe, framework-agnostic validation for Warlock.js: schemas, coercion, modifiers, JSON Schema, Standard Schema. Exports `v`, `validate`, `ObjectValidator`, `StringValidator`, `Infer`, reached as `v.object`, `v.string`, `v.array`, `v.enum`, `v.union`. Use for: "validate this request body", "require an email and a min-length password", "infer the TypeScript type from a schema", "make a field optional or give it a default", "generate JSON Schema", "write a custom rule". Not this package: database rules and file validators live in @warlock.js/core; model schemas and persistence are @warlock.js/cascade.'
|
|
3
3
|
---
|
|
4
|
+
|
|
4
5
|
# @warlock.js/seal
|
|
5
6
|
|
|
6
7
|
Compose validators from the `v` factory (`v.object({ email: v.string().email() })`), run them with `validate(schema, data)`, and infer static types with `Infer`. Validators chain rules, mutators (transforms) and modifiers; the same schema works as a Standard Schema.
|
|
7
8
|
|
|
8
9
|
## The 80% path
|
|
10
|
+
|
|
9
11
|
1. Orient: `overview.md`, `seal-basics.md`.
|
|
10
12
|
2. Choose the right primitive: `pick-seal-primitive.md`.
|
|
11
13
|
3. Shape nested data: `define-structural-shape.md`.
|
|
@@ -14,6 +16,7 @@ Compose validators from the `v` factory (`v.object({ email: v.string().email() }
|
|
|
14
16
|
6. Interop: `bridge-standard-schema.md`, `generate-json-schema.md`; extend via `extend-seal-with-plugins.md`.
|
|
15
17
|
|
|
16
18
|
## Conventions and pitfalls
|
|
19
|
+
|
|
17
20
|
- Validation is async; always `await validate(...)`.
|
|
18
21
|
- The package root is framework-agnostic; database-backed rules (unique, exists) and `FileValidator` come from `@warlock.js/core`, not here.
|
|
19
22
|
- Derive types with `Infer` instead of hand-writing parallel interfaces.
|
package/skills/overview/SKILL.md
CHANGED
|
@@ -5,7 +5,7 @@ description: "@warlock.js/seal overview for orientation and choosing the focused
|
|
|
5
5
|
|
|
6
6
|
# `@warlock.js/seal` — overview
|
|
7
7
|
|
|
8
|
-
A type-safe, framework-agnostic validation library. You build a schema with the `v` factory, run it with `validate(schema, data)`, and pull the static type out with `Infer<typeof schema>`. One schema is the runtime check
|
|
8
|
+
A type-safe, framework-agnostic validation library. You build a schema with the `v` factory, run it with `validate(schema, data)`, and pull the static type out with `Infer<typeof schema>`. One schema is the runtime check _and_ the TypeScript type _and_ (optionally) a JSON Schema.
|
|
9
9
|
|
|
10
10
|
Standalone — no framework required. Ships built-in with `@warlock.js/core`, which adds framework-specific validators (file uploads, database existence/uniqueness rules) on top.
|
|
11
11
|
|
|
@@ -28,31 +28,39 @@ Eight task skills. Most schemas only need `seal-basics` + `pick-seal-primitive`
|
|
|
28
28
|
### Foundations
|
|
29
29
|
|
|
30
30
|
#### `seal-basics`
|
|
31
|
+
|
|
31
32
|
Start here. The `v` factory, `validate(schema, data)`, and `Infer<typeof schema>`.
|
|
32
33
|
|
|
33
34
|
### Building schemas
|
|
34
35
|
|
|
35
36
|
#### `pick-seal-primitive`
|
|
37
|
+
|
|
36
38
|
Choose the right primitive — `string` / `int` / `literal` / `date` / `enum` / `computed` / `managed` / `instanceof` / `any`. Covers the close calls (`string` vs `scalar`, `int` vs `number`, `literal` vs `enum`).
|
|
37
39
|
|
|
38
40
|
#### `define-structural-shape`
|
|
41
|
+
|
|
39
42
|
Compose `v.object` / `v.array` / `v.record` / `v.tuple` / `v.union` / `v.discriminatedUnion` / `v.lazy` — object schemas, dynamic-keyed records, tagged unions, recursive shapes.
|
|
40
43
|
|
|
41
44
|
#### `compose-seal-modifiers`
|
|
45
|
+
|
|
42
46
|
Cross-cutting modifiers — `.optional` / `.nullable` / `.default` / `.catch` / `.omit` / membership rules — plus the mutator-vs-transformer pipeline and `Infer.Input` vs `Infer.Output`.
|
|
43
47
|
|
|
44
48
|
### Output + interop
|
|
45
49
|
|
|
46
50
|
#### `handle-seal-errors`
|
|
51
|
+
|
|
47
52
|
Read a `ValidationResult` — `isValid`, `errors[]`, `data`. Branch on `error.type`, customize messages, hook translation.
|
|
48
53
|
|
|
49
54
|
#### `generate-json-schema`
|
|
55
|
+
|
|
50
56
|
`schema.toJsonSchema(target)` — draft-2020-12 / draft-07 / openapi-3.0 / openai-strict. For OpenAI structured outputs, OpenAPI specs, any JSON-Schema consumer.
|
|
51
57
|
|
|
52
58
|
#### `bridge-standard-schema`
|
|
53
|
-
|
|
59
|
+
|
|
60
|
+
Standard Schema interop — why a `StandardSchemaV1<T>` slot might reject a schema, object input/output class parameters and primitive factory intersections, Cascade `Model<TSchema>` variance. For migrating off `as unknown as` casts.
|
|
54
61
|
|
|
55
62
|
#### `extend-seal-with-plugins`
|
|
63
|
+
|
|
56
64
|
Author a plugin to add validator methods (`.slug`, `.postalCode`, …) — the `SealPlugin` shape, `registerPlugin` lifecycle, TS prototype augmentation.
|
|
57
65
|
|
|
58
66
|
## Configuration
|