@warlock.js/seal 4.15.0 → 5.0.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 +41 -24
- package/cjs/index.cjs +68 -9
- package/cjs/index.cjs.map +1 -1
- package/esm/index.d.mts +3 -3
- package/esm/index.mjs +3 -3
- package/esm/mutators/index.mjs +1 -1
- package/esm/mutators/string-mutators.d.mts +16 -2
- package/esm/mutators/string-mutators.d.mts.map +1 -1
- package/esm/mutators/string-mutators.mjs +17 -3
- package/esm/mutators/string-mutators.mjs.map +1 -1
- package/esm/rules/index.mjs +1 -1
- package/esm/rules/string/index.mjs +1 -1
- package/esm/rules/string/ip.d.mts +8 -1
- package/esm/rules/string/ip.d.mts.map +1 -1
- package/esm/rules/string/ip.mjs +26 -2
- package/esm/rules/string/ip.mjs.map +1 -1
- package/esm/validators/base-validator.d.mts.map +1 -1
- package/esm/validators/base-validator.mjs +2 -1
- package/esm/validators/base-validator.mjs.map +1 -1
- package/esm/validators/string-validator.d.mts +15 -1
- package/esm/validators/string-validator.d.mts.map +1 -1
- package/esm/validators/string-validator.mjs +19 -3
- package/esm/validators/string-validator.mjs.map +1 -1
- package/llms-full.txt +3 -3
- package/package.json +6 -3
- package/skills/pick-seal-primitive/string-methods.md +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,30 +1,47 @@
|
|
|
1
|
-
# Changelog — @warlock.js/seal
|
|
2
|
-
|
|
3
|
-
All notable changes to `@warlock.js/seal` are documented in this file.
|
|
4
|
-
|
|
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
|
-
|
|
1
|
+
# Changelog — @warlock.js/seal
|
|
2
|
+
|
|
3
|
+
All notable changes to `@warlock.js/seal` are documented in this file.
|
|
4
|
+
|
|
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
|
+
|
|
7
|
+
## 5.0.0 - 2026-08-25
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- IP validation no longer imports Node's `net` module, so the same IPv4 and IPv6 rules can run in browser bundles.
|
|
12
|
+
- Optional validators now skip value rules only for absent values; present empty values such as `""` are validated instead of passing through with the wrong output type.
|
|
13
|
+
|
|
14
|
+
## 4.16.0 - 2026-08-18
|
|
15
|
+
|
|
16
|
+
### Security
|
|
17
|
+
|
|
18
|
+
- **Renamed `safeHtmlMutator`/`.safeHtml()` to `stripTagsMutator`/`.stripTags()`.** The old name implied XSS safety it never provided — the implementation is a naive `<[^>]*>` regex, not an HTML parser, and can be defeated by malformed/nested markup or content re-introduced later in a pipeline. A developer building `v.string().safeHtml()` for user-supplied rich text was liable to treat the output as pre-sanitized and skip further output encoding, opening a stored/reflected XSS path. The new names and doc comments make clear this is tag-stripping only; use a real parser-based sanitizer (DOMPurify / sanitize-html) for untrusted rich text. `safeHtmlMutator` and `.safeHtml()` remain as deprecated aliases (same behavior, `@deprecated` JSDoc pointing at the new names) so existing callers do not break.
|
|
19
|
+
|
|
20
|
+
### Dependencies
|
|
21
|
+
|
|
22
|
+
- Bumped `@mongez/supportive-is` to `^2.1.4` (no breaking changes) and `@mongez/reinforcements` to `^4.0.1`. The reinforcements major makes `Random.string/nanoid/id/token/uuid` CSPRNG-backed (WebCrypto) and removes `Random.seed()` support — audited this package's source and tests for `Random.seed(` and for seeded/reproducible use of `Random.*`; none found, so no code changes were needed.
|
|
23
|
+
|
|
7
24
|
## 4.12.0
|
|
8
25
|
|
|
9
26
|
### Changed
|
|
10
27
|
|
|
11
28
|
- Declares its own test runner and pins it to an exact version (`vitest@4.1.10`). The package is its own repository, so a runner resolved from a workspace root it may not be cloned with is a runner it cannot rely on. The pin is exact rather than a range because the version moved underneath the suite mid-development on an unrelated install — a suite whose runner can change without anyone choosing it proves less than it appears to
|
|
12
29
|
|
|
13
|
-
## 4.9.2
|
|
14
|
-
|
|
15
|
-
### Fixed
|
|
16
|
-
|
|
17
|
-
- `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
|
|
18
|
-
- `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
|
|
19
|
-
- 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`
|
|
20
|
-
- `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
|
|
21
|
-
|
|
22
|
-
## 4.2.11
|
|
23
|
-
|
|
24
|
-
### Changed
|
|
25
|
-
|
|
26
|
-
- Bumped `@mongez/reinforcements` to 3.3.0
|
|
27
|
-
|
|
28
|
-
## 4.1.15
|
|
29
|
-
|
|
30
|
-
- Baseline — per-package changelog tracking starts at this version.
|
|
30
|
+
## 4.9.2
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
|
|
34
|
+
- `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
|
|
35
|
+
- `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
|
|
36
|
+
- 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`
|
|
37
|
+
- `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
|
|
38
|
+
|
|
39
|
+
## 4.2.11
|
|
40
|
+
|
|
41
|
+
### Changed
|
|
42
|
+
|
|
43
|
+
- Bumped `@mongez/reinforcements` to 3.3.0
|
|
44
|
+
|
|
45
|
+
## 4.1.15
|
|
46
|
+
|
|
47
|
+
- Baseline — per-package changelog tracking starts at this version.
|
package/cjs/index.cjs
CHANGED
|
@@ -30,7 +30,6 @@ let _mongez_reinforcements = require("@mongez/reinforcements");
|
|
|
30
30
|
let _mongez_supportive_is = require("@mongez/supportive-is");
|
|
31
31
|
let dayjs = require("dayjs");
|
|
32
32
|
dayjs = __toESM(dayjs, 1);
|
|
33
|
-
let net = require("net");
|
|
34
33
|
|
|
35
34
|
//#region ../seal/src/standard-schema/json-schema.ts
|
|
36
35
|
/**
|
|
@@ -935,9 +934,10 @@ var BaseValidator = class {
|
|
|
935
934
|
let isValid = true;
|
|
936
935
|
const isFirstErrorOnly = context.configurations?.firstErrorOnly ?? true;
|
|
937
936
|
const isEmpty = isEmptyValue(valueForRules);
|
|
937
|
+
const skipEmptyRules = this.isOptional ? valueForRules === void 0 : isEmpty;
|
|
938
938
|
const rulesToRun = this.requiredRule ? [this.requiredRule, ...this.rules] : this.rules;
|
|
939
939
|
for (const rule of rulesToRun) {
|
|
940
|
-
if ((rule.requiresValue ?? true) &&
|
|
940
|
+
if ((rule.requiresValue ?? true) && skipEmptyRules) continue;
|
|
941
941
|
this.setRuleAttributesList(rule);
|
|
942
942
|
const result = await rule.validate(mutatedData, context);
|
|
943
943
|
if (result.isValid === false) {
|
|
@@ -1354,10 +1354,24 @@ const stringifyMutator = async (value) => {
|
|
|
1354
1354
|
const trimMutator = async (value, context) => {
|
|
1355
1355
|
return (0, _mongez_reinforcements.trim)(value?.toString(), context?.options?.needle ?? " ");
|
|
1356
1356
|
};
|
|
1357
|
-
/**
|
|
1358
|
-
|
|
1357
|
+
/**
|
|
1358
|
+
* Strip HTML tags with a naive `<...>` regex.
|
|
1359
|
+
*
|
|
1360
|
+
* This is a **tag stripper, not an XSS sanitizer**: it is not a parser, so
|
|
1361
|
+
* malformed/nested markup, attribute payloads, or content re-introduced
|
|
1362
|
+
* later in a pipeline can defeat it. Do not rely on this alone to make
|
|
1363
|
+
* untrusted rich text safe to render — use a real parser-based sanitizer
|
|
1364
|
+
* (e.g. DOMPurify / sanitize-html) for that.
|
|
1365
|
+
*/
|
|
1366
|
+
const stripTagsMutator = async (value) => {
|
|
1359
1367
|
return value?.toString().replace(/<[^>]*>?/gm, "");
|
|
1360
1368
|
};
|
|
1369
|
+
/**
|
|
1370
|
+
* @deprecated Renamed to {@link stripTagsMutator} — the old name implied
|
|
1371
|
+
* XSS safety this tag-stripping regex does not provide. Kept as an alias
|
|
1372
|
+
* for backward compatibility; switch to `stripTagsMutator`.
|
|
1373
|
+
*/
|
|
1374
|
+
const safeHtmlMutator = stripTagsMutator;
|
|
1361
1375
|
/** HTML escape */
|
|
1362
1376
|
const htmlEscapeMutator = async (value) => {
|
|
1363
1377
|
return value?.toString().replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
@@ -1755,13 +1769,38 @@ const nanoidRule = {
|
|
|
1755
1769
|
//#endregion
|
|
1756
1770
|
//#region ../seal/src/rules/string/ip.ts
|
|
1757
1771
|
/**
|
|
1772
|
+
* Pure, dependency-free re-implementation of Node's `net.isIP`/`isIPv4`/`isIPv6`.
|
|
1773
|
+
* Mirrors the exact regex Node uses internally (lib/internal/net.js) so that
|
|
1774
|
+
* behavior stays identical while remaining runnable in the browser (no `net` import).
|
|
1775
|
+
*/
|
|
1776
|
+
const v4Seg = "(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])";
|
|
1777
|
+
const v4Str = `(${v4Seg}[.]){3}${v4Seg}`;
|
|
1778
|
+
const IPv4Reg = new RegExp(`^${v4Str}$`);
|
|
1779
|
+
const v6Seg = "(?:[0-9a-fA-F]{1,4})";
|
|
1780
|
+
const IPv6Reg = new RegExp(`^((?:${v6Seg}:){7}(?:${v6Seg}|:)|(?:${v6Seg}:){6}(?:${v4Str}|:${v6Seg}|:)|(?:${v6Seg}:){5}(?::${v4Str}|(:${v6Seg}){1,2}|:)|(?:${v6Seg}:){4}(?:(:${v6Seg}){0,1}:${v4Str}|(:${v6Seg}){1,3}|:)|(?:${v6Seg}:){3}(?:(:${v6Seg}){0,2}:${v4Str}|(:${v6Seg}){1,4}|:)|(?:${v6Seg}:){2}(?:(:${v6Seg}){0,3}:${v4Str}|(:${v6Seg}){1,5}|:)|(?:${v6Seg}:){1}(?:(:${v6Seg}){0,4}:${v4Str}|(:${v6Seg}){1,6}|:)|(?::((?::${v6Seg}){0,5}:${v4Str}|(?::${v6Seg}){1,7}|:)))(%[0-9a-zA-Z-.:]{1,})?\$`);
|
|
1781
|
+
function isIPv4(value) {
|
|
1782
|
+
return IPv4Reg.test(value);
|
|
1783
|
+
}
|
|
1784
|
+
function isIPv6(value) {
|
|
1785
|
+
return IPv6Reg.test(value);
|
|
1786
|
+
}
|
|
1787
|
+
/**
|
|
1788
|
+
* Returns 4 or 6 when `value` is a valid IPv4/IPv6 address, otherwise 0.
|
|
1789
|
+
* Signature mirrors Node's `net.isIP` for drop-in behavior parity.
|
|
1790
|
+
*/
|
|
1791
|
+
function isIP(value) {
|
|
1792
|
+
if (isIPv4(value)) return 4;
|
|
1793
|
+
if (isIPv6(value)) return 6;
|
|
1794
|
+
return 0;
|
|
1795
|
+
}
|
|
1796
|
+
/**
|
|
1758
1797
|
* IP rule - validates IP address (v4 or v6)
|
|
1759
1798
|
*/
|
|
1760
1799
|
const ipRule = {
|
|
1761
1800
|
name: "ip",
|
|
1762
1801
|
defaultErrorMessage: "The :input must be a valid IP address",
|
|
1763
1802
|
async validate(value, context) {
|
|
1764
|
-
if (
|
|
1803
|
+
if (isIP(value)) return VALID_RULE;
|
|
1765
1804
|
return invalidRule(this, context);
|
|
1766
1805
|
}
|
|
1767
1806
|
};
|
|
@@ -1772,7 +1811,7 @@ const ip4Rule = {
|
|
|
1772
1811
|
name: "ip4",
|
|
1773
1812
|
defaultErrorMessage: "The :input must be a valid IPv4 address",
|
|
1774
1813
|
async validate(value, context) {
|
|
1775
|
-
if (
|
|
1814
|
+
if (isIP(value) === 4) return VALID_RULE;
|
|
1776
1815
|
return invalidRule(this, context);
|
|
1777
1816
|
}
|
|
1778
1817
|
};
|
|
@@ -1783,7 +1822,7 @@ const ip6Rule = {
|
|
|
1783
1822
|
name: "ip6",
|
|
1784
1823
|
defaultErrorMessage: "The :input must be a valid IPv6 address",
|
|
1785
1824
|
async validate(value, context) {
|
|
1786
|
-
if (
|
|
1825
|
+
if (isIP(value) === 6) return VALID_RULE;
|
|
1787
1826
|
return invalidRule(this, context);
|
|
1788
1827
|
}
|
|
1789
1828
|
};
|
|
@@ -7985,9 +8024,25 @@ var StringValidator = class extends PrimitiveValidator {
|
|
|
7985
8024
|
char
|
|
7986
8025
|
});
|
|
7987
8026
|
}
|
|
7988
|
-
/**
|
|
8027
|
+
/**
|
|
8028
|
+
* Strip HTML tags with a naive `<...>` regex.
|
|
8029
|
+
*
|
|
8030
|
+
* This is a **tag stripper, not an XSS sanitizer**: it is not a parser,
|
|
8031
|
+
* so malformed/nested markup or content re-introduced later in a
|
|
8032
|
+
* pipeline can defeat it. Do not rely on this alone to make untrusted
|
|
8033
|
+
* rich text safe to render — use a real parser-based sanitizer (e.g.
|
|
8034
|
+
* DOMPurify / sanitize-html) for that.
|
|
8035
|
+
*/
|
|
8036
|
+
stripTags() {
|
|
8037
|
+
return this.addMutator(stripTagsMutator);
|
|
8038
|
+
}
|
|
8039
|
+
/**
|
|
8040
|
+
* @deprecated Renamed to {@link StringValidator.stripTags} — the old
|
|
8041
|
+
* name implied XSS safety this tag-stripping regex does not provide.
|
|
8042
|
+
* Kept as an alias for backward compatibility; switch to `.stripTags()`.
|
|
8043
|
+
*/
|
|
7989
8044
|
safeHtml() {
|
|
7990
|
-
return this.
|
|
8045
|
+
return this.stripTags();
|
|
7991
8046
|
}
|
|
7992
8047
|
/** HTML escape special characters */
|
|
7993
8048
|
htmlEscape() {
|
|
@@ -8781,6 +8836,9 @@ exports.ip6Rule = ip6Rule;
|
|
|
8781
8836
|
exports.ipRule = ipRule;
|
|
8782
8837
|
exports.isCreditCardRule = isCreditCardRule;
|
|
8783
8838
|
exports.isDateValue = isDateValue;
|
|
8839
|
+
exports.isIP = isIP;
|
|
8840
|
+
exports.isIPv4 = isIPv4;
|
|
8841
|
+
exports.isIPv6 = isIPv6;
|
|
8784
8842
|
exports.isNumericRule = isNumericRule;
|
|
8785
8843
|
exports.jsonMutator = jsonMutator;
|
|
8786
8844
|
exports.kebabCaseMutator = kebabCaseMutator;
|
|
@@ -8900,6 +8958,7 @@ exports.startsWithRule = startsWithRule;
|
|
|
8900
8958
|
exports.stringMutator = stringMutator;
|
|
8901
8959
|
exports.stringRule = stringRule;
|
|
8902
8960
|
exports.stringifyMutator = stringifyMutator;
|
|
8961
|
+
exports.stripTagsMutator = stripTagsMutator;
|
|
8903
8962
|
exports.stripUnknownMutator = stripUnknownMutator;
|
|
8904
8963
|
exports.strongPasswordRule = strongPasswordRule;
|
|
8905
8964
|
exports.titleCaseMutator = titleCaseMutator;
|