Migration
This page covers the two recent majors: 5.0.0 (the final-shape release — the API contract is frozen after it) and 4.0.0 (production identity hardening).
Always cross-check the full CHANGELOG for the authoritative, version-exact list before upgrading a production system.
Migration to v5 (from 4.x)#
5.0.0 closes the validation contract and makes equals() answer the question its name asks. The Modulo 11 algorithm is unchanged. For most projects — normally-formatted RUTs, equals() used as "same RUT?" — nothing changes.
At a glance#
| Area | 4.1.0 behavior | 5.0.0 behavior |
|---|---|---|
validate() / isValidRut() / isRutLike() | Accepted leading-zero padding (0012345674) | Reject leading zeros — a canonical RUT has none |
equals() | Pure shape comparison (never checked the verifier) | Checks Modulo 11 by default; { requireValid: false } restores 4.x behavior |
getInvalidRutError() | Deprecated | Removed — catch InvalidRutError / check err.code === 'INVALID_RUT' |
| Node.js | engines: node >=14 | engines: node >=20 (Web Crypto guaranteed) |
1. Leading zeros are rejected by the acceptance predicates#
Zero-padding forms an unbounded family of distinct strings for one RUT (12345678 = 012345678 = …) — a canonicalization hazard for uniqueness gates. The lenient helpers (clean(), format(), equals()) still strip leading zeros; only the acceptance predicates refuse them.
import { clean, validate } from 'rut.ts'
validate('0012345674') // 4.x: true → 5.0.0: false
// Ingest zero-padded / messy input with the two-line recipe:
const rut = clean(raw, { throwOnError: false }) // '0012345674' → '12345674' | null
if (rut !== null && validate(rut)) store(rut) // canonical, Modulo 11 verifiedAction: if you feed fixed-width/zero-padded exports directly into validate(), normalize first with the recipe. Never store clean()'s output without the validate() step — clean does not check the verifier.
2. equals() checks validity by default#
Two strings with a wrong verifier are not "the same RUT" — they are not RUTs:
import { equals } from 'rut.ts'
equals('12345678-9', '12345678-9') // 4.x: true → 5.0.0: false (wrong verifier)
equals('12345678-9', '12345678-9', { requireValid: false }) // true (4.x behavior)
// Validity is checked on the NORMALIZED value (intended asymmetry):
equals('012345678-5', '12.345.678-5') // still true, though validate('012345678-5') is falseAction: if you used equals() to deduplicate dirty datasets (same typo in two rows = same entity), pass { requireValid: false }. Everything else keeps working.
3. getInvalidRutError() is gone#
import { clean, InvalidRutError } from 'rut.ts'
try {
clean('not-a-rut')
} catch (err) {
if (err instanceof InvalidRutError) {
// err.code === 'INVALID_RUT'
}
}Action: replace calls with instanceof InvalidRutError or err.code === 'INVALID_RUT'.
4. Node.js >= 20#
Node 14–18 are past end-of-life, and 20 is the first version that guarantees globalThis.crypto. Browsers, Deno and Bun are unaffected.
Action: upgrade the runtime if you deploy on Node < 20.
5.0.0 is the release after which the contract freezes: no further breaking
changes are planned, and the throwOnError: true defaults will not change
until a hypothetical v6. New capabilities arrive as additive minors.
Migration to v4 (from 3.x)#
Version 4.0.0 is a major, breaking release focused on production identity hardening. This section covers what changed and the concrete edits to make when upgrading from 3.x. (Upgrading from 3.x today? Apply this section first, then the v5 section above.)
At a glance#
| Area | 3.x behavior | 4.0.0 behavior |
|---|---|---|
| Input length | Unbounded regex parsing | Length-capped before parsing (ReDoS-safe) |
format() | Formats without checking the verifier | Validates the verifier digit in non-incremental mode |
| Error messages | May include the offending value | Generic Invalid RUT input — never echoes ID values |
| Non-string input | Could throw TypeError | Treated as invalid: null in safe mode, generic error otherwise |
generate() | Math.random() | Web Crypto when available, non-suspicious bodies |
validate() | Lenient about dot grouping | Rejects ambiguous/non-canonical grouping; strict mode added |
Breaking changes and fixes#
1. format() now validates the verifier
In 3.x, format() would happily format a structurally-shaped value even if the verifier digit was wrong. In 4.0.0, non-incremental format() throws (or returns null in safe mode) when the verifier is incorrect.
import { format } from 'rut.ts'
// 3.x: returned a formatted string
// 4.0.0: throws — verifier is wrong
format('12.345.678-0', { throwOnError: false }) // null
// Incremental mode is unchanged: still formats partial input
format('123456', { incremental: true }) // '123.456'Action: if you relied on format() to format unvalidated input, either switch to incremental: true, or validate first and handle the null/throw path.
2. Generic error messages
Errors no longer include the offending RUT, so Chilean ID values never leak into logs, traces, or error trackers.
import { clean } from 'rut.ts'
try {
clean('invalid')
} catch (e) {
// 3.x: "Invalid RUT: invalid"
// 4.0.0: "Invalid RUT input"
console.error((e as Error).message)
}Action: if you string-matched on error messages, update those checks. Prefer safe mode ({ throwOnError: false }) and branch on null instead.
3. Non-string inputs are invalid, not a TypeError
getBody(), getVerifier(), decompose(), and friends previously could throw a TypeError on null/undefined/numbers. Now they treat non-strings as ordinary invalid input.
import { getBody } from 'rut.ts'
getBody(null, { throwOnError: false }) // null (was: TypeError)
getBody(12345678, { throwOnError: false }) // null (was: TypeError)Action: remove try/catch blocks that specifically handled TypeError; rely on the null path in safe mode.
4. Stricter validate() and the new strict option
validate() now rejects ambiguous dot grouping (e.g. 12.345678-5) and oversized input fails fast. The new strict: true option also rejects repeated-digit placeholders.
import { validate } from 'rut.ts'
validate('12.345.678-5') // true
validate('12.345678-5') // false (was: true in some 3.x paths)
validate('11.111.111-1') // true
validate('11.111.111-1', { strict: true }) // false (placeholder)Action: adopt validate(input, { strict: true }) as the final acceptance gate for real identities. See Security.
5. generate() is crypto-backed
generate() now uses the Web Crypto API when available, always produces 8-digit, non-suspicious bodies, and the result passes validate(rut, { strict: true }).
import { generate, validate } from 'rut.ts'
const rut = generate()
validate(rut, { strict: true }) // always trueAction: none required. Generated values remain test/dev data — never assign them as real identities.
Recommended upgrade checklist#
Bump the dependency
npm install rut.ts@^4Switch to safe mode at boundaries
Replace try/catch around clean/format/decompose with
{ throwOnError: false } and branch on null.
Gate acceptance with strict validate()
Use validate(input, { strict: true }) wherever you accept a real
identity before storing it.
Re-check format() call sites
Any format() on unvalidated input must handle the new
invalid-verifier path or move to incremental: true.
Run your test suite
Update assertions that matched old error message strings.
Most upgrades are small: adopt safe mode, add strict: true at the
acceptance gate, and stop string-matching error messages.