@warlock.js/seal 4.9.0 → 4.9.2
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 +9 -0
- package/cjs/index.cjs +27 -4
- package/cjs/index.cjs.map +1 -1
- package/esm/factory/validate.mjs +6 -1
- package/esm/factory/validate.mjs.map +1 -1
- package/esm/mutators/number-mutators.d.mts +9 -1
- package/esm/mutators/number-mutators.d.mts.map +1 -1
- package/esm/mutators/number-mutators.mjs +10 -2
- package/esm/mutators/number-mutators.mjs.map +1 -1
- package/esm/rules/common/literal.mjs +2 -0
- package/esm/rules/common/literal.mjs.map +1 -1
- package/esm/validators/literal-validator.d.mts.map +1 -1
- package/esm/validators/literal-validator.mjs +3 -0
- package/esm/validators/literal-validator.mjs.map +1 -1
- package/esm/validators/number-validator.d.mts +8 -1
- package/esm/validators/number-validator.d.mts.map +1 -1
- package/esm/validators/number-validator.mjs +8 -1
- package/esm/validators/number-validator.mjs.map +1 -1
- package/llms-full.txt +43 -1
- package/package.json +1 -1
- package/skills/define-structural-shape/object-methods.md +20 -0
- package/skills/pick-seal-primitive/SKILL.md +16 -0
- package/skills/pick-seal-primitive/number-methods.md +7 -1
package/esm/factory/validate.mjs
CHANGED
|
@@ -21,7 +21,12 @@ const validate = async (schema, data, { context: extendedContext, ...configurati
|
|
|
21
21
|
},
|
|
22
22
|
configurations
|
|
23
23
|
};
|
|
24
|
-
|
|
24
|
+
const result = await schema.validate(data, context);
|
|
25
|
+
if (!result.isValid) return {
|
|
26
|
+
...result,
|
|
27
|
+
data: void 0
|
|
28
|
+
};
|
|
29
|
+
return result;
|
|
25
30
|
};
|
|
26
31
|
|
|
27
32
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate.mjs","names":[],"sources":["../../../../../../../seal/src/factory/validate.ts"],"sourcesContent":["import { getSealConfig } from \"../config\";\r\nimport type { SchemaContext, ValidationResult } from \"../types\";\r\nimport type { BaseValidator } from \"../validators\";\r\nimport { type ValidateOptions } from \"./validators\";\r\n\r\n/**\r\n * Validate data against a schema\r\n */\r\nexport const validate = async <T extends BaseValidator>(\r\n schema: T,\r\n data: any, // Temporarily use any - will fix type inference\r\n { context: extendedContext, ...configurations }: ValidateOptions = getSealConfig() || {},\r\n): Promise<ValidationResult> => {\r\n const context: SchemaContext = {\r\n allValues: data,\r\n parent: null,\r\n value: data,\r\n key: \"\",\r\n path: \"\",\r\n context: extendedContext,\r\n rootContext: extendedContext,\r\n translateRule(ruleTranslation) {\r\n return configurations.translateRule?.(ruleTranslation) ?? \"\";\r\n },\r\n translateAttribute(attributeTranslation) {\r\n return configurations.translateAttribute?.(attributeTranslation) ?? \"\";\r\n },\r\n configurations,\r\n };\r\n\r\n
|
|
1
|
+
{"version":3,"file":"validate.mjs","names":[],"sources":["../../../../../../../seal/src/factory/validate.ts"],"sourcesContent":["import { getSealConfig } from \"../config\";\r\nimport type { SchemaContext, ValidationResult } from \"../types\";\r\nimport type { BaseValidator } from \"../validators\";\r\nimport { type ValidateOptions } from \"./validators\";\r\n\r\n/**\r\n * Validate data against a schema\r\n */\r\nexport const validate = async <T extends BaseValidator>(\r\n schema: T,\r\n data: any, // Temporarily use any - will fix type inference\r\n { context: extendedContext, ...configurations }: ValidateOptions = getSealConfig() || {},\r\n): Promise<ValidationResult> => {\r\n const context: SchemaContext = {\r\n allValues: data,\r\n parent: null,\r\n value: data,\r\n key: \"\",\r\n path: \"\",\r\n context: extendedContext,\r\n rootContext: extendedContext,\r\n translateRule(ruleTranslation) {\r\n return configurations.translateRule?.(ruleTranslation) ?? \"\";\r\n },\r\n translateAttribute(attributeTranslation) {\r\n return configurations.translateAttribute?.(attributeTranslation) ?? \"\";\r\n },\r\n configurations,\r\n };\r\n\r\n const result = await schema.validate(data, context);\r\n\r\n // A failed validation never hands back the input it just rejected.\r\n //\r\n // `object` used to return the raw input on failure while `discriminatedUnion`\r\n // returned `undefined` — the same call shape with two different contracts.\r\n // The object behaviour was the dangerous one: validating an outbound DTO\r\n // specifically to keep internal fields out of a response, then doing the\r\n // natural `const { data } = await v.validate(dto, record); reply.send(data)`,\r\n // shipped every field the schema existed to exclude. A guard that returns the\r\n // unsafe value on failure reads as safe and isn't.\r\n if (!result.isValid) {\r\n return { ...result, data: undefined };\r\n }\r\n\r\n return result;\r\n};\r\n"],"mappings":";;;;;;AAQA,MAAa,WAAW,OACtB,QACA,MACA,EAAE,SAAS,iBAAiB,GAAG,mBAAoC,cAAc,KAAK,CAAC,MACzD;CAC9B,MAAM,UAAyB;EAC7B,WAAW;EACX,QAAQ;EACR,OAAO;EACP,KAAK;EACL,MAAM;EACN,SAAS;EACT,aAAa;EACb,cAAc,iBAAiB;GAC7B,OAAO,eAAe,gBAAgB,eAAe,KAAK;EAC5D;EACA,mBAAmB,sBAAsB;GACvC,OAAO,eAAe,qBAAqB,oBAAoB,KAAK;EACtE;EACA;CACF;CAEA,MAAM,SAAS,MAAM,OAAO,SAAS,MAAM,OAAO;CAWlD,IAAI,CAAC,OAAO,SACV,OAAO;EAAE,GAAG;EAAQ,MAAM;CAAU;CAGtC,OAAO;AACT"}
|
|
@@ -19,7 +19,15 @@ declare const floorMutator: Mutator;
|
|
|
19
19
|
* Supports decimal precision via options.decimals
|
|
20
20
|
*/
|
|
21
21
|
declare const roundMutator: Mutator;
|
|
22
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* To fixed mutator — rounds to a fixed number of decimal places, as a number.
|
|
24
|
+
*
|
|
25
|
+
* `Number.prototype.toFixed` returns a *string*, which the number validator's
|
|
26
|
+
* own type rule then rejected: `v.number().toFixed(2)` could never produce a
|
|
27
|
+
* valid result. Coercing back to a number keeps the method usable where it
|
|
28
|
+
* lives. For the fixed-point string form, format at the edge instead of asking
|
|
29
|
+
* a number schema to output a string.
|
|
30
|
+
*/
|
|
23
31
|
declare const toFixedMutator: Mutator;
|
|
24
32
|
//#endregion
|
|
25
33
|
export { absMutator, booleanMutator, ceilMutator, floorMutator, numberMutator, numericMutator, roundMutator, roundNumberMutator, toFixedMutator };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"number-mutators.d.mts","names":[],"sources":["../../../../../../../seal/src/mutators/number-mutators.ts"],"mappings":";;;;cAKa,aAAA,EAAe,OAG3B;AAHD;AAAA,cAMa,kBAAA,EAAoB,OAEhC;;cAGY,cAAA,EAAgB,OAI5B;AAAA,cAEY,cAAA,EAAgB,OAG5B;AAdD;AAAA,cAiBa,UAAA,EAAY,OAExB;;cAGY,WAAA,EAAa,OAEzB;AAtBA;AAAA,cAyBY,YAAA,EAAc,OAE1B;;;;AApBA;cA0BY,YAAA,EAAc,OAQ1B
|
|
1
|
+
{"version":3,"file":"number-mutators.d.mts","names":[],"sources":["../../../../../../../seal/src/mutators/number-mutators.ts"],"mappings":";;;;cAKa,aAAA,EAAe,OAG3B;AAHD;AAAA,cAMa,kBAAA,EAAoB,OAEhC;;cAGY,cAAA,EAAgB,OAI5B;AAAA,cAEY,cAAA,EAAgB,OAG5B;AAdD;AAAA,cAiBa,UAAA,EAAY,OAExB;;cAGY,WAAA,EAAa,OAEzB;AAtBA;AAAA,cAyBY,YAAA,EAAc,OAE1B;;;;AApBA;cA0BY,YAAA,EAAc,OAQ1B;;;;AA7BA;AAGD;;;;AAEC;cAmCY,cAAA,EAAgB,OAG5B"}
|
|
@@ -42,10 +42,18 @@ const roundMutator = async (value, context) => {
|
|
|
42
42
|
if (decimals === 0) return Math.round(Number(value));
|
|
43
43
|
return round(Number(value), decimals);
|
|
44
44
|
};
|
|
45
|
-
/**
|
|
45
|
+
/**
|
|
46
|
+
* To fixed mutator — rounds to a fixed number of decimal places, as a number.
|
|
47
|
+
*
|
|
48
|
+
* `Number.prototype.toFixed` returns a *string*, which the number validator's
|
|
49
|
+
* own type rule then rejected: `v.number().toFixed(2)` could never produce a
|
|
50
|
+
* valid result. Coercing back to a number keeps the method usable where it
|
|
51
|
+
* lives. For the fixed-point string form, format at the edge instead of asking
|
|
52
|
+
* a number schema to output a string.
|
|
53
|
+
*/
|
|
46
54
|
const toFixedMutator = async (value, context) => {
|
|
47
55
|
const decimals = context?.options?.decimals ?? 2;
|
|
48
|
-
return Number(value).toFixed(decimals);
|
|
56
|
+
return Number(Number(value).toFixed(decimals));
|
|
49
57
|
};
|
|
50
58
|
|
|
51
59
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"number-mutators.mjs","names":[],"sources":["../../../../../../../seal/src/mutators/number-mutators.ts"],"sourcesContent":["import { round } from \"@mongez/reinforcements\";\r\nimport { isNumeric } from \"@mongez/supportive-is\";\r\nimport type { Mutator } from \"../types\";\r\n\r\n/** Convert value to number */\r\nexport const numberMutator: Mutator = async (value) => {\r\n if (!value) return value;\r\n return Number(value);\r\n};\r\n\r\n/** Round number to specified decimals */\r\nexport const roundNumberMutator: Mutator = async (value, context) => {\r\n return round(value, context?.options?.decimals ?? 2);\r\n};\r\n\r\n/** Convert to boolean */\r\nexport const booleanMutator: Mutator = async (value) => {\r\n if (value === \"true\") return true;\r\n if (value === \"false\") return false;\r\n return Boolean(value);\r\n};\r\n\r\nexport const numericMutator: Mutator = async (value) => {\r\n if (!isNumeric(value)) return value;\r\n return Number(value);\r\n};\r\n\r\n/** Absolute value mutator */\r\nexport const absMutator: Mutator = async (value) => {\r\n return Math.abs(Number(value));\r\n};\r\n\r\n/** Ceil mutator */\r\nexport const ceilMutator: Mutator = async (value) => {\r\n return Math.ceil(Number(value));\r\n};\r\n\r\n/** Floor mutator */\r\nexport const floorMutator: Mutator = async (value) => {\r\n return Math.floor(Number(value));\r\n};\r\n\r\n/**\r\n * Round mutator\r\n * Supports decimal precision via options.decimals\r\n */\r\nexport const roundMutator: Mutator = async (value, context) => {\r\n const decimals = context?.options?.decimals ?? 0;\r\n // If decimals is 0, use standard Math.round for integers\r\n if (decimals === 0) {\r\n return Math.round(Number(value));\r\n }\r\n // Otherwise use reinforcements round helper for precision\r\n return round(Number(value), decimals);\r\n};\r\n\r\n
|
|
1
|
+
{"version":3,"file":"number-mutators.mjs","names":[],"sources":["../../../../../../../seal/src/mutators/number-mutators.ts"],"sourcesContent":["import { round } from \"@mongez/reinforcements\";\r\nimport { isNumeric } from \"@mongez/supportive-is\";\r\nimport type { Mutator } from \"../types\";\r\n\r\n/** Convert value to number */\r\nexport const numberMutator: Mutator = async (value) => {\r\n if (!value) return value;\r\n return Number(value);\r\n};\r\n\r\n/** Round number to specified decimals */\r\nexport const roundNumberMutator: Mutator = async (value, context) => {\r\n return round(value, context?.options?.decimals ?? 2);\r\n};\r\n\r\n/** Convert to boolean */\r\nexport const booleanMutator: Mutator = async (value) => {\r\n if (value === \"true\") return true;\r\n if (value === \"false\") return false;\r\n return Boolean(value);\r\n};\r\n\r\nexport const numericMutator: Mutator = async (value) => {\r\n if (!isNumeric(value)) return value;\r\n return Number(value);\r\n};\r\n\r\n/** Absolute value mutator */\r\nexport const absMutator: Mutator = async (value) => {\r\n return Math.abs(Number(value));\r\n};\r\n\r\n/** Ceil mutator */\r\nexport const ceilMutator: Mutator = async (value) => {\r\n return Math.ceil(Number(value));\r\n};\r\n\r\n/** Floor mutator */\r\nexport const floorMutator: Mutator = async (value) => {\r\n return Math.floor(Number(value));\r\n};\r\n\r\n/**\r\n * Round mutator\r\n * Supports decimal precision via options.decimals\r\n */\r\nexport const roundMutator: Mutator = async (value, context) => {\r\n const decimals = context?.options?.decimals ?? 0;\r\n // If decimals is 0, use standard Math.round for integers\r\n if (decimals === 0) {\r\n return Math.round(Number(value));\r\n }\r\n // Otherwise use reinforcements round helper for precision\r\n return round(Number(value), decimals);\r\n};\r\n\r\n/**\r\n * To fixed mutator — rounds to a fixed number of decimal places, as a number.\r\n *\r\n * `Number.prototype.toFixed` returns a *string*, which the number validator's\r\n * own type rule then rejected: `v.number().toFixed(2)` could never produce a\r\n * valid result. Coercing back to a number keeps the method usable where it\r\n * lives. For the fixed-point string form, format at the edge instead of asking\r\n * a number schema to output a string.\r\n */\r\nexport const toFixedMutator: Mutator = async (value, context) => {\r\n const decimals = context?.options?.decimals ?? 2;\r\n return Number(Number(value).toFixed(decimals));\r\n};\r\n"],"mappings":";;;;;AAKA,MAAa,gBAAyB,OAAO,UAAU;CACrD,IAAI,CAAC,OAAO,OAAO;CACnB,OAAO,OAAO,KAAK;AACrB;;AAGA,MAAa,qBAA8B,OAAO,OAAO,YAAY;CACnE,OAAO,MAAM,OAAO,SAAS,SAAS,YAAY,CAAC;AACrD;;AAGA,MAAa,iBAA0B,OAAO,UAAU;CACtD,IAAI,UAAU,QAAQ,OAAO;CAC7B,IAAI,UAAU,SAAS,OAAO;CAC9B,OAAO,QAAQ,KAAK;AACtB;AAEA,MAAa,iBAA0B,OAAO,UAAU;CACtD,IAAI,CAAC,UAAU,KAAK,GAAG,OAAO;CAC9B,OAAO,OAAO,KAAK;AACrB;;AAGA,MAAa,aAAsB,OAAO,UAAU;CAClD,OAAO,KAAK,IAAI,OAAO,KAAK,CAAC;AAC/B;;AAGA,MAAa,cAAuB,OAAO,UAAU;CACnD,OAAO,KAAK,KAAK,OAAO,KAAK,CAAC;AAChC;;AAGA,MAAa,eAAwB,OAAO,UAAU;CACpD,OAAO,KAAK,MAAM,OAAO,KAAK,CAAC;AACjC;;;;;AAMA,MAAa,eAAwB,OAAO,OAAO,YAAY;CAC7D,MAAM,WAAW,SAAS,SAAS,YAAY;CAE/C,IAAI,aAAa,GACf,OAAO,KAAK,MAAM,OAAO,KAAK,CAAC;CAGjC,OAAO,MAAM,OAAO,KAAK,GAAG,QAAQ;AACtC;;;;;;;;;;AAWA,MAAa,iBAA0B,OAAO,OAAO,YAAY;CAC/D,MAAM,WAAW,SAAS,SAAS,YAAY;CAC/C,OAAO,OAAO,OAAO,KAAK,CAAC,CAAC,QAAQ,QAAQ,CAAC;AAC/C"}
|
|
@@ -11,8 +11,10 @@ import "../../helpers/index.mjs";
|
|
|
11
11
|
*/
|
|
12
12
|
const literalRule = {
|
|
13
13
|
name: "literal",
|
|
14
|
+
requiresValue: false,
|
|
14
15
|
defaultErrorMessage: "The :input must be one of the following values: :values",
|
|
15
16
|
async validate(value, context) {
|
|
17
|
+
if (value === void 0) return VALID_RULE;
|
|
16
18
|
if (this.context.options.values.includes(value)) return VALID_RULE;
|
|
17
19
|
this.context.translationParams.values = this.context.options.values.map((v) => resolveTranslation({
|
|
18
20
|
key: String(v),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"literal.mjs","names":[],"sources":["../../../../../../../../seal/src/rules/common/literal.ts"],"sourcesContent":["import { invalidRule, resolveTranslation, VALID_RULE } from \"../../helpers\";\nimport type { SchemaRule } from \"../../types\";\n\n/**\n * Literal rule - value must be strictly equal to one of the literal values.\n *\n * Uses === (referential / strict equality), so distinguishes 1 from \"1\",\n * true from \"true\", etc. Mirrors `v.string().oneOf([...])` but at the\n * literal type level (TypeScript narrows to the union of literals).\n */\nexport const literalRule: SchemaRule<{ values: readonly (string | number | boolean)[] }> = {\n name: \"literal\",\n defaultErrorMessage: \"The :input must be one of the following values: :values\",\n async validate(value: any, context) {\n if (this.context.options.values.includes(value)) {\n return VALID_RULE;\n }\n\n this.context.translationParams.values = this.context.options.values\n .map(v =>\n resolveTranslation({ key: String(v), rawValue: String(v), rule: this, context }),\n )\n .join(\", \");\n\n return invalidRule(this, context);\n },\n};\n"],"mappings":";;;;;;;;;;;AAUA,MAAa,cAA8E;CACzF,MAAM;
|
|
1
|
+
{"version":3,"file":"literal.mjs","names":[],"sources":["../../../../../../../../seal/src/rules/common/literal.ts"],"sourcesContent":["import { invalidRule, resolveTranslation, VALID_RULE } from \"../../helpers\";\nimport type { SchemaRule } from \"../../types\";\n\n/**\n * Literal rule - value must be strictly equal to one of the literal values.\n *\n * Uses === (referential / strict equality), so distinguishes 1 from \"1\",\n * true from \"true\", etc. Mirrors `v.string().oneOf([...])` but at the\n * literal type level (TypeScript narrows to the union of literals).\n */\nexport const literalRule: SchemaRule<{ values: readonly (string | number | boolean)[] }> = {\n name: \"literal\",\n // Runs even on \"empty\" values. A literal set is an exact-match whitelist, so\n // there is no value it cannot safely be asked about — and `\"\"` is one of the\n // values callers most want to pin (`alt=\"\"` for a decorative image). Left at\n // the default, the empty string would skip this check entirely, which made\n // `v.literal(\"\").optional()` accept `\"\"`, `null` AND a missing key alike.\n requiresValue: false,\n defaultErrorMessage: \"The :input must be one of the following values: :values\",\n async validate(value: any, context) {\n // Absence is the required/present rule's question, not this one. Without\n // this, `requiresValue: false` above would make the literal check fire on a\n // missing key and defeat `.optional()`, which works by removing that rule.\n if (value === undefined) {\n return VALID_RULE;\n }\n\n if (this.context.options.values.includes(value)) {\n return VALID_RULE;\n }\n\n this.context.translationParams.values = this.context.options.values\n .map(v =>\n resolveTranslation({ key: String(v), rawValue: String(v), rule: this, context }),\n )\n .join(\", \");\n\n return invalidRule(this, context);\n },\n};\n"],"mappings":";;;;;;;;;;;AAUA,MAAa,cAA8E;CACzF,MAAM;CAMN,eAAe;CACf,qBAAqB;CACrB,MAAM,SAAS,OAAY,SAAS;EAIlC,IAAI,UAAU,QACZ,OAAO;EAGT,IAAI,KAAK,QAAQ,QAAQ,OAAO,SAAS,KAAK,GAC5C,OAAO;EAGT,KAAK,QAAQ,kBAAkB,SAAS,KAAK,QAAQ,QAAQ,OAC1D,KAAI,MACH,mBAAmB;GAAE,KAAK,OAAO,CAAC;GAAG,UAAU,OAAO,CAAC;GAAG,MAAM;GAAM;EAAQ,CAAC,CACjF,CAAC,CACA,KAAK,IAAI;EAEZ,OAAO,YAAY,MAAM,OAAO;CAClC;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"literal-validator.d.mts","names":[],"sources":["../../../../../../../seal/src/validators/literal-validator.ts"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"literal-validator.d.mts","names":[],"sources":["../../../../../../../seal/src/validators/literal-validator.ts"],"mappings":";;;;;;AAqBA;;;;;;;;;;;;cAAa,gBAAA,oGAEH,aAAA;EACD,MAAA,EAAQ,CAAA;cAEI,MAAA,EAAQ,CAAA,EAAG,YAAA;EAAH;;;EAuBpB,WAAA,CAAY,KAAA;EAAA;;;;;;;EAWH,KAAA;;;;;;;;;;;;;;;EAoBA,YAAA,CAAa,MAAA,GAAQ,gBAAA,GAAqC,gBAAA;AAAA"}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { applyNullable } from "../standard-schema/json-schema.mjs";
|
|
2
|
+
import { isEmptyValue } from "../helpers/is-empty-value.mjs";
|
|
3
|
+
import { presentRule } from "../rules/core/required.mjs";
|
|
2
4
|
import { BaseValidator } from "./base-validator.mjs";
|
|
3
5
|
import { literalRule } from "../rules/common/literal.mjs";
|
|
4
6
|
|
|
@@ -22,6 +24,7 @@ var LiteralValidator = class extends BaseValidator {
|
|
|
22
24
|
super();
|
|
23
25
|
this.values = values;
|
|
24
26
|
this.addMutableRule(literalRule, errorMessage, { values });
|
|
27
|
+
if (values.some((value) => isEmptyValue(value))) this.requiredRule = this.createRule(presentRule);
|
|
25
28
|
}
|
|
26
29
|
/**
|
|
27
30
|
* Check if value is one of the configured literals
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"literal-validator.mjs","names":[],"sources":["../../../../../../../seal/src/validators/literal-validator.ts"],"sourcesContent":["import { literalRule } from \"../rules/common/literal\";\nimport { applyNullable } from \"../standard-schema/json-schema\";\nimport type { JsonSchemaResult, JsonSchemaTarget } from \"../standard-schema/json-schema\";\nimport { BaseValidator } from \"./base-validator\";\n\n/**\n * Literal validator class\n *\n * Accepts a fixed tuple of primitive literal values (string, number, boolean).\n * The TypeScript type narrows to the union of those literals — `v.literal(\"a\", \"b\")`\n * infers as `\"a\" | \"b\"`, not `string`. Use for discriminator fields, enum-like\n * unions of constants, and any case where `oneOf([...])` would lose literal types.\n *\n * @example\n * v.literal(\"items\") // type: \"items\"\n * v.literal(\"draft\", \"published\") // type: \"draft\" | \"published\"\n * v.literal(1, 2, 3) // type: 1 | 2 | 3\n * v.literal(true) // type: true\n */\nexport class LiteralValidator<\n T extends readonly (string | number | boolean)[] = readonly (string | number | boolean)[],\n> extends BaseValidator {\n public values: T;\n\n public constructor(values: T, errorMessage?: string) {\n super();\n this.values = values;\n this.addMutableRule(literalRule, errorMessage, { values });\n }\n\n /**\n * Check if value is one of the configured literals\n */\n public matchesType(value: any): boolean {\n return (this.values as readonly any[]).includes(value);\n }\n\n /**\n * Clone the validator, preserving the literal `values` set.\n *\n * The base `clone()` only copies `BaseValidator` fields, so without this\n * override a cloned literal loses its public `values` array — which breaks\n * any consumer that reads it (e.g. `discriminatedUnion` branch routing).\n */\n public override clone(): this {\n const cloned = super.clone();\n cloned.values = this.values;\n return cloned;\n }\n\n /**\n * @inheritdoc\n *\n * Single literal → `{ const: <value> }`. Multiple → `{ enum: [...] }`.\n *\n * @example\n * ```ts\n * v.literal(\"items\").toJsonSchema()\n * // → { const: \"items\" }\n *\n * v.literal(\"draft\", \"published\").toJsonSchema()\n * // → { enum: [\"draft\", \"published\"] }\n * ```\n */\n public override toJsonSchema(target: JsonSchemaTarget = \"draft-2020-12\"): JsonSchemaResult {\n const schema: JsonSchemaResult =\n this.values.length === 1 ? { const: this.values[0] } : { enum: [...this.values] };\n if (this.isNullable) applyNullable(schema, target);\n return schema;\n }\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"literal-validator.mjs","names":[],"sources":["../../../../../../../seal/src/validators/literal-validator.ts"],"sourcesContent":["import { isEmptyValue } from \"../helpers/is-empty-value\";\nimport { literalRule } from \"../rules/common/literal\";\nimport { presentRule } from \"../rules/core/required\";\nimport { applyNullable } from \"../standard-schema/json-schema\";\nimport type { JsonSchemaResult, JsonSchemaTarget } from \"../standard-schema/json-schema\";\nimport { BaseValidator } from \"./base-validator\";\n\n/**\n * Literal validator class\n *\n * Accepts a fixed tuple of primitive literal values (string, number, boolean).\n * The TypeScript type narrows to the union of those literals — `v.literal(\"a\", \"b\")`\n * infers as `\"a\" | \"b\"`, not `string`. Use for discriminator fields, enum-like\n * unions of constants, and any case where `oneOf([...])` would lose literal types.\n *\n * @example\n * v.literal(\"items\") // type: \"items\"\n * v.literal(\"draft\", \"published\") // type: \"draft\" | \"published\"\n * v.literal(1, 2, 3) // type: 1 | 2 | 3\n * v.literal(true) // type: true\n */\nexport class LiteralValidator<\n T extends readonly (string | number | boolean)[] = readonly (string | number | boolean)[],\n> extends BaseValidator {\n public values: T;\n\n public constructor(values: T, errorMessage?: string) {\n super();\n this.values = values;\n this.addMutableRule(literalRule, errorMessage, { values });\n\n // `\"\"` is a value, not an absence. Every validator is required by default,\n // and `required` rejects anything `isEmptyValue` calls empty — including the\n // empty string — so `v.literal(\"\")` could never pass. When the author has\n // explicitly listed an empty value as acceptable, swap `required` for\n // `present`: the key must still exist, but judging the value is the literal\n // set's job, and it is already the stricter statement of intent.\n //\n // Dropping the rule entirely instead would make a missing key valid, which\n // is the opposite mistake — `alt=\"\"` and no `alt` at all are different\n // things to a screen reader, and to anything else reading the schema.\n if (values.some(value => isEmptyValue(value))) {\n this.requiredRule = this.createRule(presentRule);\n }\n }\n\n /**\n * Check if value is one of the configured literals\n */\n public matchesType(value: any): boolean {\n return (this.values as readonly any[]).includes(value);\n }\n\n /**\n * Clone the validator, preserving the literal `values` set.\n *\n * The base `clone()` only copies `BaseValidator` fields, so without this\n * override a cloned literal loses its public `values` array — which breaks\n * any consumer that reads it (e.g. `discriminatedUnion` branch routing).\n */\n public override clone(): this {\n const cloned = super.clone();\n cloned.values = this.values;\n return cloned;\n }\n\n /**\n * @inheritdoc\n *\n * Single literal → `{ const: <value> }`. Multiple → `{ enum: [...] }`.\n *\n * @example\n * ```ts\n * v.literal(\"items\").toJsonSchema()\n * // → { const: \"items\" }\n *\n * v.literal(\"draft\", \"published\").toJsonSchema()\n * // → { enum: [\"draft\", \"published\"] }\n * ```\n */\n public override toJsonSchema(target: JsonSchemaTarget = \"draft-2020-12\"): JsonSchemaResult {\n const schema: JsonSchemaResult =\n this.values.length === 1 ? { const: this.values[0] } : { enum: [...this.values] };\n if (this.isNullable) applyNullable(schema, target);\n return schema;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAqBA,IAAa,mBAAb,cAEU,cAAc;CAGtB,AAAO,YAAY,QAAW,cAAuB;EACnD,MAAM;EACN,KAAK,SAAS;EACd,KAAK,eAAe,aAAa,cAAc,EAAE,OAAO,CAAC;EAYzD,IAAI,OAAO,MAAK,UAAS,aAAa,KAAK,CAAC,GAC1C,KAAK,eAAe,KAAK,WAAW,WAAW;CAEnD;;;;CAKA,AAAO,YAAY,OAAqB;EACtC,OAAQ,KAAK,OAA0B,SAAS,KAAK;CACvD;;;;;;;;CASA,AAAgB,QAAc;EAC5B,MAAM,SAAS,MAAM,MAAM;EAC3B,OAAO,SAAS,KAAK;EACrB,OAAO;CACT;;;;;;;;;;;;;;;CAgBA,AAAgB,aAAa,SAA2B,iBAAmC;EACzF,MAAM,SACJ,KAAK,OAAO,WAAW,IAAI,EAAE,OAAO,KAAK,OAAO,GAAG,IAAI,EAAE,MAAM,CAAC,GAAG,KAAK,MAAM,EAAE;EAClF,IAAI,KAAK,YAAY,cAAc,QAAQ,MAAM;EACjD,OAAO;CACT;AACF"}
|
|
@@ -139,7 +139,14 @@ declare class NumberValidator extends PrimitiveValidator {
|
|
|
139
139
|
*/
|
|
140
140
|
round(decimals?: number): this;
|
|
141
141
|
/**
|
|
142
|
-
*
|
|
142
|
+
* Round to a fixed number of decimal places, keeping the value a **number**.
|
|
143
|
+
*
|
|
144
|
+
* `v.number().toFixed(2)` on `3.14159` yields `3.14`, not `"3.14"` — a number
|
|
145
|
+
* schema has to output a number, or its own type rule rejects the result.
|
|
146
|
+
* Format to a fixed-point *string* at the presentation edge instead.
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* v.number().toFixed(2) // 3.14159 → 3.14
|
|
143
150
|
*/
|
|
144
151
|
toFixed(decimals?: number): this;
|
|
145
152
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"number-validator.d.mts","names":[],"sources":["../../../../../../../seal/src/validators/number-validator.ts"],"mappings":";;;;;;AAwBA;cAAa,eAAA,SAAwB,kBAAA;cAChB,YAAA;
|
|
1
|
+
{"version":3,"file":"number-validator.d.mts","names":[],"sources":["../../../../../../../seal/src/validators/number-validator.ts"],"mappings":";;;;;;AAwBA;cAAa,eAAA,SAAwB,kBAAA;cAChB,YAAA;EAuRkB;;;EA/Q9B,WAAA,CAAY,KAAA;EATgB;;;;;;EAmB5B,GAAA,CAAI,GAAA,mBAAsB,YAAA;EAVd;;;;;;EAoBZ,GAAA,CAAI,GAAA,mBAAsB,YAAA;EAQ1B;;;;EAAA,UAAA,CAAW,KAAA,UAAe,YAAA;EAQA;;;;EAA1B,UAAA,CAAW,KAAA,UAAe,YAAA;EAuBjB;;;;;;EAbT,WAAA,CAAY,KAAA,mBAAwB,YAAA;EAgCT;;;;;;EAnB3B,QAAA,CAAS,KAAA,mBAAwB,YAAA;EA8CjC;;;;EAnCA,EAAA,CAAG,KAAA,mBAAwB,YAAA;EA8CF;;;;EAtCzB,EAAA,CAAG,KAAA,mBAAwB,YAAA;EAkDf;;;;EA1CZ,kBAAA,CAAmB,KAAA,UAAe,YAAA;EAwDlC;;;;EA7CA,SAAA,CAAU,KAAA,UAAe,YAAA;EAuDzB;;;;EA/CA,eAAA,CAAgB,KAAA,UAAe,YAAA;EAyD1B;;;;EA9CL,SAAA,CAAU,KAAA,UAAe,YAAA;EAoEzB;EA/DA,MAAA,CAAO,KAAA,UAAe,YAAA;EA+DW;;;EAxDjC,WAAA,CAAY,KAAA,UAAe,YAAA;EAsEJ;;;EA/DvB,UAAA,CAAW,KAAA,UAAe,YAAA;EAyE1B;;;EAlEA,SAAA,CAAU,KAAA,UAAe,YAAA;EAkFzB;EA7EA,QAAA,CAAS,YAAA;EA2FT;EAtFA,QAAA,CAAS,YAAA;EAoGT;EA/FA,GAAA,CAAI,YAAA;EAkHK;EA7GT,IAAA,CAAK,YAAA;EA6GiB;;;;;;EAnGtB,OAAA,CAAQ,GAAA,mBAAsB,GAAA,mBAAsB,YAAA;EA+GxC;AAAA;;;EAnGZ,cAAA,CAAe,QAAA,UAAkB,QAAA,UAAkB,YAAA;;;;;EAcnD,MAAA,CAAO,MAAA,UAAgB,YAAA;;EAKvB,SAAA,CAAU,MAAA,UAAgB,YAAA;;EAK1B,SAAA,CAAU,MAAA,UAAgB,YAAA;;;;EAS1B,GAAA;;;;EAOA,IAAA;;;;EAOA,KAAA;;;;EAOA,KAAA,CAAM,QAAA;;;;;;;;;;;EAcN,OAAA,CAAQ,QAAA;;;;;;;;;;;;;;;;EAmBC,YAAA,CAAa,MAAA,GAAQ,gBAAA,GAAqC,gBAAA;;;;;;YAShE,qBAAA,CACR,IAAA,wBACA,MAAA,EAAQ,gBAAA,GACP,gBAAA;AAAA"}
|
|
@@ -240,7 +240,14 @@ var NumberValidator = class extends PrimitiveValidator {
|
|
|
240
240
|
return this.addMutator(roundMutator, { decimals });
|
|
241
241
|
}
|
|
242
242
|
/**
|
|
243
|
-
*
|
|
243
|
+
* Round to a fixed number of decimal places, keeping the value a **number**.
|
|
244
|
+
*
|
|
245
|
+
* `v.number().toFixed(2)` on `3.14159` yields `3.14`, not `"3.14"` — a number
|
|
246
|
+
* schema has to output a number, or its own type rule rejects the result.
|
|
247
|
+
* Format to a fixed-point *string* at the presentation edge instead.
|
|
248
|
+
*
|
|
249
|
+
* @example
|
|
250
|
+
* v.number().toFixed(2) // 3.14159 → 3.14
|
|
244
251
|
*/
|
|
245
252
|
toFixed(decimals = 2) {
|
|
246
253
|
return this.addMutator(toFixedMutator, { decimals });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"number-validator.mjs","names":[],"sources":["../../../../../../../seal/src/validators/number-validator.ts"],"sourcesContent":["import { absMutator, ceilMutator, floorMutator, roundMutator, toFixedMutator } from \"../mutators\";\r\nimport {\r\n betweenNumbersRule,\r\n evenRule,\r\n greaterThanRule,\r\n lengthRule,\r\n lessThanRule,\r\n maxLengthRule,\r\n maxRule,\r\n minLengthRule,\r\n minRule,\r\n moduloRule,\r\n negativeRule,\r\n numberRule,\r\n oddRule,\r\n positiveRule,\r\n} from \"../rules\";\r\nimport { PrimitiveValidator } from \"./primitive-validator\";\r\nimport { applyNullable, getRuleOptions } from \"../standard-schema/json-schema\";\r\nimport type { JsonSchemaResult, JsonSchemaTarget } from \"../standard-schema/json-schema\";\r\n\r\n/**\r\n * Number validator class - base for Int and Float validators\r\n */\r\nexport class NumberValidator extends PrimitiveValidator {\r\n public constructor(errorMessage?: string) {\r\n super();\r\n this.addMutableRule(numberRule, errorMessage);\r\n }\r\n\r\n /**\r\n * Check if value is a number type\r\n */\r\n public matchesType(value: any): boolean {\r\n return typeof value === \"number\" && !isNaN(value);\r\n }\r\n\r\n /**\r\n * Value must be equal or higher than the given number or field\r\n * Smart detection: number or field name\r\n *\r\n * @category Validation Rule\r\n */\r\n public min(min: number | string, errorMessage?: string) {\r\n return this.addRule(minRule, errorMessage, { min, scope: \"global\" });\r\n }\r\n\r\n /**\r\n * Value must be equal or less than the given number or field\r\n * Smart detection: number or field name\r\n *\r\n * @category Validation Rule\r\n */\r\n public max(max: number | string, errorMessage?: string) {\r\n return this.addRule(maxRule, errorMessage, { max, scope: \"global\" });\r\n }\r\n\r\n /**\r\n * Value must be >= sibling field value\r\n * @category Validation Rule\r\n */\r\n public minSibling(field: string, errorMessage?: string) {\r\n return this.addRule(minRule, errorMessage, { min: field, scope: \"sibling\" });\r\n }\r\n\r\n /**\r\n * Value must be <= sibling field value\r\n * @category Validation Rule\r\n */\r\n public maxSibling(field: string, errorMessage?: string) {\r\n return this.addRule(maxRule, errorMessage, { max: field, scope: \"sibling\" });\r\n }\r\n\r\n /**\r\n * Value must be strictly greater than the given number or field (>)\r\n * Smart detection: number or field name\r\n *\r\n * @category Validation Rule\r\n */\r\n public greaterThan(value: number | string, errorMessage?: string) {\r\n return this.addRule(greaterThanRule, errorMessage, {\r\n value,\r\n scope: \"global\",\r\n });\r\n }\r\n\r\n /**\r\n * Value must be strictly less than the given number or field (<)\r\n * Smart detection: number or field name\r\n *\r\n * @category Validation Rule\r\n */\r\n public lessThan(value: number | string, errorMessage?: string) {\r\n return this.addRule(lessThanRule, errorMessage, {\r\n value,\r\n scope: \"global\",\r\n });\r\n }\r\n\r\n /**\r\n * Alias for greaterThan() - shorter syntax\r\n * @category Validation Rule\r\n */\r\n public gt(value: number | string, errorMessage?: string) {\r\n return this.greaterThan(value, errorMessage);\r\n }\r\n\r\n /**\r\n * Alias for lessThan() - shorter syntax\r\n * @category Validation Rule\r\n */\r\n public lt(value: number | string, errorMessage?: string) {\r\n return this.lessThan(value, errorMessage);\r\n }\r\n\r\n /**\r\n * Value must be > sibling field value\r\n * @category Validation Rule\r\n */\r\n public greaterThanSibling(field: string, errorMessage?: string) {\r\n return this.addRule(greaterThanRule, errorMessage, {\r\n value: field,\r\n scope: \"sibling\",\r\n });\r\n }\r\n\r\n /**\r\n * Alias for greaterThanSibling() - shorter syntax\r\n * @category Validation Rule\r\n */\r\n public gtSibling(field: string, errorMessage?: string) {\r\n return this.greaterThanSibling(field, errorMessage);\r\n }\r\n\r\n /**\r\n * Value must be < sibling field value\r\n * @category Validation Rule\r\n */\r\n public lessThanSibling(field: string, errorMessage?: string) {\r\n return this.addRule(lessThanRule, errorMessage, {\r\n value: field,\r\n scope: \"sibling\",\r\n });\r\n }\r\n\r\n /**\r\n * Alias for lessThanSibling() - shorter syntax\r\n * @category Validation Rule\r\n */\r\n public ltSibling(field: string, errorMessage?: string) {\r\n return this.lessThanSibling(field, errorMessage);\r\n }\r\n\r\n /** Value must be a modulo of the given number */\r\n public modulo(value: number, errorMessage?: string) {\r\n return this.addRule(moduloRule, errorMessage, { value });\r\n }\r\n\r\n /**\r\n * Alias for modulo() - Value must be divisible by the given number\r\n */\r\n public divisibleBy(value: number, errorMessage?: string) {\r\n return this.modulo(value, errorMessage);\r\n }\r\n\r\n /**\r\n * Alias for modulo() - Value must be a multiple of the given number\r\n */\r\n public multipleOf(value: number, errorMessage?: string) {\r\n return this.modulo(value, errorMessage);\r\n }\r\n\r\n /**\r\n * Alias for modulo() - Value must be a multiple of the given number\r\n */\r\n public modulusOf(value: number, errorMessage?: string) {\r\n return this.modulo(value, errorMessage);\r\n }\r\n\r\n /** Accept only numbers higher than 0 */\r\n public positive(errorMessage?: string) {\r\n return this.addRule(positiveRule, errorMessage);\r\n }\r\n\r\n /** Accept only negative numbers */\r\n public negative(errorMessage?: string) {\r\n return this.addRule(negativeRule, errorMessage);\r\n }\r\n\r\n /** Accept only odd numbers */\r\n public odd(errorMessage?: string) {\r\n return this.addRule(oddRule, errorMessage);\r\n }\r\n\r\n /** Accept only even numbers */\r\n public even(errorMessage?: string) {\r\n return this.addRule(evenRule, errorMessage);\r\n }\r\n\r\n /**\r\n * Accept only numbers between the given two numbers or fields (Inclusive)\r\n * Smart detection: number or field name\r\n *\r\n * @category Validation Rule\r\n */\r\n public between(min: number | string, max: number | string, errorMessage?: string) {\r\n return this.addRule(betweenNumbersRule, errorMessage, {\r\n min,\r\n max,\r\n scope: \"global\",\r\n });\r\n }\r\n\r\n /**\r\n * Value must be between sibling field values\r\n * @category Validation Rule\r\n */\r\n public betweenSibling(minField: string, maxField: string, errorMessage?: string) {\r\n return this.addRule(betweenNumbersRule, errorMessage, {\r\n min: minField,\r\n max: maxField,\r\n scope: \"sibling\",\r\n });\r\n }\r\n\r\n // Enum and value membership methods are inherited from PrimitiveValidator.\r\n\r\n /**\r\n * Value (as a string) must be exactly this many characters.\r\n * Useful for fixed-format numeric codes (e.g. 4-digit PIN).\r\n */\r\n public length(length: number, errorMessage?: string) {\r\n return this.addRule(lengthRule, errorMessage, { length });\r\n }\r\n\r\n /** Value (as string representation) length must be ≥ min */\r\n public minLength(length: number, errorMessage?: string) {\r\n return this.addRule(minLengthRule, errorMessage, { minLength: length });\r\n }\r\n\r\n /** Value (as string representation) length must be ≤ max */\r\n public maxLength(length: number, errorMessage?: string) {\r\n return this.addRule(maxLengthRule, errorMessage, { maxLength: length });\r\n }\r\n\r\n // Mutators\r\n\r\n /**\r\n * Convert value to its absolute value\r\n */\r\n public abs() {\r\n return this.addMutator(absMutator);\r\n }\r\n\r\n /**\r\n * Round value up to the nearest integer\r\n */\r\n public ceil() {\r\n return this.addMutator(ceilMutator);\r\n }\r\n\r\n /**\r\n * Round value down to the nearest integer\r\n */\r\n public floor() {\r\n return this.addMutator(floorMutator);\r\n }\r\n\r\n /**\r\n * Round value to the nearest integer or specified decimals\r\n */\r\n public round(decimals = 0) {\r\n return this.addMutator(roundMutator, { decimals });\r\n }\r\n\r\n /**\r\n * Format number using fixed-point notation\r\n */\r\n public toFixed(decimals = 2) {\r\n return this.addMutator(toFixedMutator, { decimals });\r\n }\r\n\r\n /**\r\n * @inheritdoc\r\n *\r\n * Returns `{ type: \"number\" }` with numeric constraint keywords.\r\n * IntValidator overrides `type` to `\"integer\"`.\r\n *\r\n * @note Sibling-scoped rules (minSibling, maxSibling, etc.) are not representable\r\n * in JSON Schema and are silently omitted.\r\n *\r\n * @example\r\n * ```ts\r\n * v.number().min(0).max(100).toJsonSchema(\"draft-2020-12\")\r\n * // → { type: \"number\", minimum: 0, maximum: 100 }\r\n * ```\r\n */\r\n public override toJsonSchema(target: JsonSchemaTarget = \"draft-2020-12\"): JsonSchemaResult {\r\n return this.buildNumberJsonSchema(\"number\", target);\r\n }\r\n\r\n /**\r\n * Shared logic for number/integer JSON Schema generation.\r\n * Called by NumberValidator.toJsonSchema() (→ type: \"number\")\r\n * and IntValidator.toJsonSchema() (→ type: \"integer\").\r\n */\r\n protected buildNumberJsonSchema(\r\n type: \"number\" | \"integer\",\r\n target: JsonSchemaTarget,\r\n ): JsonSchemaResult {\r\n const schema: JsonSchemaResult = { type };\r\n\r\n // minimum (inclusive)\r\n const minOpts = getRuleOptions(this.rules, \"min\");\r\n if (minOpts?.min !== undefined && typeof minOpts.min === \"number\") {\r\n schema.minimum = minOpts.min;\r\n }\r\n\r\n // maximum (inclusive)\r\n const maxOpts = getRuleOptions(this.rules, \"max\");\r\n if (maxOpts?.max !== undefined && typeof maxOpts.max === \"number\") {\r\n schema.maximum = maxOpts.max;\r\n }\r\n\r\n // between (inclusive range)\r\n const betweenOpts = getRuleOptions(this.rules, \"betweenNumbers\");\r\n if (betweenOpts) {\r\n if (typeof betweenOpts.min === \"number\") schema.minimum = betweenOpts.min;\r\n if (typeof betweenOpts.max === \"number\") schema.maximum = betweenOpts.max;\r\n }\r\n\r\n // greaterThan (>) → exclusiveMinimum\r\n const gtOpts = getRuleOptions(this.rules, \"greaterThan\");\r\n if (gtOpts?.value !== undefined && typeof gtOpts.value === \"number\") {\r\n if (target === \"draft-07\") {\r\n schema.minimum = gtOpts.value;\r\n schema.exclusiveMinimum = true;\r\n } else {\r\n schema.exclusiveMinimum = gtOpts.value;\r\n }\r\n }\r\n\r\n // lessThan (<) → exclusiveMaximum\r\n const ltOpts = getRuleOptions(this.rules, \"lessThan\");\r\n if (ltOpts?.value !== undefined && typeof ltOpts.value === \"number\") {\r\n if (target === \"draft-07\") {\r\n schema.maximum = ltOpts.value;\r\n schema.exclusiveMaximum = true;\r\n } else {\r\n schema.exclusiveMaximum = ltOpts.value;\r\n }\r\n }\r\n\r\n // multipleOf / modulo\r\n const moduloOpts = getRuleOptions(this.rules, \"modulo\");\r\n if (moduloOpts?.value !== undefined && typeof moduloOpts.value === \"number\") {\r\n schema.multipleOf = moduloOpts.value;\r\n }\r\n\r\n // enum (from PrimitiveValidator.in / .enum)\r\n const inOpts = getRuleOptions(this.rules, \"in\");\r\n if (inOpts?.values && Array.isArray(inOpts.values)) {\r\n schema.enum = inOpts.values;\r\n }\r\n\r\n if (this.isNullable) applyNullable(schema, target);\r\n\r\n return schema;\r\n }\r\n}\r\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"number-validator.mjs","names":[],"sources":["../../../../../../../seal/src/validators/number-validator.ts"],"sourcesContent":["import { absMutator, ceilMutator, floorMutator, roundMutator, toFixedMutator } from \"../mutators\";\r\nimport {\r\n betweenNumbersRule,\r\n evenRule,\r\n greaterThanRule,\r\n lengthRule,\r\n lessThanRule,\r\n maxLengthRule,\r\n maxRule,\r\n minLengthRule,\r\n minRule,\r\n moduloRule,\r\n negativeRule,\r\n numberRule,\r\n oddRule,\r\n positiveRule,\r\n} from \"../rules\";\r\nimport { PrimitiveValidator } from \"./primitive-validator\";\r\nimport { applyNullable, getRuleOptions } from \"../standard-schema/json-schema\";\r\nimport type { JsonSchemaResult, JsonSchemaTarget } from \"../standard-schema/json-schema\";\r\n\r\n/**\r\n * Number validator class - base for Int and Float validators\r\n */\r\nexport class NumberValidator extends PrimitiveValidator {\r\n public constructor(errorMessage?: string) {\r\n super();\r\n this.addMutableRule(numberRule, errorMessage);\r\n }\r\n\r\n /**\r\n * Check if value is a number type\r\n */\r\n public matchesType(value: any): boolean {\r\n return typeof value === \"number\" && !isNaN(value);\r\n }\r\n\r\n /**\r\n * Value must be equal or higher than the given number or field\r\n * Smart detection: number or field name\r\n *\r\n * @category Validation Rule\r\n */\r\n public min(min: number | string, errorMessage?: string) {\r\n return this.addRule(minRule, errorMessage, { min, scope: \"global\" });\r\n }\r\n\r\n /**\r\n * Value must be equal or less than the given number or field\r\n * Smart detection: number or field name\r\n *\r\n * @category Validation Rule\r\n */\r\n public max(max: number | string, errorMessage?: string) {\r\n return this.addRule(maxRule, errorMessage, { max, scope: \"global\" });\r\n }\r\n\r\n /**\r\n * Value must be >= sibling field value\r\n * @category Validation Rule\r\n */\r\n public minSibling(field: string, errorMessage?: string) {\r\n return this.addRule(minRule, errorMessage, { min: field, scope: \"sibling\" });\r\n }\r\n\r\n /**\r\n * Value must be <= sibling field value\r\n * @category Validation Rule\r\n */\r\n public maxSibling(field: string, errorMessage?: string) {\r\n return this.addRule(maxRule, errorMessage, { max: field, scope: \"sibling\" });\r\n }\r\n\r\n /**\r\n * Value must be strictly greater than the given number or field (>)\r\n * Smart detection: number or field name\r\n *\r\n * @category Validation Rule\r\n */\r\n public greaterThan(value: number | string, errorMessage?: string) {\r\n return this.addRule(greaterThanRule, errorMessage, {\r\n value,\r\n scope: \"global\",\r\n });\r\n }\r\n\r\n /**\r\n * Value must be strictly less than the given number or field (<)\r\n * Smart detection: number or field name\r\n *\r\n * @category Validation Rule\r\n */\r\n public lessThan(value: number | string, errorMessage?: string) {\r\n return this.addRule(lessThanRule, errorMessage, {\r\n value,\r\n scope: \"global\",\r\n });\r\n }\r\n\r\n /**\r\n * Alias for greaterThan() - shorter syntax\r\n * @category Validation Rule\r\n */\r\n public gt(value: number | string, errorMessage?: string) {\r\n return this.greaterThan(value, errorMessage);\r\n }\r\n\r\n /**\r\n * Alias for lessThan() - shorter syntax\r\n * @category Validation Rule\r\n */\r\n public lt(value: number | string, errorMessage?: string) {\r\n return this.lessThan(value, errorMessage);\r\n }\r\n\r\n /**\r\n * Value must be > sibling field value\r\n * @category Validation Rule\r\n */\r\n public greaterThanSibling(field: string, errorMessage?: string) {\r\n return this.addRule(greaterThanRule, errorMessage, {\r\n value: field,\r\n scope: \"sibling\",\r\n });\r\n }\r\n\r\n /**\r\n * Alias for greaterThanSibling() - shorter syntax\r\n * @category Validation Rule\r\n */\r\n public gtSibling(field: string, errorMessage?: string) {\r\n return this.greaterThanSibling(field, errorMessage);\r\n }\r\n\r\n /**\r\n * Value must be < sibling field value\r\n * @category Validation Rule\r\n */\r\n public lessThanSibling(field: string, errorMessage?: string) {\r\n return this.addRule(lessThanRule, errorMessage, {\r\n value: field,\r\n scope: \"sibling\",\r\n });\r\n }\r\n\r\n /**\r\n * Alias for lessThanSibling() - shorter syntax\r\n * @category Validation Rule\r\n */\r\n public ltSibling(field: string, errorMessage?: string) {\r\n return this.lessThanSibling(field, errorMessage);\r\n }\r\n\r\n /** Value must be a modulo of the given number */\r\n public modulo(value: number, errorMessage?: string) {\r\n return this.addRule(moduloRule, errorMessage, { value });\r\n }\r\n\r\n /**\r\n * Alias for modulo() - Value must be divisible by the given number\r\n */\r\n public divisibleBy(value: number, errorMessage?: string) {\r\n return this.modulo(value, errorMessage);\r\n }\r\n\r\n /**\r\n * Alias for modulo() - Value must be a multiple of the given number\r\n */\r\n public multipleOf(value: number, errorMessage?: string) {\r\n return this.modulo(value, errorMessage);\r\n }\r\n\r\n /**\r\n * Alias for modulo() - Value must be a multiple of the given number\r\n */\r\n public modulusOf(value: number, errorMessage?: string) {\r\n return this.modulo(value, errorMessage);\r\n }\r\n\r\n /** Accept only numbers higher than 0 */\r\n public positive(errorMessage?: string) {\r\n return this.addRule(positiveRule, errorMessage);\r\n }\r\n\r\n /** Accept only negative numbers */\r\n public negative(errorMessage?: string) {\r\n return this.addRule(negativeRule, errorMessage);\r\n }\r\n\r\n /** Accept only odd numbers */\r\n public odd(errorMessage?: string) {\r\n return this.addRule(oddRule, errorMessage);\r\n }\r\n\r\n /** Accept only even numbers */\r\n public even(errorMessage?: string) {\r\n return this.addRule(evenRule, errorMessage);\r\n }\r\n\r\n /**\r\n * Accept only numbers between the given two numbers or fields (Inclusive)\r\n * Smart detection: number or field name\r\n *\r\n * @category Validation Rule\r\n */\r\n public between(min: number | string, max: number | string, errorMessage?: string) {\r\n return this.addRule(betweenNumbersRule, errorMessage, {\r\n min,\r\n max,\r\n scope: \"global\",\r\n });\r\n }\r\n\r\n /**\r\n * Value must be between sibling field values\r\n * @category Validation Rule\r\n */\r\n public betweenSibling(minField: string, maxField: string, errorMessage?: string) {\r\n return this.addRule(betweenNumbersRule, errorMessage, {\r\n min: minField,\r\n max: maxField,\r\n scope: \"sibling\",\r\n });\r\n }\r\n\r\n // Enum and value membership methods are inherited from PrimitiveValidator.\r\n\r\n /**\r\n * Value (as a string) must be exactly this many characters.\r\n * Useful for fixed-format numeric codes (e.g. 4-digit PIN).\r\n */\r\n public length(length: number, errorMessage?: string) {\r\n return this.addRule(lengthRule, errorMessage, { length });\r\n }\r\n\r\n /** Value (as string representation) length must be ≥ min */\r\n public minLength(length: number, errorMessage?: string) {\r\n return this.addRule(minLengthRule, errorMessage, { minLength: length });\r\n }\r\n\r\n /** Value (as string representation) length must be ≤ max */\r\n public maxLength(length: number, errorMessage?: string) {\r\n return this.addRule(maxLengthRule, errorMessage, { maxLength: length });\r\n }\r\n\r\n // Mutators\r\n\r\n /**\r\n * Convert value to its absolute value\r\n */\r\n public abs() {\r\n return this.addMutator(absMutator);\r\n }\r\n\r\n /**\r\n * Round value up to the nearest integer\r\n */\r\n public ceil() {\r\n return this.addMutator(ceilMutator);\r\n }\r\n\r\n /**\r\n * Round value down to the nearest integer\r\n */\r\n public floor() {\r\n return this.addMutator(floorMutator);\r\n }\r\n\r\n /**\r\n * Round value to the nearest integer or specified decimals\r\n */\r\n public round(decimals = 0) {\r\n return this.addMutator(roundMutator, { decimals });\r\n }\r\n\r\n /**\r\n * Round to a fixed number of decimal places, keeping the value a **number**.\r\n *\r\n * `v.number().toFixed(2)` on `3.14159` yields `3.14`, not `\"3.14\"` — a number\r\n * schema has to output a number, or its own type rule rejects the result.\r\n * Format to a fixed-point *string* at the presentation edge instead.\r\n *\r\n * @example\r\n * v.number().toFixed(2) // 3.14159 → 3.14\r\n */\r\n public toFixed(decimals = 2) {\r\n return this.addMutator(toFixedMutator, { decimals });\r\n }\r\n\r\n /**\r\n * @inheritdoc\r\n *\r\n * Returns `{ type: \"number\" }` with numeric constraint keywords.\r\n * IntValidator overrides `type` to `\"integer\"`.\r\n *\r\n * @note Sibling-scoped rules (minSibling, maxSibling, etc.) are not representable\r\n * in JSON Schema and are silently omitted.\r\n *\r\n * @example\r\n * ```ts\r\n * v.number().min(0).max(100).toJsonSchema(\"draft-2020-12\")\r\n * // → { type: \"number\", minimum: 0, maximum: 100 }\r\n * ```\r\n */\r\n public override toJsonSchema(target: JsonSchemaTarget = \"draft-2020-12\"): JsonSchemaResult {\r\n return this.buildNumberJsonSchema(\"number\", target);\r\n }\r\n\r\n /**\r\n * Shared logic for number/integer JSON Schema generation.\r\n * Called by NumberValidator.toJsonSchema() (→ type: \"number\")\r\n * and IntValidator.toJsonSchema() (→ type: \"integer\").\r\n */\r\n protected buildNumberJsonSchema(\r\n type: \"number\" | \"integer\",\r\n target: JsonSchemaTarget,\r\n ): JsonSchemaResult {\r\n const schema: JsonSchemaResult = { type };\r\n\r\n // minimum (inclusive)\r\n const minOpts = getRuleOptions(this.rules, \"min\");\r\n if (minOpts?.min !== undefined && typeof minOpts.min === \"number\") {\r\n schema.minimum = minOpts.min;\r\n }\r\n\r\n // maximum (inclusive)\r\n const maxOpts = getRuleOptions(this.rules, \"max\");\r\n if (maxOpts?.max !== undefined && typeof maxOpts.max === \"number\") {\r\n schema.maximum = maxOpts.max;\r\n }\r\n\r\n // between (inclusive range)\r\n const betweenOpts = getRuleOptions(this.rules, \"betweenNumbers\");\r\n if (betweenOpts) {\r\n if (typeof betweenOpts.min === \"number\") schema.minimum = betweenOpts.min;\r\n if (typeof betweenOpts.max === \"number\") schema.maximum = betweenOpts.max;\r\n }\r\n\r\n // greaterThan (>) → exclusiveMinimum\r\n const gtOpts = getRuleOptions(this.rules, \"greaterThan\");\r\n if (gtOpts?.value !== undefined && typeof gtOpts.value === \"number\") {\r\n if (target === \"draft-07\") {\r\n schema.minimum = gtOpts.value;\r\n schema.exclusiveMinimum = true;\r\n } else {\r\n schema.exclusiveMinimum = gtOpts.value;\r\n }\r\n }\r\n\r\n // lessThan (<) → exclusiveMaximum\r\n const ltOpts = getRuleOptions(this.rules, \"lessThan\");\r\n if (ltOpts?.value !== undefined && typeof ltOpts.value === \"number\") {\r\n if (target === \"draft-07\") {\r\n schema.maximum = ltOpts.value;\r\n schema.exclusiveMaximum = true;\r\n } else {\r\n schema.exclusiveMaximum = ltOpts.value;\r\n }\r\n }\r\n\r\n // multipleOf / modulo\r\n const moduloOpts = getRuleOptions(this.rules, \"modulo\");\r\n if (moduloOpts?.value !== undefined && typeof moduloOpts.value === \"number\") {\r\n schema.multipleOf = moduloOpts.value;\r\n }\r\n\r\n // enum (from PrimitiveValidator.in / .enum)\r\n const inOpts = getRuleOptions(this.rules, \"in\");\r\n if (inOpts?.values && Array.isArray(inOpts.values)) {\r\n schema.enum = inOpts.values;\r\n }\r\n\r\n if (this.isNullable) applyNullable(schema, target);\r\n\r\n return schema;\r\n }\r\n}\r\n"],"mappings":"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"}
|
package/llms-full.txt
CHANGED
|
@@ -743,6 +743,26 @@ By default, extra keys in input are silently dropped from `data` (no error, no f
|
|
|
743
743
|
|
|
744
744
|
`.allowUnknown()` only affects direct children — nested objects keep their own policies. For a fully permissive object including nested children, set `.allowUnknown()` on each level.
|
|
745
745
|
|
|
746
|
+
### Output DTOs — stripping extras instead of rejecting them
|
|
747
|
+
|
|
748
|
+
The default is to **reject** unknown keys, which is what you want for inbound payloads. For an *outbound* DTO — where the record legitimately carries internal fields you simply don't want to expose — use `.stripUnknown()`, which drops them and returns the clean object:
|
|
749
|
+
|
|
750
|
+
```ts
|
|
751
|
+
const publicArticle = v.object({
|
|
752
|
+
id: v.string().required(),
|
|
753
|
+
title: v.string().required(),
|
|
754
|
+
}).stripUnknown();
|
|
755
|
+
|
|
756
|
+
const { isValid, data } = await v.validate(publicArticle, record);
|
|
757
|
+
// record had version / authorId / status; data has only id + title
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
Without `.stripUnknown()` the same record fails with *"The schema contains unknown keys: …"* rather than being cleaned.
|
|
761
|
+
|
|
762
|
+
:::note[`data` is `undefined` on failure — since 4.9.2]
|
|
763
|
+
A failed validation returns `data: undefined`, never the input it rejected. Before 4.9.2 an `object` returned the raw input on failure (while `discriminatedUnion` returned `undefined`), so a caller who read `data` without branching on `isValid` forwarded the exact fields the schema existed to exclude. Always branch on `isValid`.
|
|
764
|
+
:::
|
|
765
|
+
|
|
746
766
|
## Object-level mutators
|
|
747
767
|
|
|
748
768
|
| Method | Args | Effect |
|
|
@@ -1441,6 +1461,22 @@ v.instanceof(MyClass) // type: MyClass
|
|
|
1441
1461
|
- `v.string().oneOf(["a", "b"])` infers as `string` (loses literal types). Use when broad type is fine.
|
|
1442
1462
|
- `v.enum(["a", "b"])` runs the same `oneOf` rule at runtime (it builds a `StringValidator().oneOf(...)`), but the `v.enum` factory overload **preserves the literal union** — it infers `"a" | "b"`, not `string`. Pass a TS enum object (`v.enum(Direction)`) and it uses `Object.values`, inferring `Direction[keyof Direction]`.
|
|
1443
1463
|
|
|
1464
|
+
`**`v.literal("")` — the empty string is a value, not an absence.** `""` is accepted when it is one of the configured literals, and the field is still required to be *present*:
|
|
1465
|
+
|
|
1466
|
+
```ts
|
|
1467
|
+
const decorative = v.object({ decorative: v.literal(true), alt: v.literal("") });
|
|
1468
|
+
|
|
1469
|
+
await v.validate(decorative, { decorative: true, alt: "" }); // valid
|
|
1470
|
+
await v.validate(decorative, { decorative: true, alt: "text" }); // invalid — not the literal
|
|
1471
|
+
await v.validate(decorative, { decorative: true }); // invalid — key missing
|
|
1472
|
+
```
|
|
1473
|
+
|
|
1474
|
+
That distinction matters wherever "empty" and "absent" mean different things — `alt=""` marks an image as decorative to a screen reader, while a missing `alt` is an authoring mistake. Add `.optional()` only when a missing key really is acceptable; a present value must still match the literal.
|
|
1475
|
+
|
|
1476
|
+
:::note[Fixed in 4.9.2]
|
|
1477
|
+
Before 4.9.2 `v.literal("")` always failed with *"is required"*, and `.optional()` looked like a workaround while actually disabling the literal check — accepting `""`, `null`, and a missing key alike. `v.literal(0)` and `v.literal(false)` were never affected.
|
|
1478
|
+
:::
|
|
1479
|
+
|
|
1444
1480
|
`v.instanceof(Ctor)` for File/Buffer/Uint8Array/custom classes. Returns `{}` from `toJsonSchema()` (not representable). For OpenAPI `File`, attach `{ type: "string", format: "binary" }` manually after generation.
|
|
1445
1481
|
|
|
1446
1482
|
## `v.any` — escape hatch
|
|
@@ -1846,10 +1882,16 @@ For most numeric-shaped IDs you'd use `v.string().length(n).numeric()` instead
|
|
|
1846
1882
|
| `.ceil()` | — | round up to integer |
|
|
1847
1883
|
| `.floor()` | — | round down to integer |
|
|
1848
1884
|
| `.round(decimals?)` | default 0 | round to N decimals |
|
|
1849
|
-
| `.toFixed(decimals?)` | default 2 |
|
|
1885
|
+
| `.toFixed(decimals?)` | default 2 | round to N decimals — returns a **number** |
|
|
1850
1886
|
|
|
1851
1887
|
These run *before* validation rules. If you mutate `1.6` with `.ceil()`, `v.int()` sees `2` and passes. Use mutators when the input arrives in a slightly wrong form and you want to coerce, not reject.
|
|
1852
1888
|
|
|
1889
|
+
That ordering is exactly why `.toFixed()` yields a number rather than a fixed-point string: the mutated value is what the validator's own type rule sees, and a `number` schema that emitted `"3.14"` would reject its own output. Format to a string at the presentation edge instead.
|
|
1890
|
+
|
|
1891
|
+
:::note[Fixed in 4.9.2]
|
|
1892
|
+
Before 4.9.2 `.toFixed()` returned a string, so `v.number().toFixed(2)` **always failed validation** — masked because a failed validation still handed back its mutated `data`. No working code can have relied on it.
|
|
1893
|
+
:::
|
|
1894
|
+
|
|
1853
1895
|
## JSON Schema notes
|
|
1854
1896
|
|
|
1855
1897
|
- `v.number()` → `{ type: "number" }`
|
package/package.json
CHANGED
|
@@ -94,6 +94,26 @@ By default, extra keys in input are silently dropped from `data` (no error, no f
|
|
|
94
94
|
|
|
95
95
|
`.allowUnknown()` only affects direct children — nested objects keep their own policies. For a fully permissive object including nested children, set `.allowUnknown()` on each level.
|
|
96
96
|
|
|
97
|
+
### Output DTOs — stripping extras instead of rejecting them
|
|
98
|
+
|
|
99
|
+
The default is to **reject** unknown keys, which is what you want for inbound payloads. For an *outbound* DTO — where the record legitimately carries internal fields you simply don't want to expose — use `.stripUnknown()`, which drops them and returns the clean object:
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
const publicArticle = v.object({
|
|
103
|
+
id: v.string().required(),
|
|
104
|
+
title: v.string().required(),
|
|
105
|
+
}).stripUnknown();
|
|
106
|
+
|
|
107
|
+
const { isValid, data } = await v.validate(publicArticle, record);
|
|
108
|
+
// record had version / authorId / status; data has only id + title
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Without `.stripUnknown()` the same record fails with *"The schema contains unknown keys: …"* rather than being cleaned.
|
|
112
|
+
|
|
113
|
+
:::note[`data` is `undefined` on failure — since 4.9.2]
|
|
114
|
+
A failed validation returns `data: undefined`, never the input it rejected. Before 4.9.2 an `object` returned the raw input on failure (while `discriminatedUnion` returned `undefined`), so a caller who read `data` without branching on `isValid` forwarded the exact fields the schema existed to exclude. Always branch on `isValid`.
|
|
115
|
+
:::
|
|
116
|
+
|
|
97
117
|
## Object-level mutators
|
|
98
118
|
|
|
99
119
|
| Method | Args | Effect |
|
|
@@ -65,6 +65,22 @@ v.instanceof(MyClass) // type: MyClass
|
|
|
65
65
|
- `v.string().oneOf(["a", "b"])` infers as `string` (loses literal types). Use when broad type is fine.
|
|
66
66
|
- `v.enum(["a", "b"])` runs the same `oneOf` rule at runtime (it builds a `StringValidator().oneOf(...)`), but the `v.enum` factory overload **preserves the literal union** — it infers `"a" | "b"`, not `string`. Pass a TS enum object (`v.enum(Direction)`) and it uses `Object.values`, inferring `Direction[keyof Direction]`.
|
|
67
67
|
|
|
68
|
+
`**`v.literal("")` — the empty string is a value, not an absence.** `""` is accepted when it is one of the configured literals, and the field is still required to be *present*:
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
const decorative = v.object({ decorative: v.literal(true), alt: v.literal("") });
|
|
72
|
+
|
|
73
|
+
await v.validate(decorative, { decorative: true, alt: "" }); // valid
|
|
74
|
+
await v.validate(decorative, { decorative: true, alt: "text" }); // invalid — not the literal
|
|
75
|
+
await v.validate(decorative, { decorative: true }); // invalid — key missing
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
That distinction matters wherever "empty" and "absent" mean different things — `alt=""` marks an image as decorative to a screen reader, while a missing `alt` is an authoring mistake. Add `.optional()` only when a missing key really is acceptable; a present value must still match the literal.
|
|
79
|
+
|
|
80
|
+
:::note[Fixed in 4.9.2]
|
|
81
|
+
Before 4.9.2 `v.literal("")` always failed with *"is required"*, and `.optional()` looked like a workaround while actually disabling the literal check — accepting `""`, `null`, and a missing key alike. `v.literal(0)` and `v.literal(false)` were never affected.
|
|
82
|
+
:::
|
|
83
|
+
|
|
68
84
|
`v.instanceof(Ctor)` for File/Buffer/Uint8Array/custom classes. Returns `{}` from `toJsonSchema()` (not representable). For OpenAPI `File`, attach `{ type: "string", format: "binary" }` manually after generation.
|
|
69
85
|
|
|
70
86
|
## `v.any` — escape hatch
|
|
@@ -79,10 +79,16 @@ For most numeric-shaped IDs you'd use `v.string().length(n).numeric()` instead
|
|
|
79
79
|
| `.ceil()` | — | round up to integer |
|
|
80
80
|
| `.floor()` | — | round down to integer |
|
|
81
81
|
| `.round(decimals?)` | default 0 | round to N decimals |
|
|
82
|
-
| `.toFixed(decimals?)` | default 2 |
|
|
82
|
+
| `.toFixed(decimals?)` | default 2 | round to N decimals — returns a **number** |
|
|
83
83
|
|
|
84
84
|
These run *before* validation rules. If you mutate `1.6` with `.ceil()`, `v.int()` sees `2` and passes. Use mutators when the input arrives in a slightly wrong form and you want to coerce, not reject.
|
|
85
85
|
|
|
86
|
+
That ordering is exactly why `.toFixed()` yields a number rather than a fixed-point string: the mutated value is what the validator's own type rule sees, and a `number` schema that emitted `"3.14"` would reject its own output. Format to a string at the presentation edge instead.
|
|
87
|
+
|
|
88
|
+
:::note[Fixed in 4.9.2]
|
|
89
|
+
Before 4.9.2 `.toFixed()` returned a string, so `v.number().toFixed(2)` **always failed validation** — masked because a failed validation still handed back its mutated `data`. No working code can have relied on it.
|
|
90
|
+
:::
|
|
91
|
+
|
|
86
92
|
## JSON Schema notes
|
|
87
93
|
|
|
88
94
|
- `v.number()` → `{ type: "number" }`
|