@warlock.js/seal 5.8.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,18 @@ 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
+
13
+ ## 5.9.0 - 2026-09-13
14
+
15
+ ### Added
16
+
17
+ - `.coerce()` modifier on the number validators (`int` / `number` / `float` / `numeric`): opt into parsing a numeric-shaped string (e.g. a query-string param) into a number while the type rule stays strict. `v.int()` is unchanged by default; chain `.coerce()` to accept `"2"` as `2`. The inferred output type is unaffected.
18
+
7
19
  ## 5.7.0 - 2026-09-11
8
20
 
9
21
  ### Changed
@@ -11,6 +23,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
11
23
  - Internal type-safety hardening; no behaviour change.
12
24
 
13
25
  ## 5.6.0 - 2026-09-08
26
+
14
27
  ### Fixed
15
28
 
16
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.
@@ -86,7 +99,7 @@ changelogs.
86
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
87
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
88
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`
89
- - `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
90
103
 
91
104
  ## 4.2.11
92
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
  */
@@ -6977,6 +7013,26 @@ var NumberValidator = class extends PrimitiveValidator {
6977
7013
  return this.addMutator(toFixedMutator, { decimals });
6978
7014
  }
6979
7015
  /**
7016
+ * Opt in to coercion: a numeric-shaped string is converted to a number
7017
+ * **before** the type rules run; any other value passes through unchanged
7018
+ * so bad input still fails the type rule.
7019
+ *
7020
+ * Strictness is preserved — coercion only fixes the string *representation*,
7021
+ * not the value's shape. `v.int().coerce()` turns `"5"` into `5` (valid) but
7022
+ * `"5.5"` into `5.5`, which still fails the integer rule. Number validators
7023
+ * do **not** coerce by default; this only takes effect when explicitly chained.
7024
+ *
7025
+ * The output type is unchanged (`Infer<>` keys off the validator class), so
7026
+ * `v.int().coerce()` still infers `number`.
7027
+ *
7028
+ * @example
7029
+ * v.int().coerce() // "5" → 5 (valid); "5.5" → 5.5 (invalid); "abc" → invalid
7030
+ * v.number().coerce() // "5.5" → 5.5 (valid)
7031
+ */
7032
+ coerce() {
7033
+ return this.addMutator(numericMutator);
7034
+ }
7035
+ /**
6980
7036
  * @inheritdoc
6981
7037
  *
6982
7038
  * Returns `{ type: "number" }` with numeric constraint keywords.
@@ -8788,6 +8844,7 @@ exports.betweenNumbersRule = betweenNumbersRule;
8788
8844
  exports.betweenTimesRule = betweenTimesRule;
8789
8845
  exports.betweenYearsRule = betweenYearsRule;
8790
8846
  exports.birthdayRule = birthdayRule;
8847
+ exports.booleanCoerceMutator = booleanCoerceMutator;
8791
8848
  exports.booleanMutator = booleanMutator;
8792
8849
  exports.booleanRule = booleanRule;
8793
8850
  exports.businessDayRule = businessDayRule;