Skip to content

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#

Area4.1.0 behavior5.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()DeprecatedRemoved — catch InvalidRutError / check err.code === 'INVALID_RUT'
Node.jsengines: node >=14engines: 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.

TypeScript
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 verified

Action: 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:

TypeScript
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 false

Action: 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#

TypeScript
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#

Area3.x behavior4.0.0 behavior
Input lengthUnbounded regex parsingLength-capped before parsing (ReDoS-safe)
format()Formats without checking the verifierValidates the verifier digit in non-incremental mode
Error messagesMay include the offending valueGeneric Invalid RUT input — never echoes ID values
Non-string inputCould throw TypeErrorTreated as invalid: null in safe mode, generic error otherwise
generate()Math.random()Web Crypto when available, non-suspicious bodies
validate()Lenient about dot groupingRejects 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.

TypeScript
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.

TypeScript
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.

TypeScript
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.

TypeScript
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 }).

TypeScript
import { generate, validate } from 'rut.ts'
 
const rut = generate()
validate(rut, { strict: true }) // always true

Action: none required. Generated values remain test/dev data — never assign them as real identities.

1

Bump the dependency

Shell
npm install rut.ts@^4
2

Switch to safe mode at boundaries

Replace try/catch around clean/format/decompose with { throwOnError: false } and branch on null.

3

Gate acceptance with strict validate()

Use validate(input, { strict: true }) wherever you accept a real identity before storing it.

4

Re-check format() call sites

Any format() on unvalidated input must handle the new invalid-verifier path or move to incremental: true.

5

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.