@warlock.js/seal 5.9.0 → 5.11.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.11.0 - 2026-09-14
8
+
9
+ ### Fixed
10
+
11
+ - `v.boolean().accepted()` / `.declined()` (and their conditional variants) rejected every string form they advertised — `"yes"`, `"on"`, `"1"` — because the boolean type rule ran first. They now parse those form values into a real boolean.
12
+
13
+ ## 5.10.0 - 2026-09-14
14
+
15
+ ### Added
16
+
17
+ - `.coerce()` on `v.boolean()` — opt-in query-string coercion (`"true"`/`"1"` → `true`, `"false"`/`"0"` → `false`).
18
+
7
19
  ## 5.9.0 - 2026-09-13
8
20
 
9
21
  ### Added
@@ -17,6 +29,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
17
29
  - Internal type-safety hardening; no behaviour change.
18
30
 
19
31
  ## 5.6.0 - 2026-09-08
32
+
20
33
  ### Fixed
21
34
 
22
35
  - **`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 +105,7 @@ changelogs.
92
105
  - `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
106
  - `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
107
  - 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
108
+ - `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
109
 
97
110
  ## 4.2.11
98
111
 
package/cjs/index.cjs CHANGED
@@ -1127,6 +1127,273 @@ const removeEmptyArrayElementsMutator = async (value) => {
1127
1127
  return value.filter((item) => !isEmptyValue(item));
1128
1128
  };
1129
1129
 
1130
+ //#endregion
1131
+ //#region ../seal/src/rules/scalar/accepted-rule.ts
1132
+ /**
1133
+ * The full set of values treated as "accepted" by `acceptedRule` and its
1134
+ * conditional variants. Shared with `formBooleanMutator` (see
1135
+ * `../../mutators/boolean-mutators`) so `v.boolean().accepted()` recognizes
1136
+ * exactly this list — single source of truth, no duplicated literals.
1137
+ */
1138
+ const ACCEPTED_VALUES = [
1139
+ "1",
1140
+ "true",
1141
+ "yes",
1142
+ "y",
1143
+ "on",
1144
+ 1,
1145
+ true,
1146
+ "Yes",
1147
+ "Y",
1148
+ "On"
1149
+ ];
1150
+ const isAcceptedValue = (value) => {
1151
+ return ACCEPTED_VALUES.includes(value);
1152
+ };
1153
+ /**
1154
+ * Validate value as accepted if it equals:
1155
+ * 1 | "1" | true | "true" | "yes" | "y" | "on"
1156
+ */
1157
+ const acceptedRule = {
1158
+ name: "accepted",
1159
+ defaultErrorMessage: "The :input must be accepted",
1160
+ description: "The value must be accepted if it equals: 1 | '1' | true | 'true' | 'yes' | 'y' | 'on' | 1 | true | 'Yes' | 'Y' | 'On'",
1161
+ async validate(value, context) {
1162
+ if (isAcceptedValue(value)) return VALID_RULE;
1163
+ return invalidRule(this, context);
1164
+ }
1165
+ };
1166
+ /**
1167
+ * Accepted value if another field's value equals to a specific value
1168
+ */
1169
+ const acceptedIfRule = {
1170
+ name: "acceptedIf",
1171
+ description: "The field must be accepted if :field's value equals to :value",
1172
+ defaultErrorMessage: "The :input must be accepted",
1173
+ async validate(value, context) {
1174
+ const fieldValue = getFieldValue(this, context);
1175
+ const { value: expectedValue } = this.context.options;
1176
+ this.context.translatableParams.field = this.context.options.field;
1177
+ this.context.translatableParams.value = this.context.options.value;
1178
+ if (fieldValue !== expectedValue) return invalidRule(this, context);
1179
+ if (!isAcceptedValue(value)) return invalidRule(this, context);
1180
+ return VALID_RULE;
1181
+ }
1182
+ };
1183
+ /**
1184
+ * Accepted if another field is not equal to the given value
1185
+ */
1186
+ const acceptedUnlessRule = {
1187
+ name: "acceptedUnless",
1188
+ description: "The field must be accepted if :field's value is not equal to :value",
1189
+ defaultErrorMessage: "The :input must be accepted",
1190
+ async validate(value, context) {
1191
+ const fieldValue = getFieldValue(this, context);
1192
+ const { value: expectedValue } = this.context.options;
1193
+ this.context.translatableParams.field = this.context.options.field;
1194
+ this.context.translatableParams.value = this.context.options.value;
1195
+ if (fieldValue === expectedValue) return invalidRule(this, context);
1196
+ if (!isAcceptedValue(value)) return invalidRule(this, context);
1197
+ return VALID_RULE;
1198
+ }
1199
+ };
1200
+ /**
1201
+ * Accepted if another field is required
1202
+ */
1203
+ const acceptedIfRequiredRule = {
1204
+ name: "acceptedIfRequired",
1205
+ description: "The field must be accepted if :field is required",
1206
+ defaultErrorMessage: "The :input must be accepted",
1207
+ async validate(value, context) {
1208
+ const fieldValue = getFieldValue(this, context);
1209
+ this.context.translatableParams.field = this.context.options.field;
1210
+ if (isEmptyValue(fieldValue)) return invalidRule(this, context);
1211
+ if (!isAcceptedValue(value)) return invalidRule(this, context);
1212
+ return VALID_RULE;
1213
+ }
1214
+ };
1215
+ /**
1216
+ * Accepted if another field is present
1217
+ */
1218
+ const acceptedIfPresentRule = {
1219
+ name: "acceptedIfPresent",
1220
+ description: "The field must be accepted if :field is present",
1221
+ defaultErrorMessage: "The :input must be accepted",
1222
+ async validate(value, context) {
1223
+ const fieldValue = getFieldValue(this, context);
1224
+ this.context.translatableParams.field = this.context.options.field;
1225
+ if (fieldValue === void 0) return invalidRule(this, context);
1226
+ if (!isAcceptedValue(value)) return invalidRule(this, context);
1227
+ return VALID_RULE;
1228
+ }
1229
+ };
1230
+ /**
1231
+ * Accepted if another field is missing
1232
+ */
1233
+ const acceptedWithoutRule = {
1234
+ name: "acceptedWithout",
1235
+ description: "The field must be accepted if :field is missing",
1236
+ defaultErrorMessage: "The :input must be accepted",
1237
+ async validate(value, context) {
1238
+ const fieldValue = getFieldValue(this, context);
1239
+ this.context.translatableParams.field = this.context.options.field;
1240
+ if (fieldValue !== void 0) return invalidRule(this, context);
1241
+ if (!isAcceptedValue(value)) return invalidRule(this, context);
1242
+ return VALID_RULE;
1243
+ }
1244
+ };
1245
+
1246
+ //#endregion
1247
+ //#region ../seal/src/rules/scalar/declined-rule.ts
1248
+ /**
1249
+ * The full set of values treated as "declined" by `declinedRule` and its
1250
+ * conditional variants. Shared with `formBooleanMutator` (see
1251
+ * `../../mutators/boolean-mutators`) so `v.boolean().declined()` recognizes
1252
+ * exactly this list — single source of truth, no duplicated literals.
1253
+ */
1254
+ const DECLINED_VALUES = [
1255
+ "0",
1256
+ "false",
1257
+ "no",
1258
+ "n",
1259
+ "off",
1260
+ 0,
1261
+ false,
1262
+ "No",
1263
+ "N",
1264
+ "Off"
1265
+ ];
1266
+ const isDeclinedValue = (value) => {
1267
+ return DECLINED_VALUES.includes(value);
1268
+ };
1269
+ /**
1270
+ * Validate value as declined if it equals:
1271
+ * 0 | "0" | false | "false" | "no" | "n" | "off" | 0 | false | "No" | "N" | "Off"
1272
+ */
1273
+ const declinedRule = {
1274
+ name: "declined",
1275
+ defaultErrorMessage: "The :input must be declined",
1276
+ description: "The value must be declined if it equals: 0 | '0' | false | 'false' | 'no' | 'n' | 'off' | 0 | false | 'No' | 'N' | 'Off'",
1277
+ async validate(value, context) {
1278
+ if (isDeclinedValue(value)) return VALID_RULE;
1279
+ return invalidRule(this, context);
1280
+ }
1281
+ };
1282
+ /**
1283
+ * Declined value if another field's value equals to a specific value
1284
+ */
1285
+ const declinedIfRule = {
1286
+ name: "declinedIf",
1287
+ description: "The field must be declined if :field's value equals to :value",
1288
+ defaultErrorMessage: "The :input must be declined",
1289
+ async validate(value, context) {
1290
+ const fieldValue = getFieldValue(this, context);
1291
+ const { value: expectedValue } = this.context.options;
1292
+ this.context.translatableParams.field = this.context.options.field;
1293
+ this.context.translatableParams.value = this.context.options.value;
1294
+ if (fieldValue !== expectedValue) return invalidRule(this, context);
1295
+ if (!isDeclinedValue(value)) return invalidRule(this, context);
1296
+ return VALID_RULE;
1297
+ }
1298
+ };
1299
+ /**
1300
+ * Declined if another field is not equal to the given value
1301
+ */
1302
+ const declinedUnlessRule = {
1303
+ name: "declinedUnless",
1304
+ description: "The field must be declined if :field's value is not equal to :value",
1305
+ defaultErrorMessage: "The :input must be declined",
1306
+ async validate(value, context) {
1307
+ const fieldValue = getFieldValue(this, context);
1308
+ const { value: expectedValue } = this.context.options;
1309
+ this.context.translatableParams.field = this.context.options.field;
1310
+ this.context.translatableParams.value = this.context.options.value;
1311
+ if (fieldValue === expectedValue) return invalidRule(this, context);
1312
+ if (!isDeclinedValue(value)) return invalidRule(this, context);
1313
+ return VALID_RULE;
1314
+ }
1315
+ };
1316
+ /**
1317
+ * Declined if another field is required
1318
+ */
1319
+ const declinedIfRequiredRule = {
1320
+ name: "declinedIfRequired",
1321
+ description: "The field must be declined if :field is required",
1322
+ defaultErrorMessage: "The :input must be declined",
1323
+ async validate(value, context) {
1324
+ const fieldValue = getFieldValue(this, context);
1325
+ this.context.translatableParams.field = this.context.options.field;
1326
+ if (isEmptyValue(fieldValue)) return invalidRule(this, context);
1327
+ if (!isDeclinedValue(value)) return invalidRule(this, context);
1328
+ return VALID_RULE;
1329
+ }
1330
+ };
1331
+ /**
1332
+ * Declined if another field is present
1333
+ */
1334
+ const declinedIfPresentRule = {
1335
+ name: "declinedIfPresent",
1336
+ description: "The field must be declined if :field is present",
1337
+ defaultErrorMessage: "The :input must be declined",
1338
+ async validate(value, context) {
1339
+ const fieldValue = getFieldValue(this, context);
1340
+ this.context.translatableParams.field = this.context.options.field;
1341
+ if (fieldValue === void 0) return invalidRule(this, context);
1342
+ if (!isDeclinedValue(value)) return invalidRule(this, context);
1343
+ return VALID_RULE;
1344
+ }
1345
+ };
1346
+ /**
1347
+ * Declined if another field is missing
1348
+ */
1349
+ const declinedWithoutRule = {
1350
+ name: "declinedWithout",
1351
+ description: "The field must be declined if :field is missing",
1352
+ defaultErrorMessage: "The :input must be declined",
1353
+ async validate(value, context) {
1354
+ const fieldValue = getFieldValue(this, context);
1355
+ this.context.translatableParams.field = this.context.options.field;
1356
+ if (fieldValue !== void 0) return invalidRule(this, context);
1357
+ if (!isDeclinedValue(value)) return invalidRule(this, context);
1358
+ return VALID_RULE;
1359
+ }
1360
+ };
1361
+
1362
+ //#endregion
1363
+ //#region ../seal/src/mutators/boolean-mutators.ts
1364
+ /**
1365
+ * Coerce a query-string-shaped boolean into a real boolean.
1366
+ *
1367
+ * Converts exactly: `"true"` / `"1"` / `1` → `true`, `"false"` / `"0"` / `0` → `false`.
1368
+ * Case-sensitive, no trimming. Any other value (`"yes"`, `"on"`, `"TRUE"`, `""`,
1369
+ * `2`, `null`, etc.) passes through unchanged so the boolean type rule rejects it.
1370
+ */
1371
+ const booleanCoerceMutator = async (value) => {
1372
+ if (value === "true" || value === "1" || value === 1) return true;
1373
+ if (value === "false" || value === "0" || value === 0) return false;
1374
+ return value;
1375
+ };
1376
+ /**
1377
+ * Parse form-shaped boolean values into a real boolean, ahead of the type
1378
+ * rule.
1379
+ *
1380
+ * Opted into by calling any `accepted*`/`declined*` method on
1381
+ * `v.boolean()` (see `BooleanValidator`'s `withFormBooleanMutator`). Converts
1382
+ * every value in `ACCEPTED_VALUES` (`1`, `"1"`, `true`, `"true"`, `"yes"`,
1383
+ * `"y"`, `"on"`, `"Yes"`, `"Y"`, `"On"`) to `true`, and every value in
1384
+ * `DECLINED_VALUES` (`0`, `"0"`, `false`, `"false"`, `"no"`, `"n"`, `"off"`,
1385
+ * `"No"`, `"N"`, `"Off"`) to `false`. Any other value passes through
1386
+ * unchanged so the boolean type rule still rejects it.
1387
+ *
1388
+ * Named (not anonymous) so callers can detect whether it has already been
1389
+ * added — see the idempotence check in `BooleanValidator`.
1390
+ */
1391
+ const formBooleanMutator = async (value) => {
1392
+ if (ACCEPTED_VALUES.includes(value)) return true;
1393
+ if (DECLINED_VALUES.includes(value)) return false;
1394
+ return value;
1395
+ };
1396
+
1130
1397
  //#endregion
1131
1398
  //#region ../seal/src/mutators/date-mutators.ts
1132
1399
  /**
@@ -5484,224 +5751,6 @@ var PrimitiveValidator = class extends BaseValidator {
5484
5751
  }
5485
5752
  };
5486
5753
 
5487
- //#endregion
5488
- //#region ../seal/src/rules/scalar/accepted-rule.ts
5489
- const isAcceptedValue = (value) => {
5490
- return [
5491
- "1",
5492
- "true",
5493
- "yes",
5494
- "y",
5495
- "on",
5496
- 1,
5497
- true,
5498
- "Yes",
5499
- "Y",
5500
- "On"
5501
- ].includes(value);
5502
- };
5503
- /**
5504
- * Validate value as accepted if it equals:
5505
- * 1 | "1" | true | "true" | "yes" | "y" | "on"
5506
- */
5507
- const acceptedRule = {
5508
- name: "accepted",
5509
- defaultErrorMessage: "The :input must be accepted",
5510
- description: "The value must be accepted if it equals: 1 | '1' | true | 'true' | 'yes' | 'y' | 'on' | 1 | true | 'Yes' | 'Y' | 'On'",
5511
- async validate(value, context) {
5512
- if (isAcceptedValue(value)) return VALID_RULE;
5513
- return invalidRule(this, context);
5514
- }
5515
- };
5516
- /**
5517
- * Accepted value if another field's value equals to a specific value
5518
- */
5519
- const acceptedIfRule = {
5520
- name: "acceptedIf",
5521
- description: "The field must be accepted if :field's value equals to :value",
5522
- defaultErrorMessage: "The :input must be accepted",
5523
- async validate(value, context) {
5524
- const fieldValue = getFieldValue(this, context);
5525
- const { value: expectedValue } = this.context.options;
5526
- this.context.translatableParams.field = this.context.options.field;
5527
- this.context.translatableParams.value = this.context.options.value;
5528
- if (fieldValue !== expectedValue) return invalidRule(this, context);
5529
- if (!isAcceptedValue(value)) return invalidRule(this, context);
5530
- return VALID_RULE;
5531
- }
5532
- };
5533
- /**
5534
- * Accepted if another field is not equal to the given value
5535
- */
5536
- const acceptedUnlessRule = {
5537
- name: "acceptedUnless",
5538
- description: "The field must be accepted if :field's value is not equal to :value",
5539
- defaultErrorMessage: "The :input must be accepted",
5540
- async validate(value, context) {
5541
- const fieldValue = getFieldValue(this, context);
5542
- const { value: expectedValue } = this.context.options;
5543
- this.context.translatableParams.field = this.context.options.field;
5544
- this.context.translatableParams.value = this.context.options.value;
5545
- if (fieldValue === expectedValue) return invalidRule(this, context);
5546
- if (!isAcceptedValue(value)) return invalidRule(this, context);
5547
- return VALID_RULE;
5548
- }
5549
- };
5550
- /**
5551
- * Accepted if another field is required
5552
- */
5553
- const acceptedIfRequiredRule = {
5554
- name: "acceptedIfRequired",
5555
- description: "The field must be accepted if :field is required",
5556
- defaultErrorMessage: "The :input must be accepted",
5557
- async validate(value, context) {
5558
- const fieldValue = getFieldValue(this, context);
5559
- this.context.translatableParams.field = this.context.options.field;
5560
- if (isEmptyValue(fieldValue)) return invalidRule(this, context);
5561
- if (!isAcceptedValue(value)) return invalidRule(this, context);
5562
- return VALID_RULE;
5563
- }
5564
- };
5565
- /**
5566
- * Accepted if another field is present
5567
- */
5568
- const acceptedIfPresentRule = {
5569
- name: "acceptedIfPresent",
5570
- description: "The field must be accepted if :field is present",
5571
- defaultErrorMessage: "The :input must be accepted",
5572
- async validate(value, context) {
5573
- const fieldValue = getFieldValue(this, context);
5574
- this.context.translatableParams.field = this.context.options.field;
5575
- if (fieldValue === void 0) return invalidRule(this, context);
5576
- if (!isAcceptedValue(value)) return invalidRule(this, context);
5577
- return VALID_RULE;
5578
- }
5579
- };
5580
- /**
5581
- * Accepted if another field is missing
5582
- */
5583
- const acceptedWithoutRule = {
5584
- name: "acceptedWithout",
5585
- description: "The field must be accepted if :field is missing",
5586
- defaultErrorMessage: "The :input must be accepted",
5587
- async validate(value, context) {
5588
- const fieldValue = getFieldValue(this, context);
5589
- this.context.translatableParams.field = this.context.options.field;
5590
- if (fieldValue !== void 0) return invalidRule(this, context);
5591
- if (!isAcceptedValue(value)) return invalidRule(this, context);
5592
- return VALID_RULE;
5593
- }
5594
- };
5595
-
5596
- //#endregion
5597
- //#region ../seal/src/rules/scalar/declined-rule.ts
5598
- const isDeclinedValue = (value) => {
5599
- return [
5600
- "0",
5601
- "false",
5602
- "no",
5603
- "n",
5604
- "off",
5605
- 0,
5606
- false,
5607
- "No",
5608
- "N",
5609
- "Off"
5610
- ].includes(value);
5611
- };
5612
- /**
5613
- * Validate value as declined if it equals:
5614
- * 0 | "0" | false | "false" | "no" | "n" | "off" | 0 | false | "No" | "N" | "Off"
5615
- */
5616
- const declinedRule = {
5617
- name: "declined",
5618
- defaultErrorMessage: "The :input must be declined",
5619
- description: "The value must be declined if it equals: 0 | '0' | false | 'false' | 'no' | 'n' | 'off' | 0 | false | 'No' | 'N' | 'Off'",
5620
- async validate(value, context) {
5621
- if (isDeclinedValue(value)) return VALID_RULE;
5622
- return invalidRule(this, context);
5623
- }
5624
- };
5625
- /**
5626
- * Declined value if another field's value equals to a specific value
5627
- */
5628
- const declinedIfRule = {
5629
- name: "declinedIf",
5630
- description: "The field must be declined if :field's value equals to :value",
5631
- defaultErrorMessage: "The :input must be declined",
5632
- async validate(value, context) {
5633
- const fieldValue = getFieldValue(this, context);
5634
- const { value: expectedValue } = this.context.options;
5635
- this.context.translatableParams.field = this.context.options.field;
5636
- this.context.translatableParams.value = this.context.options.value;
5637
- if (fieldValue !== expectedValue) return invalidRule(this, context);
5638
- if (!isDeclinedValue(value)) return invalidRule(this, context);
5639
- return VALID_RULE;
5640
- }
5641
- };
5642
- /**
5643
- * Declined if another field is not equal to the given value
5644
- */
5645
- const declinedUnlessRule = {
5646
- name: "declinedUnless",
5647
- description: "The field must be declined if :field's value is not equal to :value",
5648
- defaultErrorMessage: "The :input must be declined",
5649
- async validate(value, context) {
5650
- const fieldValue = getFieldValue(this, context);
5651
- const { value: expectedValue } = this.context.options;
5652
- this.context.translatableParams.field = this.context.options.field;
5653
- this.context.translatableParams.value = this.context.options.value;
5654
- if (fieldValue === expectedValue) return invalidRule(this, context);
5655
- if (!isDeclinedValue(value)) return invalidRule(this, context);
5656
- return VALID_RULE;
5657
- }
5658
- };
5659
- /**
5660
- * Declined if another field is required
5661
- */
5662
- const declinedIfRequiredRule = {
5663
- name: "declinedIfRequired",
5664
- description: "The field must be declined if :field is required",
5665
- defaultErrorMessage: "The :input must be declined",
5666
- async validate(value, context) {
5667
- const fieldValue = getFieldValue(this, context);
5668
- this.context.translatableParams.field = this.context.options.field;
5669
- if (isEmptyValue(fieldValue)) return invalidRule(this, context);
5670
- if (!isDeclinedValue(value)) return invalidRule(this, context);
5671
- return VALID_RULE;
5672
- }
5673
- };
5674
- /**
5675
- * Declined if another field is present
5676
- */
5677
- const declinedIfPresentRule = {
5678
- name: "declinedIfPresent",
5679
- description: "The field must be declined if :field is present",
5680
- defaultErrorMessage: "The :input must be declined",
5681
- async validate(value, context) {
5682
- const fieldValue = getFieldValue(this, context);
5683
- this.context.translatableParams.field = this.context.options.field;
5684
- if (fieldValue === void 0) return invalidRule(this, context);
5685
- if (!isDeclinedValue(value)) return invalidRule(this, context);
5686
- return VALID_RULE;
5687
- }
5688
- };
5689
- /**
5690
- * Declined if another field is missing
5691
- */
5692
- const declinedWithoutRule = {
5693
- name: "declinedWithout",
5694
- description: "The field must be declined if :field is missing",
5695
- defaultErrorMessage: "The :input must be declined",
5696
- async validate(value, context) {
5697
- const fieldValue = getFieldValue(this, context);
5698
- this.context.translatableParams.field = this.context.options.field;
5699
- if (fieldValue !== void 0) return invalidRule(this, context);
5700
- if (!isDeclinedValue(value)) return invalidRule(this, context);
5701
- return VALID_RULE;
5702
- }
5703
- };
5704
-
5705
5754
  //#endregion
5706
5755
  //#region ../seal/src/validators/boolean-validator.ts
5707
5756
  /**
@@ -5722,65 +5771,112 @@ var BooleanValidator = class extends PrimitiveValidator {
5722
5771
  matchesType(value) {
5723
5772
  return typeof value === "boolean";
5724
5773
  }
5725
- /** Value must be accepted (true, "yes", 1, "on", etc.) */
5774
+ /**
5775
+ * Add a rule from the accepted/declined family, opting the validator into
5776
+ * form-boolean parsing.
5777
+ *
5778
+ * Calling ANY `accepted*`/`declined*` method on `v.boolean()` adds (once,
5779
+ * idempotently — checked by mutator identity so chaining several of these
5780
+ * methods never stacks duplicates) `formBooleanMutator`, which runs before
5781
+ * the type rule and converts the accepted set (`1`, `"1"`, `true`,
5782
+ * `"true"`, `"yes"`, `"y"`, `"on"`, `"Yes"`, `"Y"`, `"On"`) to `true` and
5783
+ * the declined set (`0`, `"0"`, `false`, `"false"`, `"no"`, `"n"`, `"off"`,
5784
+ * `"No"`, `"N"`, `"Off"`) to `false`; any other value passes through
5785
+ * unchanged and is rejected by the strict type rule as before.
5786
+ *
5787
+ * The mutator and the rule are added to the SAME resolved instance in one
5788
+ * pass — resolving `this.instance` twice (once per `addRule`/`addMutator`
5789
+ * call) would clone twice on an immutable validator and silently drop one
5790
+ * of the two additions.
5791
+ */
5792
+ addFormBooleanRule(rule, errorMessage, options = {}) {
5793
+ const instance = this.instance;
5794
+ if (!instance.mutators.some((mutator) => mutator.mutate === formBooleanMutator)) instance.addMutableMutator(formBooleanMutator);
5795
+ instance.addMutableRule(rule, errorMessage, options);
5796
+ return instance;
5797
+ }
5798
+ /** Value must be accepted (true, "yes", 1, "on", etc. — form strings are parsed into a real boolean first) */
5726
5799
  accepted(errorMessage) {
5727
- return this.addRule(acceptedRule, errorMessage);
5800
+ return this.addFormBooleanRule(acceptedRule, errorMessage);
5728
5801
  }
5729
5802
  /** Value must be accepted if another field equals a value */
5730
5803
  acceptedIf(field, value, errorMessage) {
5731
- return this.addRule(acceptedIfRule, errorMessage, {
5804
+ return this.addFormBooleanRule(acceptedIfRule, errorMessage, {
5732
5805
  field,
5733
5806
  value
5734
5807
  });
5735
5808
  }
5736
5809
  /** Value must be accepted unless another field equals a value */
5737
5810
  acceptedUnless(field, value, errorMessage) {
5738
- return this.addRule(acceptedUnlessRule, errorMessage, {
5811
+ return this.addFormBooleanRule(acceptedUnlessRule, errorMessage, {
5739
5812
  field,
5740
5813
  value
5741
5814
  });
5742
5815
  }
5743
5816
  /** Value must be accepted if another field is required */
5744
5817
  acceptedIfRequired(field, errorMessage) {
5745
- return this.addRule(acceptedIfRequiredRule, errorMessage, { field });
5818
+ return this.addFormBooleanRule(acceptedIfRequiredRule, errorMessage, { field });
5746
5819
  }
5747
5820
  /** Value must be accepted if another field is present */
5748
5821
  acceptedIfPresent(field, errorMessage) {
5749
- return this.addRule(acceptedIfPresentRule, errorMessage, { field });
5822
+ return this.addFormBooleanRule(acceptedIfPresentRule, errorMessage, { field });
5750
5823
  }
5751
5824
  /** Value must be accepted if another field is missing */
5752
5825
  acceptedWithout(field, errorMessage) {
5753
- return this.addRule(acceptedWithoutRule, errorMessage, { field });
5826
+ return this.addFormBooleanRule(acceptedWithoutRule, errorMessage, { field });
5754
5827
  }
5755
- /** Value must be declined (false, "no", 0, "off", etc.) */
5828
+ /** Value must be declined (false, "no", 0, "off", etc. — form strings are parsed into a real boolean first) */
5756
5829
  declined(errorMessage) {
5757
- return this.addRule(declinedRule, errorMessage);
5830
+ return this.addFormBooleanRule(declinedRule, errorMessage);
5758
5831
  }
5759
5832
  /** Value must be declined if another field equals a value */
5760
5833
  declinedIf(field, value, errorMessage) {
5761
- return this.addRule(declinedIfRule, errorMessage, {
5834
+ return this.addFormBooleanRule(declinedIfRule, errorMessage, {
5762
5835
  field,
5763
5836
  value
5764
5837
  });
5765
5838
  }
5766
5839
  /** Value must be declined unless another field equals a value */
5767
5840
  declinedUnless(field, value, errorMessage) {
5768
- return this.addRule(declinedUnlessRule, errorMessage, {
5841
+ return this.addFormBooleanRule(declinedUnlessRule, errorMessage, {
5769
5842
  field,
5770
5843
  value
5771
5844
  });
5772
5845
  }
5773
5846
  /** Value must be declined if another field is required */
5774
5847
  declinedIfRequired(field, errorMessage) {
5775
- return this.addRule(declinedIfRequiredRule, errorMessage, { field });
5848
+ return this.addFormBooleanRule(declinedIfRequiredRule, errorMessage, { field });
5776
5849
  }
5777
5850
  /** Value must be declined if another field is present */
5778
5851
  declinedIfPresent(field, errorMessage) {
5779
- return this.addRule(declinedIfPresentRule, errorMessage, { field });
5852
+ return this.addFormBooleanRule(declinedIfPresentRule, errorMessage, { field });
5780
5853
  }
5781
5854
  /** Value must be declined if another field is missing */
5782
5855
  declinedWithout(field, errorMessage) {
5783
- return this.addRule(declinedWithoutRule, errorMessage, { field });
5856
+ return this.addFormBooleanRule(declinedWithoutRule, errorMessage, { field });
5857
+ }
5858
+ /**
5859
+ * Opt in to coercion: an exact query-string-shaped boolean is converted to a
5860
+ * real boolean **before** the type rules run; any other value passes through
5861
+ * unchanged so bad input still fails the type rule.
5862
+ *
5863
+ * Converts exactly `"true"` / `"1"` / `1` → `true` and `"false"` / `"0"` / `0`
5864
+ * → `false`. Case-sensitive, no trimming — `"TRUE"`, `"yes"`, `"on"`, `""`, and
5865
+ * `2` all pass through unchanged and fail the type rule. `v.boolean()` does
5866
+ * **not** coerce by default; this only takes effect when explicitly chained.
5867
+ * For form-style truthy strings like `"yes"` / `"on"`, use `.accepted()` /
5868
+ * `.declined()` instead — those methods opt into their own form-boolean
5869
+ * parsing (a wider value set, case-insensitive on a few forms) and then
5870
+ * apply the accepted/declined pass-fail rule on top of the parsed boolean.
5871
+ *
5872
+ * The output type is unchanged (`Infer<>` keys off the validator class), so
5873
+ * `v.boolean().coerce()` still infers `boolean`.
5874
+ *
5875
+ * @example
5876
+ * v.boolean().coerce() // "true" → true; "1" → true; "0" → false; "yes" → invalid
5877
+ */
5878
+ coerce() {
5879
+ return this.addMutator(booleanCoerceMutator);
5784
5880
  }
5785
5881
  /**
5786
5882
  * Value must be strictly true (not "yes", "on", 1, etc.)
@@ -8808,6 +8904,7 @@ exports.betweenNumbersRule = betweenNumbersRule;
8808
8904
  exports.betweenTimesRule = betweenTimesRule;
8809
8905
  exports.betweenYearsRule = betweenYearsRule;
8810
8906
  exports.birthdayRule = birthdayRule;
8907
+ exports.booleanCoerceMutator = booleanCoerceMutator;
8811
8908
  exports.booleanMutator = booleanMutator;
8812
8909
  exports.booleanRule = booleanRule;
8813
8910
  exports.businessDayRule = businessDayRule;
@@ -8839,6 +8936,7 @@ exports.forbiddenIfNotRule = forbiddenIfNotRule;
8839
8936
  exports.forbiddenIfRule = forbiddenIfRule;
8840
8937
  exports.forbiddenMethodsApplied = forbiddenMethodsApplied;
8841
8938
  exports.forbiddenRule = forbiddenRule;
8939
+ exports.formBooleanMutator = formBooleanMutator;
8842
8940
  exports.fromHourRule = fromHourRule;
8843
8941
  exports.fromMinuteRule = fromMinuteRule;
8844
8942
  exports.fromTodayRule = fromTodayRule;