Skip to content

v5.1.0

Choose a tag to compare

@hansfpc hansfpc released this 06 Nov 18:50
· 6 commits to canary since this release
3c0b01a

A feature release with one addition: RUT_PATTERN, the shapes validate() accepts, exported as regular-expression strings for schemas and database constraints, where code cannot run. Nothing existing changes: every function behaves exactly as in 5.0.1, and the Modulo 11 algorithm is unchanged.

Why

A RUT rule usually has to live in more than one place: the API schema, a database column, a backend written in another language. validate() cannot run in any of them, so the shape gets retyped there as a hand-written regex, and hand-written RUT regexes drift. They let leading zeros through, accept 9-digit bodies, or match non-ASCII digits through \d. RUT_PATTERN gives those places the exact shape rule the library enforces, and a test suite keeps the two from ever disagreeing.

Do I need to change anything?

No. This release only adds an export. If you don't import RUT_PATTERN, your bundle does not grow: it is a plain object that bundlers drop when unused.

Added

  • RUT_PATTERN — the accepted shapes as plain regular-expression strings, for a JSON Schema or OpenAPI pattern, a zod/valibot regex, or a database CHECK constraint.

    import { RUT_PATTERN, validate } from 'rut.ts'
    
    RUT_PATTERN.any     // '^([1-9][0-9]{6,7}-?[0-9kK]|[1-9][0-9]?[.][0-9]{3}[.][0-9]{3}-[0-9kK])$'
    RUT_PATTERN.compact // '^[1-9][0-9]{6,7}[0-9kK]$'                       123456785
    RUT_PATTERN.hyphen  // '^[1-9][0-9]{6,7}-[0-9kK]$'                      12345678-5
    RUT_PATTERN.dotted  // '^[1-9][0-9]?[.][0-9]{3}[.][0-9]{3}-[0-9kK]$'    12.345.678-5
    
    // zod: the pattern documents the shape, the refinement does the real check
    const rut = z
      .string()
      .trim()
      .regex(new RegExp(RUT_PATTERN.hyphen))
      .refine((value) => validate(value))
    • Safe to paste. The strings are anchored, flagless and backslash-free, so they need no escaping in JSON, YAML or SQL, and they carry the no-leading-zero and 7–8 digit rules themselves.
    • They cannot drift from the validator. For inputs up to the 64-char cap, isRutLike(s) === new RegExp(RUT_PATTERN.any).test(s.trim()), and an accepted input matches exactly one of compact / hyphen / dotted. A test suite pins this.
    • Part of the frozen contract. The four strings are public API, byte for byte.

    What a pattern does not do:

    • It checks shape only, never the verifier digit. About ten in eleven strings that match are not real RUTs, so always run validate() wherever code can run.
    • any and compact match any 9-digit number, a phone number included. Prefer hyphen or dotted in API schemas.
    • Other engines read anchors differently. In Python use re.fullmatch (with re.search, $ also matches before a trailing newline; the same goes for Perl), and in Ruby replace ^/$ with \A/\z, because there they match at every line.

Changed

  • Docs: the 7–8 digit body range is stated as part of the frozen contract. The README and llms.txt now say explicitly that the body must be 1.000.000–99.999.999, and that a shorter body is rejected even when its verifier is correct (validate('999999-K') → false, calculateVerifier('999999') throws). Historical RUNs below one million are deliberately out of scope. No behavior change: this documents what the library already does.

Unchanged

  • Every existing function. validate(), isValidRut(), isRutLike(), format(), clean() and the rest behave exactly as in 5.0.1. validate() does not use the exported strings.
  • The body floor stays at 7 digits. No option to accept shorter bodies was added: validate() remains flag-free except strict.
  • What you ship if you don't use it. A bundle that imports only validate or format is byte-for-byte the same size as before. Importing everything adds about 85 bytes gzipped.

Internal

  • RUT_PATTERN equivalence suite (40 tests): the exact strings, an exhaustive check of every string up to 9 characters over a reduced alphabet (2.4 million), a property test over mutated RUTs, and the documented limits. It fails if either the patterns or the validator change on their own.
  • Contract tests pin the body range (25 tests). The existing "too short" cases also carried a wrong verifier, so nothing in the unit suites proved that validate(), isRutLike(), isValidRut() or calculateVerifier() reject a body for being too short. The new cases use a correct Modulo 11 digit below the floor (999999-K, 999.999-K, 100000-4, 1000-6, 1-9) and check the exact boundaries (999999 / 1000000 / 99999999 / 100000000).
  • Test suite: 613 tests across 17 suites.

Full notes: CHANGELOG.md