ZOYKUHot Board

arrowsw/rut.tsDokumentation

Handle 🇨🇱 RUT values with ease using TypeScript.

Sprach-Hinweis:Es ist keine lokalisierte Dokumentation verfügbar. Es wird das offizielle englische Original angezeigt.
npm i rut.ts
Rut.ts logo

Rut.ts: Handle chilean RUT values with ease using TypeScript.

npm version downloads bundle size types included zero dependencies license

The complete, security-hardened toolkit for the Chilean RUT (Rol Único Tributario): validate, format, clean, decompose and generate — with a correctness contract you can rely on in production.

  • 🪶 Tiny & zero-dependency — tree-shakeable ESM, ships only what you import.
  • 🔒 Hardened by default — bounded parsing, strict mode, generic errors. No ID values leak into logs or traces.
  • 🧠 Fully typed — first-class TypeScript types, no @types package needed.
  • 🌐 Universal — runs in Node (>= 20), the browser, Deno and Bun.
  • ✅ Battle-tested — a differential harness guards every release against regressions.

Installation

npm install rut.ts
# or: bun add rut.ts · pnpm add rut.ts · yarn add rut.ts

Quick start

import { validate, format, clean, decompose, isRutLike } from 'rut.ts'

// Validate — the strict canonical gate; strict mode also rejects placeholders
validate('12.345.678-5') // true
validate('12.345.678-0') // false (wrong verifier)
validate('11.111.111-1', { strict: true }) // false (suspicious)

// Format — accepts compact or formatted input
format('123456785') // '12.345.678-5'
format('123456785', { dots: false }) // '12345678-5'
format('123456789', { throwOnError: false }) // null (wrong verifier)

// Format progressively as the user types (great for form inputs)
format('1234', { incremental: true }) // '1.234'
format('123456785', { incremental: true }) // '12.345.678-5'

// Clean & decompose
clean('12.345.678-5') // '123456785'
decompose('12.345.678-5') // { body: '12345678', verifier: '5' }

// Cheap shape check, no full validation
isRutLike('12.345.678-5') // true

// Safe mode everywhere — return null instead of throwing
format('abc', { throwOnError: false }) // null

Branded types & typed errors

import { isValidRut, InvalidRutError, mask, equals, generate } from 'rut.ts'
import type { Rut } from 'rut.ts'

// Type guard — narrows `unknown`/`string` to the branded `Rut`
function persist(value: string) {
  if (isValidRut(value)) {
    const rut: Rut = value // ✅ the type system knows it was validated
  }
}

// Typed errors — branch on the class/code, never on message text
try {
  mask('not-a-rut') // any safe helper throws InvalidRutError in default mode
} catch (err) {
  if (err instanceof InvalidRutError) err.code // 'INVALID_RUT'
}

// Mask for safe logging, and compare across shapes
mask('12.345.678-5') // '12.***.***-5'
equals('12.345.678-5', '123456785') // true  (same valid RUT, different shapes)
equals('12345678-9', '12345678-9') // false (wrong verifier — not a RUT at all)
equals('12345678-9', '12345678-9', { requireValid: false }) // true (pure shape comparison)

// Generation options
generate() // '29.561.896-5'  (8-digit dotted, default)
generate({ format: 'compact' }) // '233715913'
generate({ bodyLength: 7, format: 'hyphen' }) // '7788862-4'
generate({ count: 3 }) // ['…', '…', '…']

Both err instanceof InvalidRutError and err.code === 'INVALID_RUT' also match an error thrown by another copy of rut.ts in the same process: the ESM build next to the CommonJS one (your app imports it while a dependency require()s it), another installed version, or another realm (an iframe, vm). If the other copy may be an older rut.ts, check err.code: it works with every version.

📚 Full guides and live examples: rut.arrowsw.com

Features

  • Validation — verifier check with bounded input parsing and an optional strict mode that rejects placeholder/repeated-digit RUTs.
  • Branded types — isValidRut() (type guard) narrows input to a branded Rut, so "this string was validated" flows through the type system.
  • Typed errors — InvalidRutError (with a stable code) instead of message-matching; instanceof and code both hold across the ESM and CommonJS builds.
  • Formatting — standardized output, with or without dots.
  • Incremental formatting — progressive formatting as the user types, ideal for form inputs.
  • Masking — mask() produces 12.***.***-5 for safe logging/display.
  • Comparison — equals() answers "same RUT?" across different shapes, checking validity by default ({ requireValid: false } for pure shape comparison).
  • Cleaning — permissively strip extraneous characters and leading zeros.
  • Decomposition — split a RUT into its body and verifier digit.
  • Generation — cryptographically-backed random valid RUTs, with bodyLength, format and count options.
  • Calculate verifier — compute the verifier digit for a given body.
  • Format detection — cheap isRutLike check without full validation.
  • Shape patterns — RUT_PATTERN exposes the accepted shapes as regex strings for JSON Schema, OpenAPI, zod and database constraints.
  • Safe mode — every safe function supports throwOnError: false to return null instead of throwing.
New to RUTs? What the format means

The RUT (Rol Único Tributario) is the unique Chilean identification number used for tax, legal identification, government services, and banking.

Format: XX.XXX.XXX-Y

  • X = Body (7–8 digits)
  • Y = Verifier digit (0–9 or K)

Example: 12.345.678-5

The verifier digit is derived from the body via the Modulo 11 algorithm, which is what makes a RUT self-validating.

Security & correctness

rut.ts treats RUT validation as an identity-security boundary, not just string formatting. That posture is the point of the library:

  • validate(input, { strict: true }) is the recommended acceptance gate for identity-sensitive flows. It rejects malformed dot grouping, caps oversized inputs before parsing, rejects repeated-digit placeholders, and compares the verifier via Modulo 11.
  • Errors are generic (Invalid RUT input) so Chilean ID values never end up echoed into logs, traces, or user-visible exceptions.
  • clean() is intentionally permissive — useful for display/storage normalization, but it does not prove the verifier is correct. Always validate() before accepting a RUT.

Accepted input formats (the validation contract)

validate() and isRutLike() accept only these shapes (optionally with surrounding whitespace, verifier k/K case-insensitive):

Shape Example Notes
Compact 123456785 7–8 digit body + verifier
Compact + hyphen 12345678-5
Canonical dotted 12.345.678-5, 1.234.567-4 Chilean grouping; the - is required

Anything else is rejected, including leading-zero padding (012.345.678-5, 0012345678 — a canonical RUT has none, and padding forms an unbounded family of strings for one RUT), non-canonical dot grouping (12.345678-5, 12345.678-5, 1.2.3.4-5), the dotted shape without its verifier hyphen (12.345.6785), internal spaces, commas, and any input longer than 64 chars.

The body must be 7 or 8 digits (1.000.000–99.999.999), and that range is part of the contract: a shorter body is rejected even when its verifier is correct (validate('999999-K') → false, calculateVerifier('999999') throws). Historical RUNs below one million exist, and some SII document formats still admit them, but they are deliberately out of scope. If you must accept one, check its Modulo 11 digit outside rut.ts.

Zero-padded data? Leading zeros are rejected by validate() / isValidRut() / isRutLike(), but clean(), format() and equals() stay permissive and still strip them. If you ingest fixed-width/zero-padded values, use the two-line recipe below — normalize first, then validate.

The 64-char limit is a security bound, not a format rule. A real RUT is ~9 significant characters, so the cap never rejects a realistic RUT — it just refuses to process implausibly long strings, neutralizing CPU/ReDoS-style abuse before any parsing runs.

💡 clean() / decompose() stay permissive — never treat their output as "validated".

Ingesting dirty or legacy input

Legacy exports, mainframe fixed-width files, and copy-pasted values rarely arrive canonical. The blessed path is two lines — normalize first, then validate the normalized value:

import { clean, validate } from 'rut.ts'

const rut = clean(raw, { throwOnError: false }) // strips zeros, dots, garbage → '12345674' | null
if (rut !== null && validate(rut)) {
  store(rut) // canonical compact form, Modulo 11 verified — safe for UNIQUE keys
}

Two things make this recipe safe: clean() collapses the unbounded zero-padded family into one canonical string (so a UNIQUE constraint can't be bypassed by padding), and validate() then proves the verifier. Never store clean()'s output without the validate() step — clean does not check the Modulo 11 digit.

validate() deliberately has no relaxation flags: it stays a binary canonical gate. When input might be messy, normalize it first — don't reach for a looser validator.

equals() vs validate() — which one?

Question you're asking Use
"Are these the same RUT?" (shape-agnostic, zero-padding tolerated) equals()
"Is this string written as a valid canonical RUT?" (identity/uniqueness gate) validate() / isValidRut()

By default equals also checks validity — two strings with a wrong verifier are not "the same RUT" because they are not RUTs (equals('12345678-9', '12345678-9') → false). Pass { requireValid: false } for the pure shape comparison (useful when deduplicating dirty datasets, where the same typo twice is still one entity).

Intended asymmetry: equals('012345678-5', '12.345.678-5') → true while validate('012345678-5') → false. equals is a normalization operation — it inherits clean()'s permissiveness (leading zeros, separators, case) and adds only the verifier check. Requiring canonical shape would make it useless for the very thing it exists for: comparing different shapes of the same RUT.

Incremental formatting

format(input, { incremental: true }) formats a RUT progressively as the user types — ideal for real-time feedback in form fields.

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  setRut(format(e.target.value, { incremental: true }))
}

Use it for: real-time input formatting and visual feedback. Don't use it for: validating, or formatting already-complete/stored RUTs (use format() / validate()). Incremental output may not be a valid RUT until the input is complete — always validate() the final value.

A trailing hyphen after a complete body (7 or 8 digits) is kept as "verifier pending" — format('12345678-', { incremental: true }) → '12.345.678-' — so the field doesn't re-split the body while the user types or deletes the verifier. A K is always treated as the verifier, however short the input: format('7612K', { incremental: true }) → '7.612-K'.

Shape patterns for schemas and databases

RUT_PATTERN exposes the shapes validate() accepts as plain regular-expression strings, for the places where code cannot run: an OpenAPI or JSON Schema pattern, a zod regex, a database CHECK constraint.

import { RUT_PATTERN, validate } from 'rut.ts'

RUT_PATTERN.hyphen // '^[1-9][0-9]{6,7}-[0-9kK]$'
RUT_PATTERN.dotted // '^[1-9][0-9]?[.][0-9]{3}[.][0-9]{3}-[0-9kK]$'
RUT_PATTERN.compact // '^[1-9][0-9]{6,7}[0-9kK]$'
RUT_PATTERN.any // any of the three shapes

// 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))
# OpenAPI / JSON Schema
rut:
  type: string
  pattern: '^[1-9][0-9]{6,7}-[0-9kK]$'
  • A pattern 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.
  • Prefer hyphen or dotted in API schemas. any and compact match any 9-digit number, a phone number included.
  • Safe to paste. The strings are anchored, flagless and backslash-free, so they need no escaping in JSON, YAML or SQL.
  • They match isRutLike() exactly in JavaScript, after trim() and up to the 64-char cap. 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.

TypeScript types

import type {
  DecomposedRut,
  EqualsOptions,
  FormatOptions,
  GenerateOptions,
  Rut,
  SafeOptions,
  ValidateOptions,
  VerifierDigit,
} from 'rut.ts'

// VerifierDigit:  '0' | '1' | … | '9' | 'K'
// DecomposedRut:  { body: string; verifier: VerifierDigit }
// FormatOptions:  { incremental?: boolean; dots?: boolean; throwOnError?: boolean }
// ValidateOptions:{ strict?: boolean }
// EqualsOptions:  { requireValid?: boolean }
// SafeOptions:    { throwOnError?: boolean }
// GenerateOptions:{ bodyLength?: 7 | 8; format?: 'dotted' | 'compact' | 'hyphen'; count?: number }
// Rut:            string & { /* brand */ }  — a validated RUT (from isValidRut)

Upgrading

The validate() contract is frozen and no further breaking changes are planned. Release notes and migration notes for every version live in the CHANGELOG.

Contributing

Contributions are welcome — feel free to open issues for bugs and feature requests, or submit a pull request.

License

MIT © rut.ts contributors

Dokumentationsspiegel archiviert aus dem offiziellen GitHub-Repository · GitHub-Repository aufrufen
Snapshot-Datum: 2026-11-08