@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
package/llms.txt
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
## Skills
|
|
8
8
|
|
|
9
|
-
- [bridge-standard-schema](@warlock.js/seal/bridge-standard-schema/SKILL.md): Diagnose `StandardSchemaV1` interop —
|
|
9
|
+
- [bridge-standard-schema](@warlock.js/seal/bridge-standard-schema/SKILL.md): Diagnose `StandardSchemaV1` interop — input/output class parameters and primitive factory intersections, why typed slots reject a schema, cascade `Model<TSchema>` variance. Triggers: `StandardSchemaV1`, `~standard`, `Infer`, `ObjectValidator`, `StringValidator`, `LiteralValidator`, `BaseValidator`, `Model<TSchema>`, `StandardJSONSchemaV1`, `Result<unknown>`; "StandardSchemaV1 slot rejecting my schema", "Result<unknown> error", "drop as unknown as cast", "cascade Model schema variance"; typical import `import { v, type Infer } from "@warlock.js/seal"`. Skip: foundations — the `seal-basics` topic; JSON Schema gen — the `generate-json-schema` topic; competing spec `@standard-schema/spec`.
|
|
10
10
|
- [compose-seal-modifiers](@warlock.js/seal/compose-seal-modifiers/SKILL.md): Apply cross-cutting modifiers — `.optional` / `.nullable` / `.default` / `.catch` / `.omit` / membership rules — plus the mutator-vs-transformer pipeline and `Infer.Input` vs `Infer.Output`. Triggers: `.optional`, `.required`, `.nullable`, `.default`, `.catch`, `.present`, `.requiredIf`, `.requiredWith`, `.in`, `.oneOf`, `.notIn`, `.omit`, `.attribute`, `.mutable`, `.addMutator`, `Infer.Input`, `Infer.Output`; "mark field optional in seal", "default value in schema", "mutator vs transformer", "when does .catch fire"; typical import `import { v, type Infer } from "@warlock.js/seal"`. Skip: primitives — the `pick-seal-primitive` topic; containers — the `define-structural-shape` topic; errors — the `handle-seal-errors` topic; competing `zod` `.optional`/`.default`.
|
|
11
11
|
- [define-structural-shape](@warlock.js/seal/define-structural-shape/SKILL.md): Compose `v.object` / `v.array` / `v.record` / `v.tuple` / `v.union` / `v.discriminatedUnion` / `v.lazy`. Triggers: `v.object`, `v.array`, `v.record`, `v.tuple`, `v.union`, `v.discriminatedUnion`, `v.lazy`, `ObjectValidator`; "how do I build an object schema", "dynamic-keyed record", "tagged union with discriminator", "recursive schema", "self-referencing schema"; typical import `import { v, type Infer } from "@warlock.js/seal"`. Skip: leaf primitives — the `pick-seal-primitive` topic; modifiers — the `compose-seal-modifiers` topic; standard-schema bridge — the `bridge-standard-schema` topic; competing libs `zod`, `valibot`.
|
|
12
12
|
- [extend-seal-with-plugins](@warlock.js/seal/extend-seal-with-plugins/SKILL.md): Author a custom seal plugin to add validator methods (`.slug`, `.postalCode`, etc) — `SealPlugin` shape, `registerPlugin` lifecycle, TS prototype augmentation. Triggers: `SealPlugin`, `registerPlugin`, `unregisterPlugin`, `hasPlugin`, `getInstalledPlugins`, `StringValidator`, `NumberValidator`, `DateValidator`, `BaseValidator`, `install`, `uninstall`; "how do I add a custom validator method", "write a seal plugin", "extend v.string with .slug", "module augmentation for seal"; typical import `import { StringValidator, registerPlugin, type SealPlugin } from "@warlock.js/seal"`. Skip: built-in primitives — the `pick-seal-primitive` topic; bridge typing — the `bridge-standard-schema` topic; competing `zod` `.refine`/`.transform`.
|
package/package.json
CHANGED
|
@@ -1,34 +1,35 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: bridge-standard-schema
|
|
3
|
-
description: 'Diagnose `StandardSchemaV1` interop —
|
|
3
|
+
description: 'Diagnose `StandardSchemaV1` interop — input/output class parameters and primitive factory intersections, why typed slots reject a schema, cascade `Model<TSchema>` variance. Triggers: `StandardSchemaV1`, `~standard`, `Infer`, `ObjectValidator`, `StringValidator`, `LiteralValidator`, `BaseValidator`, `Model<TSchema>`, `StandardJSONSchemaV1`, `Result<unknown>`; "StandardSchemaV1 slot rejecting my schema", "Result<unknown> error", "drop as unknown as cast", "cascade Model schema variance"; typical import `import { v, type Infer } from "@warlock.js/seal"`. Skip: foundations — the `seal-basics` topic; JSON Schema gen — the `generate-json-schema` topic; competing spec `@standard-schema/spec`.'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# The Standard Schema bridge
|
|
7
7
|
|
|
8
8
|
Seal validators implement [Standard Schema V1](https://standardschema.dev) — every validator exposes a `~standard` member with `validate()`, JSON Schema metadata, and inferred types. Any consumer that accepts `StandardSchemaV1<T>` (`@warlock.js/ai` supervisor `output`, ai tool `input`, LangGraph state, OpenAI structured outputs, TanStack Form, Conform) accepts a seal schema directly.
|
|
9
9
|
|
|
10
|
-
This skill
|
|
10
|
+
This skill covers the input and output types exposed through `~standard`, and
|
|
11
|
+
compatibility with consumers such as Cascade model schemas and AI tool schemas.
|
|
11
12
|
|
|
12
13
|
## The shape of the bridge
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
`ObjectValidator<TSchema>` already threads `Infer.Input` and `Infer.Output` shapes
|
|
16
|
+
into `BaseValidator<TInput, TOutput>`. Both `v.object(fields)` and the standalone
|
|
17
|
+
`object(fields)` factory return that class directly. No extra Standard Schema
|
|
18
|
+
intersection is needed for objects: avoiding one keeps large composed schemas
|
|
19
|
+
cheaper for TypeScript to instantiate.
|
|
15
20
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
object: <T extends Schema>(schema: T) =>
|
|
19
|
-
new ObjectValidator<T>(schema) as ObjectValidator<T>
|
|
20
|
-
& StandardSchemaV1<Infer<ObjectValidator<T>>>;
|
|
21
|
-
|
|
22
|
-
string: () =>
|
|
23
|
-
new StringValidator() as StringValidator & StandardSchemaV1<string>;
|
|
24
|
-
|
|
25
|
-
literal: <T extends readonly [...] >(...values: T) =>
|
|
26
|
-
new LiteralValidator<T>(values) as LiteralValidator<T> & StandardSchemaV1<T[number]>;
|
|
21
|
+
Other factories add a Standard Schema intersection when their class has no precise
|
|
22
|
+
generic output declaration, for example:
|
|
27
23
|
|
|
28
|
-
|
|
24
|
+
```ts
|
|
25
|
+
// Inside Seal's factory:
|
|
26
|
+
string: () => new StringValidator() as StringValidator & StandardSchemaV1<string>;
|
|
29
27
|
```
|
|
30
28
|
|
|
31
|
-
|
|
29
|
+
Keep input and output distinct when writing consumer types. Defaults and catches
|
|
30
|
+
can make input fields optional while output fields are required. Avoid computing
|
|
31
|
+
`Infer<this>` in the base class: object shapes are already supplied as generic
|
|
32
|
+
parameters, and recursively walking the full class increases compiler work.
|
|
32
33
|
|
|
33
34
|
## What this means for app code
|
|
34
35
|
|
|
@@ -50,7 +51,7 @@ ai.tool({
|
|
|
50
51
|
name: "create_user",
|
|
51
52
|
description: "Create a new user",
|
|
52
53
|
input: userSchema,
|
|
53
|
-
execute: async input => createUser(input),
|
|
54
|
+
execute: async (input) => createUser(input),
|
|
54
55
|
});
|
|
55
56
|
|
|
56
57
|
// ✅ supervisor output — no cast
|
|
@@ -74,7 +75,7 @@ const schema = v.object({
|
|
|
74
75
|
blocks: v.array(v.literal("text", "image")).optional(),
|
|
75
76
|
});
|
|
76
77
|
|
|
77
|
-
type Output = Infer<typeof schema>;
|
|
78
|
+
type Output = Infer.Output<typeof schema>;
|
|
78
79
|
// { email: string; blocks?: ("text" | "image")[] }
|
|
79
80
|
```
|
|
80
81
|
|
|
@@ -84,17 +85,12 @@ Hand-rolled parallel types drift the moment a field changes — and seal's tight
|
|
|
84
85
|
|
|
85
86
|
Three failure modes you'll see in real projects:
|
|
86
87
|
|
|
87
|
-
### 1.
|
|
88
|
+
### 1. An annotation discarded schema details
|
|
88
89
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
// ✅ Let inference run
|
|
94
|
-
const schema = v.object({...});
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
The annotation strips `& StandardSchemaV1<...>` from the type. The schema stops fitting `StandardSchemaV1<T>` slots. Fix: remove the annotation. If you need the value type, use `Infer<typeof schema>`.
|
|
90
|
+
Let the factory infer its concrete schema. An annotation such as bare
|
|
91
|
+
`ObjectValidator` loses the field types; primitive factories also lose their
|
|
92
|
+
Standard Schema intersection when annotated with a bare primitive class.
|
|
93
|
+
`ObjectValidator<typeof fields>` preserves an object's input and output types.
|
|
98
94
|
|
|
99
95
|
### 2. The schema's inferred shape doesn't match the target type
|
|
100
96
|
|
|
@@ -109,23 +105,51 @@ The supervisor's explicit `<MyOutput>` generic is a constraint the schema must s
|
|
|
109
105
|
- **Drop the explicit generic** — let the supervisor infer output type from the schema. The hand-rolled type was probably documentation-only anyway.
|
|
110
106
|
- **Align them** — if the hand-rolled type is the source of truth (e.g. a domain type from elsewhere), make the schema match.
|
|
111
107
|
|
|
112
|
-
### 3. The error says `Result<unknown>`
|
|
108
|
+
### 3. The error says `Result<unknown>`
|
|
109
|
+
|
|
110
|
+
Check whether an annotation erased a factory's output type, and compare
|
|
111
|
+
`Infer.Input<typeof schema>` and `Infer.Output<typeof schema>` with the consumer's
|
|
112
|
+
expected types. Output slots should use the post-default output shape. Objects
|
|
113
|
+
carry that shape directly through their class generics; primitive factories add
|
|
114
|
+
it through their Standard Schema intersection.
|
|
113
115
|
|
|
114
|
-
|
|
116
|
+
## Union and record outputs
|
|
115
117
|
|
|
116
|
-
|
|
118
|
+
Use `Infer.Output<T>` or `StandardSchemaV1.InferOutput<T>` for downstream data.
|
|
119
|
+
Unions and records preserve their children's output types independently of input
|
|
120
|
+
shapes. Defaults and catches make object fields required in output. Their factory
|
|
121
|
+
intersections put the precise Standard Schema signature first, so direct
|
|
122
|
+
`~standard.validate()` calls return the declared output. `Infer.Output` reads the
|
|
123
|
+
output metadata rather than inferring from overloaded validation signatures. A union
|
|
124
|
+
returns the selected branch's validated data before its own transformers and
|
|
125
|
+
refinements; it does not discard branch defaults, computed values or transformations.
|
|
117
126
|
|
|
118
127
|
```ts
|
|
119
|
-
|
|
120
|
-
const
|
|
121
|
-
|
|
128
|
+
const entry = v.object({ count: v.int().optional().default(1) });
|
|
129
|
+
const entries = v.record(entry);
|
|
130
|
+
type Entries = Infer.Output<typeof entries>;
|
|
131
|
+
// Record<string, { count: number }>
|
|
132
|
+
const maybeEntry = v.union([entry, v.string()]);
|
|
133
|
+
type MaybeEntry = Infer.Output<typeof maybeEntry>;
|
|
134
|
+
// { count: number } | string
|
|
122
135
|
```
|
|
123
136
|
|
|
124
|
-
|
|
137
|
+
When upgrading, union output can change through member trimming, Date parsing,
|
|
138
|
+
defaults, computed fields and unknown-key stripping. An explicit `undefined`
|
|
139
|
+
member output drops its containing object field. Keep the raw input before
|
|
140
|
+
validation if storage, comparisons or serialization also need that value.
|
|
141
|
+
|
|
142
|
+
## Compiler budget
|
|
143
|
+
|
|
144
|
+
From `packages/seal`, run `pnpm bench:types --check` after changes to inference or
|
|
145
|
+
factory types. CI runs the same deterministic composed-schema fixture when Seal
|
|
146
|
+
is selected. Instantiations and type counts may grow at most 5% above the checked-in
|
|
147
|
+
baseline. Refresh with `node scripts/bench-types.mjs --update` only after reviewing
|
|
148
|
+
the measurements; time and memory are diagnostic rather than gated.
|
|
125
149
|
|
|
126
150
|
## Why not `Omit<class, "~standard"> & StandardSchemaV1<T>`?
|
|
127
151
|
|
|
128
|
-
Tempting — it would force the factory's `~standard` to fully replace the class's wider one, fixing the misleading `Result<unknown>` error message. **Don't do it.** `Omit` on a class instance type triggers the same variance trap as putting `Infer<this>` on the class itself: cascade explodes with hundreds of `ObjectValidator<{specific}>` vs `ObjectValidator<TSchema>` errors.
|
|
152
|
+
Tempting — it would force the factory's `~standard` to fully replace the class's wider one, fixing the misleading `Result<unknown>` error message. **Don't do it.** `Omit` on a class instance type triggers the same variance trap as putting `Infer<this>` on the class itself: cascade explodes with hundreds of `ObjectValidator<{specific}>` vs `ObjectValidator<TSchema>` errors. Keep object factories as their existing generic class, and retain factory-side intersections for primitive classes that need them. Both preserve the validator's class members for generic consumers.
|
|
129
153
|
|
|
130
154
|
## Why not import `@standard-schema/spec` instead of forking the types?
|
|
131
155
|
|
|
@@ -17,31 +17,61 @@ When `validate()` runs against a value, the order is:
|
|
|
17
17
|
3. optional/required check — decide whether to run rules
|
|
18
18
|
4. requiredRule — required-condition rule (.requiredIf, .requiredWith, etc.)
|
|
19
19
|
5. rules — every other rule, in declaration order
|
|
20
|
-
6.
|
|
20
|
+
6. children — validate container members, fill defaults and computed fields
|
|
21
|
+
7. transformers — reshape value into data (.toISOString, .toLowerCase)
|
|
22
|
+
8. refinements — typed custom checks against successful output
|
|
21
23
|
```
|
|
22
24
|
|
|
23
25
|
If a rule fails, transformers don't run for that field.
|
|
24
26
|
|
|
25
27
|
**Mutator vs transformer mental model:**
|
|
28
|
+
|
|
26
29
|
- **Mutator** = pre-validation reshape. `v.date()` ships one that parses strings. Use when you want rules to see the reshaped value.
|
|
27
30
|
- **Transformer** = post-validation reshape. Lands in `data`. Use when you only care about the output form.
|
|
28
31
|
|
|
29
32
|
```ts
|
|
30
33
|
// trim BEFORE length check — use a mutator
|
|
31
|
-
v.string()
|
|
34
|
+
v.string()
|
|
35
|
+
.addMutator((s) => s.trim())
|
|
36
|
+
.min(3);
|
|
32
37
|
// " Hi " → mutator trims → "Hi" → fails min(3)
|
|
33
38
|
|
|
34
39
|
// trim only the OUTPUT — use a transformer (.trim() on string is a transformer)
|
|
35
|
-
v.string().min(3).trim()
|
|
40
|
+
v.string().min(3).trim();
|
|
36
41
|
// " Hi " → rules see " Hi " (length 6, passes min(3)) → trim → data = "Hi"
|
|
37
42
|
```
|
|
38
43
|
|
|
44
|
+
## `.refine()`
|
|
45
|
+
|
|
46
|
+
Refinements receive the validator's output type, after children, defaults, computed
|
|
47
|
+
fields and output transformers. They skip failed validation and absent/null values,
|
|
48
|
+
including when `firstErrorOnly: false`. Return an error string to reject the value;
|
|
49
|
+
return `undefined` to accept it. Async callbacks are supported. Use `.addRule()`
|
|
50
|
+
when a check must run against input before children validate.
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
v.object({ count: v.int().optional().default(1) }).refine((value) => {
|
|
54
|
+
// count is a number, and defaults have already been applied.
|
|
55
|
+
return value.count > 10 ? "Too many items" : undefined;
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Successful empty defaults such as `.default("")` are refined too. `.merge()`
|
|
60
|
+
appends the other object's refinements; `.extend()` imports only fields. Cloning
|
|
61
|
+
and field-selection methods preserve the receiver's refinements, so callbacks
|
|
62
|
+
must tolerate fields removed by `.pick()` or `.without()`.
|
|
63
|
+
|
|
64
|
+
Output transformers do not yet change declared output types: a
|
|
65
|
+
`v.date().toISOString()` refinement receives a string at runtime despite the
|
|
66
|
+
`Date` declaration. Use a Date input rule, or convert after validation; moving
|
|
67
|
+
`.refine()` earlier in the chain does not move it before transformers.
|
|
68
|
+
|
|
39
69
|
## `.required()` / `.optional()` / `.present()`
|
|
40
70
|
|
|
41
71
|
```ts
|
|
42
|
-
v.string()
|
|
43
|
-
v.string().optional()
|
|
44
|
-
v.string().present()
|
|
72
|
+
v.string(); // already required inside v.object
|
|
73
|
+
v.string().optional(); // type: string | undefined — { isOptional: true } brand
|
|
74
|
+
v.string().present(); // must exist, may be "" / null
|
|
45
75
|
```
|
|
46
76
|
|
|
47
77
|
**Required is the default inside `v.object`.** Most schemas read cleaner without `.required()` — `Infer<>` already shows what's required (no `?`) vs optional (`?`). Calling `.required()` explicitly is harmless and accepted, but the canonical seal style is to skip it. Keep `.optional()` explicit (it changes behavior); skip `.required()` (it doesn't).
|
|
@@ -49,9 +79,11 @@ v.string().present() // must exist, may be "" / null
|
|
|
49
79
|
The `{ isOptional: true }` brand survives chaining (`.optional().min(3)` is still optional) and `Infer<>` reads it to make the key optional.
|
|
50
80
|
|
|
51
81
|
**When `.required()` is still useful:**
|
|
52
|
-
|
|
82
|
+
|
|
83
|
+
- It's not needed for conditional rules — `.requiredIf(field, value)` and friends _replace_ the default required-condition slot, so they work standalone.
|
|
53
84
|
|
|
54
85
|
Conditional variants (run inside `v.object` only):
|
|
86
|
+
|
|
55
87
|
- `.requiredIf(field, value)` / `.requiredIfSibling(field, value)`
|
|
56
88
|
- `.requiredWith(field)` / `.requiredWithSibling(field)`
|
|
57
89
|
- `.requiredWithout(field)` / `.requiredWithoutSibling(field)`
|
|
@@ -63,22 +95,22 @@ Conditional variants (run inside `v.object` only):
|
|
|
63
95
|
## `.nullable()` / `.notNullable()` / `.nullish()`
|
|
64
96
|
|
|
65
97
|
```ts
|
|
66
|
-
v.string().nullable()
|
|
67
|
-
v.string().nullish()
|
|
98
|
+
v.string().nullable(); // type: string | null
|
|
99
|
+
v.string().nullish(); // sugar for .optional().nullable()
|
|
68
100
|
```
|
|
69
101
|
|
|
70
|
-
Independent of optional — a field can be required
|
|
102
|
+
Independent of optional — a field can be required _and_ nullable. Defaults to non-nullable. The `{ isNullable: true }` brand widens both `Infer.Input` and `Infer.Output` with `| null`.
|
|
71
103
|
|
|
72
104
|
## `.default(value | callback)`
|
|
73
105
|
|
|
74
106
|
```ts
|
|
75
|
-
v.string().default("guest")
|
|
76
|
-
v.int().default(0)
|
|
77
|
-
v.date().default(() => new Date())
|
|
78
|
-
v.array(v.string()).default([])
|
|
107
|
+
v.string().default("guest");
|
|
108
|
+
v.int().default(0);
|
|
109
|
+
v.date().default(() => new Date()); // lazy — fresh each validation
|
|
110
|
+
v.array(v.string()).default([]);
|
|
79
111
|
```
|
|
80
112
|
|
|
81
|
-
If input is `undefined` (or missing), the default is used and rules run against it. Pass a callback for fresh-per-run values. The default fires
|
|
113
|
+
If input is `undefined` (or missing), the default is used and rules run against it. Pass a callback for fresh-per-run values. The default fires _before_ rules — so `v.string().min(3).default("a")` fails because `"a"` is shorter than 3.
|
|
82
114
|
|
|
83
115
|
Date sugar: `v.date().defaultNow()` ≡ `.default(() => new Date())`.
|
|
84
116
|
|
|
@@ -86,7 +118,7 @@ The `{ hasDefault: true }` brand makes the key optional in `Infer.Input` (caller
|
|
|
86
118
|
|
|
87
119
|
## `.catch(fallback)`
|
|
88
120
|
|
|
89
|
-
Rescues
|
|
121
|
+
Rescues _failed_ validation by substituting a fallback. Complement to `.default()`:
|
|
90
122
|
|
|
91
123
|
- `.default(x)` fires when input is **absent**
|
|
92
124
|
- `.catch(y)` fires when input is **present but invalid**
|
|
@@ -121,7 +153,7 @@ const schema = v.object({
|
|
|
121
153
|
retries: v.int().catch(3),
|
|
122
154
|
});
|
|
123
155
|
|
|
124
|
-
type In
|
|
156
|
+
type In = Infer.Input<typeof schema>;
|
|
125
157
|
// {
|
|
126
158
|
// bio?: string;
|
|
127
159
|
// status?: Status; ← default makes caller optional
|
|
@@ -135,10 +167,11 @@ type Out = Infer.Output<typeof schema>;
|
|
|
135
167
|
// retries: number; ← catch guarantees a value
|
|
136
168
|
// }
|
|
137
169
|
|
|
138
|
-
type Default = Infer<typeof schema>;
|
|
170
|
+
type Default = Infer<typeof schema>; // alias for Infer.Input
|
|
139
171
|
```
|
|
140
172
|
|
|
141
173
|
**When to reach for which:**
|
|
174
|
+
|
|
142
175
|
- `Infer.Input<T>` (or bare `Infer<T>`) — for HTTP request bodies, form payloads, DTOs, anything pre-validation. **The common case in HTTP-shaped code.**
|
|
143
176
|
- `Infer.Output<T>` — for Cascade `Model<>` params, validated state, anywhere downstream of `validate()`.
|
|
144
177
|
|
|
@@ -148,26 +181,26 @@ Both widen with `| null` when `.nullable()` is set.
|
|
|
148
181
|
|
|
149
182
|
Three failure modes, three different rescue mechanisms:
|
|
150
183
|
|
|
151
|
-
| Input state
|
|
152
|
-
|
|
|
153
|
-
| Field absent
|
|
154
|
-
| Field absent (no default)
|
|
155
|
-
| Field present and invalid
|
|
156
|
-
| Field is `null`
|
|
157
|
-
| Field present, empty (`""`, `[]`, `{}`) | (none needed — empty is a valid value) | Preserved as-is
|
|
184
|
+
| Input state | Rescued by | What appears in `data` |
|
|
185
|
+
| --------------------------------------- | -------------------------------------- | ---------------------- |
|
|
186
|
+
| Field absent | `.default(x)` | `x` |
|
|
187
|
+
| Field absent (no default) | `.optional()` | Key omitted entirely |
|
|
188
|
+
| Field present and invalid | `.catch(y)` | `y` |
|
|
189
|
+
| Field is `null` | `.nullable()` | `null` |
|
|
190
|
+
| Field present, empty (`""`, `[]`, `{}`) | (none needed — empty is a valid value) | Preserved as-is |
|
|
158
191
|
|
|
159
192
|
Full truth table for an `.optional()` field inside `v.object(…)`:
|
|
160
193
|
|
|
161
|
-
| Input
|
|
162
|
-
|
|
|
163
|
-
| Field absent
|
|
164
|
-
| Field is explicit `undefined`
|
|
165
|
-
| Field is `null` (no `.nullable()`)
|
|
166
|
-
| Field is `null` with `.nullable()`
|
|
167
|
-
| Field present and empty (`""`, `[]`, `{}`) | Preserved as-is — empty ≠ absent
|
|
168
|
-
| Field is `.default(x)` and absent
|
|
169
|
-
| Field is `.default(x)` and present
|
|
170
|
-
| Field is `.catch(y)` and validation fails
|
|
194
|
+
| Input | What appears in `data` |
|
|
195
|
+
| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
196
|
+
| Field absent | Key **omitted entirely** (not `undefined`-valued) |
|
|
197
|
+
| Field is explicit `undefined` | Key omitted (treated identically to absent) |
|
|
198
|
+
| Field is `null` (no `.nullable()`) | Key omitted — for an **optional** field, `null` coalesces to empty and the (cleared) required rule doesn't fire. On a **required** field the same `null` triggers a validation error. |
|
|
199
|
+
| Field is `null` with `.nullable()` | Key is `null` |
|
|
200
|
+
| Field present and empty (`""`, `[]`, `{}`) | Preserved as-is — empty ≠ absent |
|
|
201
|
+
| Field is `.default(x)` and absent | Key is `x` (default fires, then rules run on `x`) |
|
|
202
|
+
| Field is `.default(x)` and present | Caller value wins; default is unused |
|
|
203
|
+
| Field is `.catch(y)` and validation fails | Key is `y` (catch rescues) |
|
|
171
204
|
|
|
172
205
|
```ts
|
|
173
206
|
const schema = v.object({
|
|
@@ -175,11 +208,12 @@ const schema = v.object({
|
|
|
175
208
|
embedding: v.array(v.number()).optional(),
|
|
176
209
|
});
|
|
177
210
|
|
|
178
|
-
(await validate(schema, {}))
|
|
179
|
-
|
|
180
|
-
//
|
|
211
|
+
(await validate(schema, {}))
|
|
212
|
+
.data
|
|
213
|
+
// → {} — neither key appears
|
|
214
|
+
// (NOT { metadata: {}, embedding: [] })
|
|
181
215
|
|
|
182
|
-
(await validate(schema, { metadata: {}, embedding: [] })).data
|
|
216
|
+
(await validate(schema, { metadata: {}, embedding: [] })).data;
|
|
183
217
|
// → { metadata: {}, embedding: [] } — present-empty is preserved
|
|
184
218
|
```
|
|
185
219
|
|
|
@@ -192,12 +226,12 @@ Collection validators (`v.record`, `v.array`, `v.tuple`) explicitly honor this
|
|
|
192
226
|
Available on `v.string()`, `v.number()`, `v.int()`, `v.float()`, `v.boolean()`, `v.scalar()`:
|
|
193
227
|
|
|
194
228
|
```ts
|
|
195
|
-
v.string().in(["admin", "user", "guest"])
|
|
196
|
-
v.string().oneOf(["a", "b"])
|
|
197
|
-
v.string().notIn(["banned", "blocked"])
|
|
198
|
-
v.string().forbids(["banned"])
|
|
199
|
-
v.number().allowsOnly([1, 2, 3])
|
|
200
|
-
v.string().enum(MyTSEnum)
|
|
229
|
+
v.string().in(["admin", "user", "guest"]); // value must match one
|
|
230
|
+
v.string().oneOf(["a", "b"]); // alias for .in
|
|
231
|
+
v.string().notIn(["banned", "blocked"]); // value must NOT match
|
|
232
|
+
v.string().forbids(["banned"]); // alias for .notIn
|
|
233
|
+
v.number().allowsOnly([1, 2, 3]); // stricter — explicit allowlist
|
|
234
|
+
v.string().enum(MyTSEnum); // accepts a TS enum object via Object.values
|
|
201
235
|
```
|
|
202
236
|
|
|
203
237
|
For literal-typed narrowing (`"admin" | "user" | "guest"` instead of `string`), use `v.literal(...)` instead — `oneOf` keeps the broader primitive type.
|
|
@@ -209,10 +243,10 @@ v.object({
|
|
|
209
243
|
email: v.string(),
|
|
210
244
|
password: v.string(),
|
|
211
245
|
passwordConfirm: v.string().sameAs("password").omit(),
|
|
212
|
-
})
|
|
246
|
+
});
|
|
213
247
|
```
|
|
214
248
|
|
|
215
|
-
`.omit()` keeps the field in
|
|
249
|
+
`.omit()` keeps the field in _validation_ but drops it from `data` and from `Infer<>`. Use for confirmation/checksum fields that exist only for cross-field rules. `.exclude()` is the same idea, used internally for managed/computed plumbing.
|
|
216
250
|
|
|
217
251
|
## `.label("Display Name")` — field display name
|
|
218
252
|
|
|
@@ -221,7 +255,7 @@ To control the `:input` placeholder in a field's own messages, call `.label()` o
|
|
|
221
255
|
```ts
|
|
222
256
|
v.object({
|
|
223
257
|
email_address: v.string().label("Email Address"),
|
|
224
|
-
})
|
|
258
|
+
});
|
|
225
259
|
// Error message: "The Email Address is required" (instead of "The email_address is required")
|
|
226
260
|
```
|
|
227
261
|
|
|
@@ -229,12 +263,14 @@ v.object({
|
|
|
229
263
|
|
|
230
264
|
### `.attributes({ ... })` is a different tool
|
|
231
265
|
|
|
232
|
-
`.attributes()` does NOT relabel a field's own `:input`. It supplies named substitution values consumed by the translation layer and by rules that reference
|
|
266
|
+
`.attributes()` does NOT relabel a field's own `:input`. It supplies named substitution values consumed by the translation layer and by rules that reference _other_ fields (e.g. `matches`):
|
|
233
267
|
|
|
234
268
|
```ts
|
|
235
|
-
v.string()
|
|
236
|
-
|
|
237
|
-
|
|
269
|
+
v.string()
|
|
270
|
+
.sameAs("confirmPassword")
|
|
271
|
+
.attributes({
|
|
272
|
+
matches: { confirmPassword: "Confirm Password" },
|
|
273
|
+
});
|
|
238
274
|
```
|
|
239
275
|
|
|
240
276
|
For per-field display names use `.label()`; for translated messages wire the `translateRule` / `translateAttribute` hooks via `configureSeal()`.
|
|
@@ -262,7 +298,7 @@ Switch back with `.immutable`. Default is fine 99% of the time. Reach for `.muta
|
|
|
262
298
|
|
|
263
299
|
## Things NOT to do
|
|
264
300
|
|
|
265
|
-
- Don't put a transformer where a mutator belongs — `.trim()` is a transformer; if you need trimming
|
|
301
|
+
- Don't put a transformer where a mutator belongs — `.trim()` is a transformer; if you need trimming _before_ `.min()`, use `.addMutator(s => s.trim())`.
|
|
266
302
|
- Don't combine `.required()` and `.optional()` on the same chain — last wins, but the intent is unclear; pick one.
|
|
267
303
|
- Don't chain `.default("a")` with `.min(3)` and expect `"a"` to pass — the default goes through rules.
|
|
268
304
|
- Don't expect `.requiredIf()` to work on a standalone validator outside `v.object` — sibling resolution silently passes.
|
|
@@ -14,20 +14,20 @@ v.object({
|
|
|
14
14
|
email: v.string().email(),
|
|
15
15
|
age: v.int().min(13).optional(),
|
|
16
16
|
role: v.literal("admin", "user", "guest"),
|
|
17
|
-
})
|
|
17
|
+
});
|
|
18
18
|
```
|
|
19
19
|
|
|
20
20
|
- **Required by default.** `.optional()` to opt out — the `{ isOptional: true }` brand makes the key optional in `Infer<>`.
|
|
21
21
|
- **Unknown keys policy.** By default extra keys are silently dropped from `data`. Toggle with `.allowUnknown()` (forward as-is), `.stripUnknown()` (explicit drop), or `.allow(...keys)` (whitelist specific extras). See [`object-methods.md`](./object-methods.md).
|
|
22
22
|
- **Schema composition.** `.extend(schema)`, `.merge(other)`, `.pick(...keys)`, `.without(...keys)`, `.partial(...keys)`, `.requiredFields(...keys)` — all in [`object-methods.md`](./object-methods.md).
|
|
23
|
-
- **Cross-field rules** (`sameAs`, `requiredIf`, `requiredWith`) attach to fields
|
|
23
|
+
- **Cross-field rules** (`sameAs`, `requiredIf`, `requiredWith`) attach to fields _inside_ a `v.object`. Without a parent, sibling resolution silently passes.
|
|
24
24
|
|
|
25
25
|
## `v.array` — homogeneous lists
|
|
26
26
|
|
|
27
27
|
```ts
|
|
28
|
-
v.array(v.string())
|
|
29
|
-
v.array(userSchema)
|
|
30
|
-
v.array(v.array(v.int()))
|
|
28
|
+
v.array(v.string()); // type: string[]
|
|
29
|
+
v.array(userSchema); // type: User[]
|
|
30
|
+
v.array(v.array(v.int())); // type: number[][] — recursive inference
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
The inner validator runs against each element; failure on any element fails the array. Method surface (`.min`, `.max`, `.unique`, `.sorted`, `.flip`, …) in [`array-methods.md`](./array-methods.md).
|
|
@@ -35,9 +35,9 @@ The inner validator runs against each element; failure on any element fails the
|
|
|
35
35
|
## `v.record` — homogeneous values, dynamic keys
|
|
36
36
|
|
|
37
37
|
```ts
|
|
38
|
-
v.record(v.int())
|
|
39
|
-
v.record(v.object({ count: v.int() }))
|
|
40
|
-
v.record()
|
|
38
|
+
v.record(v.int()); // type: Record<string, number>
|
|
39
|
+
v.record(v.object({ count: v.int() })); // type: Record<string, { count: number }>
|
|
40
|
+
v.record(); // type: Record<string, any>
|
|
41
41
|
```
|
|
42
42
|
|
|
43
43
|
Reach for `v.record` when keys are dynamic (user-provided, dictionary-style) but values share a schema. If keys are also constrained (e.g. only `"draft" | "published"`), use `v.object` with literal keys instead — the constraint lives in the type.
|
|
@@ -45,26 +45,40 @@ Reach for `v.record` when keys are dynamic (user-provided, dictionary-style) but
|
|
|
45
45
|
## `v.tuple` — positional types
|
|
46
46
|
|
|
47
47
|
```ts
|
|
48
|
-
v.tuple([v.string(), v.int(), v.boolean()])
|
|
49
|
-
v.tuple([v.literal("ok"), v.string()])
|
|
48
|
+
v.tuple([v.string(), v.int(), v.boolean()]); // type: [string, number, boolean]
|
|
49
|
+
v.tuple([v.literal("ok"), v.string()]); // type: ["ok", string]
|
|
50
50
|
```
|
|
51
51
|
|
|
52
52
|
Each position has its own validator; the array length must match the tuple length. Pair with `v.literal` at position 0 for result-tuple patterns (`["ok", data]` vs `["error", message]`).
|
|
53
53
|
|
|
54
|
+
Pass the validators as a literal list so each position keeps its own type, for input and validated output alike. A list typed `BaseValidator[]` (built at runtime) has no fixed positions, so its output is an array rather than a tuple.
|
|
55
|
+
|
|
54
56
|
## `v.union` — one of N validators (untagged)
|
|
55
57
|
|
|
56
58
|
```ts
|
|
57
|
-
v.union([v.string(), v.int()])
|
|
59
|
+
v.union([v.string(), v.int()]); // type: string | number
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Every branch whose type matches the value (`matchesType()`) is tried, in order, and the first that passes wins; the union returns that branch's validated output. `firstErrorOnly` limits the errors reported, never the branches tried, so a union of several object shapes accepts a value matching any of them:
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
const owner = v.object({ kind: v.literal("owner"), id: v.literal("__owner__") });
|
|
66
|
+
const member = v.object({ kind: v.literal("member"), id: v.string() });
|
|
67
|
+
|
|
68
|
+
await validate(v.union([owner, member]), { kind: "member", id: "x" });
|
|
69
|
+
// → valid, data: { kind: "member", id: "x" }
|
|
58
70
|
```
|
|
59
71
|
|
|
60
|
-
|
|
72
|
+
When no branch passes, the union reports the **closest** failed branch, picked by: fewest mismatched `v.literal(...)` fields, then fewest errors, then the deepest error path, then declaration order. Its errors keep their own paths (`actor.id`, not `actor`) and have the type `"union"` — the first one by default, all of them with `firstErrorOnly: false`. A custom `errorMessage` (`v.union([...], "Unknown actor")`), or a value no branch can hold by type, gives a single error at the union's path.
|
|
73
|
+
|
|
74
|
+
For object shapes that share a tag field, `v.discriminatedUnion` is still the better fit: it routes straight to one branch instead of trying each, and narrows exactly in TypeScript.
|
|
61
75
|
|
|
62
76
|
## `v.discriminatedUnion` — tagged unions (recommended for objects)
|
|
63
77
|
|
|
64
78
|
```ts
|
|
65
79
|
const email = v.object({ type: v.literal("email"), email: v.string().email() });
|
|
66
|
-
const sms
|
|
67
|
-
const push
|
|
80
|
+
const sms = v.object({ type: v.literal("sms"), phone: v.string() });
|
|
81
|
+
const push = v.object({ type: v.literal("push"), deviceId: v.string() });
|
|
68
82
|
|
|
69
83
|
const notif = v.discriminatedUnion("type", [email, sms, push]);
|
|
70
84
|
|
|
@@ -76,8 +90,8 @@ type Notif = Infer<typeof notif>;
|
|
|
76
90
|
|
|
77
91
|
Routes payloads by reading the discriminator field (here `type`), looking it up in a key→branch map built at construction time, and delegating to the matching branch only. Benefits over plain `v.union`:
|
|
78
92
|
|
|
79
|
-
- **Precise errors.** Failures come from the
|
|
80
|
-
- **O(1) routing** instead of
|
|
93
|
+
- **Precise errors.** Failures come from the branch the tag names, not from the closest guess.
|
|
94
|
+
- **O(1) routing** instead of trying each branch.
|
|
81
95
|
- **Exact TS inference** — discriminated union with narrowing inside `if (x.type === "email")` blocks.
|
|
82
96
|
- **Cleaner JSON Schema** — `oneOf` with literal discriminators; OpenAI-strict accepts it.
|
|
83
97
|
|
|
@@ -115,20 +129,20 @@ Use for: trees (categories, file systems), threaded data (comment chains), mutua
|
|
|
115
129
|
|
|
116
130
|
## Quick map
|
|
117
131
|
|
|
118
|
-
| Want
|
|
119
|
-
|
|
|
120
|
-
| Fixed-shape record
|
|
121
|
-
| Dynamic keys, same value shape
|
|
122
|
-
| List of items
|
|
123
|
-
| Position-typed array
|
|
124
|
-
| One of N scalar types
|
|
125
|
-
| One of N object shapes with a tag field | `v.discriminatedUnion(key, [...])`
|
|
126
|
-
| Self-referencing or forward reference
|
|
127
|
-
| One of N constants
|
|
132
|
+
| Want | Reach for |
|
|
133
|
+
| --------------------------------------- | ----------------------------------------------------------------------------- |
|
|
134
|
+
| Fixed-shape record | `v.object({...})` |
|
|
135
|
+
| Dynamic keys, same value shape | `v.record(valueSchema)` |
|
|
136
|
+
| List of items | `v.array(itemSchema)` |
|
|
137
|
+
| Position-typed array | `v.tuple([a, b, c])` |
|
|
138
|
+
| One of N scalar types | `v.union([...])` |
|
|
139
|
+
| One of N object shapes with a tag field | `v.discriminatedUnion(key, [...])` |
|
|
140
|
+
| Self-referencing or forward reference | `v.lazy(() => schema)` |
|
|
141
|
+
| One of N constants | `v.literal(...values)` (not structural — see the `pick-seal-primitive` topic) |
|
|
128
142
|
|
|
129
143
|
## A note on cascade Models
|
|
130
144
|
|
|
131
|
-
`@warlock.js/cascade` Models declare `static schema: ObjectValidator<TSchema>`. Passing a `v.object({...})` works directly — the factory return widens to fit the Model's invariant generic without breaking type checking. If TS complains
|
|
145
|
+
`@warlock.js/cascade` Models declare `static schema: ObjectValidator<TSchema>`. Passing a `v.object({...})` works directly — the factory return widens to fit the Model's invariant generic without breaking type checking. If TS complains _"`ObjectValidator<{specific}>` is not assignable to `ObjectValidator<TSchema>`"_, that's the variance trap and the answer isn't to widen the schema — it's almost always that `Model<TSchema>` was parameterized with a hand-rolled type that drifted from the schema's inferred shape. Fix the type, not the schema. See the `bridge-standard-schema` topic.
|
|
132
146
|
|
|
133
147
|
## Method-surface reference
|
|
134
148
|
|