@warlock.js/seal 5.9.0 → 5.10.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 CHANGED
@@ -4,6 +4,12 @@ All notable changes to `@warlock.js/seal` are documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). `@warlock.js/*` packages are released in lockstep — every package shares the same version number, so a version below may list only the changes that affected this package.
6
6
 
7
+ ## 5.10.0 - 2026-09-14
8
+
9
+ ### Added
10
+
11
+ - `.coerce()` on `v.boolean()` — opt-in query-string coercion (`"true"`/`"1"` → `true`, `"false"`/`"0"` → `false`).
12
+
7
13
  ## 5.9.0 - 2026-09-13
8
14
 
9
15
  ### Added
@@ -17,6 +23,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
17
23
  - Internal type-safety hardening; no behaviour change.
18
24
 
19
25
  ## 5.6.0 - 2026-09-08
26
+
20
27
  ### Fixed
21
28
 
22
29
  - **`warlock build` and `warlock start` died inside Seal before any application code ran**, with a `ReferenceError` from `array-validator.ts` extending `BaseValidator` before `base-validator.ts` had finished evaluating. The cause was one inline `type` import: under `verbatimModuleSyntax`, `import { type ValidateOptions } from "./validators"` is emitted as a real side-effect import, which closed an import cycle that a type-only import would never have created. Written as `import type { ... }`, the module is not pulled into evaluation at all. Nothing about the validators changed — only which modules load, and when.
@@ -92,7 +99,7 @@ changelogs.
92
99
  - `v.literal("")` could never pass. Every validator is required by default and `required` rejects anything the empty-value check calls empty — which includes `""` — so a schema demanding an exact empty string reported "is required" for a field that was present. A literal set containing an empty value now uses `present` (the key must exist) instead of `required`, leaving the literal set to judge the value. Only the empty string was affected; `v.literal(0)` and `v.literal(false)` always worked
93
100
  - `v.literal("").optional()` silently disabled the literal check rather than fixing it, accepting `""`, `null` **and** a missing key alike. The literal rule now runs on empty values (`requiresValue: false`) while treating absence as the required/present rule's question, so `.optional()` means optional again and a present value must still match
94
101
  - a **failed** validation no longer returns the input it rejected. `object` returned the raw input — including the unknown keys it had just complained about — while `discriminatedUnion` returned `undefined`; the same call shape had two contracts. Validating an outbound DTO to keep internal fields out of a response, then reading `data` without branching on `isValid`, shipped every field the schema existed to exclude. `data` is now `undefined` whenever `isValid` is `false`
95
- - `v.number().toFixed(n)` could never produce a valid result — the mutator returned `Number(value).toFixed(n)`, a *string*, which the validator's own `number` type rule then rejected. It now yields a number (`3.14159` → `3.14`), so the method works where it lives. No working code can have depended on the old output, since every such validation failed; for a fixed-point *string*, format at the presentation edge rather than asking a number schema to emit one
102
+ - `v.number().toFixed(n)` could never produce a valid result — the mutator returned `Number(value).toFixed(n)`, a _string_, which the validator's own `number` type rule then rejected. It now yields a number (`3.14159` → `3.14`), so the method works where it lives. No working code can have depended on the old output, since every such validation failed; for a fixed-point _string_, format at the presentation edge rather than asking a number schema to emit one
96
103
 
97
104
  ## 4.2.11
98
105
 
package/cjs/index.cjs CHANGED
@@ -1127,6 +1127,21 @@ const removeEmptyArrayElementsMutator = async (value) => {
1127
1127
  return value.filter((item) => !isEmptyValue(item));
1128
1128
  };
1129
1129
 
1130
+ //#endregion
1131
+ //#region ../seal/src/mutators/boolean-mutators.ts
1132
+ /**
1133
+ * Coerce a query-string-shaped boolean into a real boolean.
1134
+ *
1135
+ * Converts exactly: `"true"` / `"1"` / `1` → `true`, `"false"` / `"0"` / `0` → `false`.
1136
+ * Case-sensitive, no trimming. Any other value (`"yes"`, `"on"`, `"TRUE"`, `""`,
1137
+ * `2`, `null`, etc.) passes through unchanged so the boolean type rule rejects it.
1138
+ */
1139
+ const booleanCoerceMutator = async (value) => {
1140
+ if (value === "true" || value === "1" || value === 1) return true;
1141
+ if (value === "false" || value === "0" || value === 0) return false;
1142
+ return value;
1143
+ };
1144
+
1130
1145
  //#endregion
1131
1146
  //#region ../seal/src/mutators/date-mutators.ts
1132
1147
  /**
@@ -5783,6 +5798,27 @@ var BooleanValidator = class extends PrimitiveValidator {
5783
5798
  return this.addRule(declinedWithoutRule, errorMessage, { field });
5784
5799
  }
5785
5800
  /**
5801
+ * Opt in to coercion: an exact query-string-shaped boolean is converted to a
5802
+ * real boolean **before** the type rules run; any other value passes through
5803
+ * unchanged so bad input still fails the type rule.
5804
+ *
5805
+ * Converts exactly `"true"` / `"1"` / `1` → `true` and `"false"` / `"0"` / `0`
5806
+ * → `false`. Case-sensitive, no trimming — `"TRUE"`, `"yes"`, `"on"`, `""`, and
5807
+ * `2` all pass through unchanged and fail the type rule. `v.boolean()` does
5808
+ * **not** coerce by default; this only takes effect when explicitly chained.
5809
+ * For form-style truthy strings like `"yes"` / `"on"`, use `.accepted()` /
5810
+ * `.declined()` instead — those are separate pass/fail rules, not coercion.
5811
+ *
5812
+ * The output type is unchanged (`Infer<>` keys off the validator class), so
5813
+ * `v.boolean().coerce()` still infers `boolean`.
5814
+ *
5815
+ * @example
5816
+ * v.boolean().coerce() // "true" → true; "1" → true; "0" → false; "yes" → invalid
5817
+ */
5818
+ coerce() {
5819
+ return this.addMutator(booleanCoerceMutator);
5820
+ }
5821
+ /**
5786
5822
  * Value must be strictly true (not "yes", "on", 1, etc.)
5787
5823
  * @alias accepted - strict version
5788
5824
  */
@@ -8808,6 +8844,7 @@ exports.betweenNumbersRule = betweenNumbersRule;
8808
8844
  exports.betweenTimesRule = betweenTimesRule;
8809
8845
  exports.betweenYearsRule = betweenYearsRule;
8810
8846
  exports.birthdayRule = birthdayRule;
8847
+ exports.booleanCoerceMutator = booleanCoerceMutator;
8811
8848
  exports.booleanMutator = booleanMutator;
8812
8849
  exports.booleanRule = booleanRule;
8813
8850
  exports.businessDayRule = businessDayRule;