Skip to content

Dev - #51

Merged
erayhanoglu merged 18 commits into
mainfrom
dev
Sep 8, 2026
Merged

Dev#51
erayhanoglu merged 18 commits into
mainfrom
dev

Conversation

@erayhanoglu

Copy link
Copy Markdown
Member

No description provided.

erayhanoglu and others added 18 commits August 11, 2026 12:09
- context.ts: fix OPTIONAL_VAR_PATTERN so {{key|fallback}} message
  templates split on the pipe correctly instead of swallowing the
  whole placeholder into the key
- is-boolean.ts: anchor TRUE_PATTERN/FALSE_PATTERN alternatives so
  substrings like "t" or "no" can't match anywhere in the input
  (e.g. "October" was coercing to true)
- is-date.ts: fix PRECISION_INDEX so the "d" shorthand maps to the
  same precision as "day" instead of colliding with "hours"/"hr"
- is-tuple.ts: check the coerced array (not the raw input) for the
  array-type check, and enforce tuple arity so missing/extra
  elements are rejected instead of silently accepted

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- is-date.ts: add abbreviated precision keys (yr/mo/d/hr/min/sec) to
  setPrecision(), and validate Date/number input with date-fns
  isValid() in coerceDateString() so invalid dates aren't silently
  coerced into bogus "NaN"-based strings
- is-defined.ts: correct the JSDoc, which claimed null was rejected
  even though the code (and its test) only ever checked undefined
- is-number.ts / is-integer.ts: actually reject bigint inputs that
  lose precision when coerced to number, instead of silently
  falling through and returning the rounded value
- is-email.ts: wire up the previously-ignored ignoreMaxLength and
  domainSpecificValidation options instead of hardcoding
  ignore_max_length and leaving domain_specific_validation unset
- is-time.ts: fix TIME_PATTERN so a dangling separator with no
  following digits (e.g. "12:30:") is no longer accepted

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- matches.ts: reset lastIndex before each test() call so a caller
  passing a global/sticky RegExp doesn't get nondeterministic
  pass/fail results across repeated validations of the same input
- exists.ts: check the property descriptor using context.property
  (the actual key being validated) instead of input, which is
  always undefined at that point in the check — previously exists()
  rejected any key whose value is genuinely undefined, even though
  the key itself is present on the object

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- all-of.ts / one-of.ts: pass the combinator's own options (onFail,
  coerce) to the underlying validator() factory call, matching the
  other combinators (nullable, optional, required, pipe); previously
  these were silently dropped. Also drop the dead `context || options`
  fallback since context is always a real Context inside the rule fn.
- utilities.ts: iif() now determines whether the "check" validator
  passed by whether it throws, not by whether it returns something
  other than undefined - some validators (e.g. isUndefined) legitimately
  return undefined on success, which previously made iif pick the
  "else" branch even though the check passed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds missing rejection-path, option, and edge-case tests across
nearly every rule file (coverage: 91.0% -> 99.5% statements,
88.8% -> 98.0% branches, 93.2% -> 98.1% functions), and fixes five
real bugs surfaced while writing/verifying those tests:

- is-tuple.ts: item label was suffixed with the index twice
  ("Point[0][0]" instead of "Point[0]")
- is-object.ts: additionalFields: 'error' crashed with "Cannot read
  properties of undefined" instead of reporting a validation error,
  because context.fail() was called with an undefined rule
- is-date.ts: the timezone suffix for offsets behind UTC (e.g. US
  timezones) was just a bare "-" with no hours/minutes, due to an
  operator-precedence mistake in the ternary building the string
- is-alpha.ts / is-alphanumeric.ts / is-base64.ts: stray unmatched
  leading quote in the failure message
- is-iban.ts: whitelist/blacklist options were never forwarded to
  the underlying validator, so they were silently ignored

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
src/factories.ts (a re-export of rules/index.ts) and
src/helpers/object.utils.ts (omitKeys/pickKeys) were never imported
from anywhere and not part of the public API surface - confirmed via
grep and a clean dpdm dependency check. Removing them brings src/
statement coverage to 100%.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds `npm run bench` (benchmark/harness.ts + cli.ts): a duration-based
(not fixed-sample-count) throughput/memory benchmark runner with a
`-s`/`--rule` selector (comma-separated, case-insensitive, `*`
wildcard, "all"/"none"), colored output (ansi-colors), and one
benchmark file per rule under benchmark/rules/, auto-discovered.

Writing and profiling those benchmarks (node --cpu-prof) surfaced a
real, verified performance bug: nested validator calls throughout the
combinator layer (pipe, allOf, is-array/is-record/is-tuple/is-object's
item loops, one-of's branch loop) passed the parent context as the
*2nd* ("options") argument instead of the 3rd ("context") one. The
validator wrapper only skips creating a child Context when context
arrives in its own slot and the callee has no extra options - passing
it as "options" instead unconditionally forced a Context.extend() on
every nested call, even when nothing needed merging. Fixed all of
those call sites, and additionally:
- Context.extend() itself: replaced Object.entries()+destructuring
  (allocates a discarded array of [key, value] pairs) with
  Object.keys()+indexed access, and Object.setPrototypeOf() after
  creation (one of the more expensive object operations in V8) with
  Object.create() at creation time.
- is-array.ts/is-record.ts: the per-item "onFail" customization is now
  set once on the reused item context instead of passed as a fresh
  options object on every item, so those calls can take the
  extend()-skipping fast path too.

Measured effect (isolated before/after benchmarks): lengthMin ~2.3x,
isObject with a 50-field schema ~6.4x, isObject nested one level
~1.9x, isArray(20 items) ~1.6x. No behavior change - full suite still
passes, including new tests for the label/prototype-inheritance edge
case this uncovered in is-object.ts.

Also fixes several typos found while reviewing rules to benchmark
them: wrong @validator tags in is-bigint.ts/is-instanceof.ts
(copy-pasted from other rules), "oerce"/"grater" typos, and "lover"
instead of "lower" in is-lt.ts/is-lte.ts's actual error messages
(updated the tests asserting on that text accordingly).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…y-rules

nullable.ts, optional.ts and required.ts all called their wrapped
rule as `nested(input, context)` - context in the 2nd ("options")
slot instead of the 3rd, the same needless-extend()-forcing mistake
already fixed elsewhere in this codebase for pipe/allOf/is-array/
is-record/is-tuple/is-object/one-of. Fixed all three.

Also fixes two more copy-pasted JSDoc blocks found while writing
their benchmarks: fixed.ts's whole doc comment (tag *and*
description) was copied from required.ts and described required's
behavior, not fixed's; nullable.ts's @validator tag said "optional".

Adds benchmarks for the rest of utility-rules: exists, fixed,
nullable, optional, required, getLength, stringReplace, stringSplit,
trim, trimStart, trimEnd.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ments

Adds benchmark/rules/*.bench.ts for all 31 format-rules (isAlpha through
matches), completing rule-category benchmark coverage. Also fixes two
copy-paste JSDoc bugs found while writing the benchmarks: isMobilePhone's
doc described email validation, and matches' doc described UUID coercion.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds resultsToMarkdown() to render the same results table as plain-text
Markdown (no ANSI codes), and has the CLI write it to BENCHMARKS.md at
the project root after every run. Also prints a one-line "✓ <case name>"
as each case finishes, since a full run (~150+ cases) previously left the
console silent for minutes. BENCHMARKS.md is gitignored since its numbers
are machine-dependent and regenerated on every run.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
heapUsedPerOpBytes/rssPerOpBytes were computed by forcing a GC right
before reading the "after" snapshot. Since the benchmarked fn's return
value is always discarded, everything it allocated was garbage by that
point, so the forced GC collected it right before we looked - making
every case report ~0 bytes/op regardless of actual allocation (verified
isObject's 50-field case, which clearly allocates, showed exactly 0).
Removing that second forceGc() (keeping the one before the pass, for a
clean baseline) surfaces real, sensible numbers, e.g. isObject ~1.8-3.7
KB/op vs isBoolean ~0.01-0.4 KB/op. BENCHMARKS.md regenerated with the fix.

Also stop ignoring BENCHMARKS.md so it's tracked in git.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- forwardRef/iif (core/utilities.ts) still called nested validators as
  nested(input, context) instead of nested(input, undefined, context) -
  the wrong-slot perf/correctness regression already fixed everywhere
  else in this project's rules, but missed here since these two live in
  core, not rules/.
- isBigint silently accepted a plain number without coerce: true,
  inconsistent with every sibling type-rule and with toBigint's own
  { coerce: true } default in src/index.ts; a non-integer number also
  leaked a raw RangeError message instead of failing cleanly. Now
  requires coerce for both number and string input, and always fails
  with "Value must be a BigInt".
- isEmail: "must much" -> "must match" typo in its failure message.
- isMobilePhone/isSWIFT: "a valid a ..." double-article typo.
- isPassportNumber: unmatched closing paren in "... PassportNumber)".
- JSDoc fixes: isDate (claimed a date string validates without
  coerce - it doesn't), isObject (didn't mention additionalFields/
  caseInSensitive/detectCircular at all), nullable ("undefined of
  null" typo), getLength (didn't mention Set/Map support), oneOf/pipe
  (both had an empty JSDoc block).

Tests updated/added for the behavior changes; BENCHMARKS.md refreshed
from a full run after the fixes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds docs/api.md (the Validator shape, .silent(), pre-built instances
vs. factories, ExecutionOptions, error shape, composition patterns, and
how to write a custom rule) plus one page per rule category -
docs/api/{type,logical,utility,format}-rules.md - covering every
exported validator function with its signature, options, behavior, and
a verified usage example.

Every example was checked against the matching test/**/*.spec.ts
fixtures (or run directly against @browsery/validator where no spec
existed) rather than invented, which is how the bugs fixed in the
previous commit were found.

README.md is rewritten with an accurate feature summary, a verified
quick-start example, a script reference table, a benchmarking section,
and links into the new docs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@erayhanoglu
erayhanoglu merged commit b4e8ae7 into main Sep 8, 2026
3 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant