rut.ts 4.0.0 → 4.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +114 -121
  2. package/package.json +2 -1
package/README.md CHANGED
@@ -1,181 +1,174 @@
1
1
  <div align="center">
2
2
  <img src="https://user-images.githubusercontent.com/12705403/158434864-7f13401a-b973-4267-b035-d9882cf6c545.png" alt="Rut.ts logo" width="100%">
3
3
  <h1>Rut.ts: Handle chilean RUT values with ease using TypeScript.</h1>
4
- </div>
5
-
6
- ![Open Bundle](https://deno.bundlejs.com/badge?q=rut.ts@4.0.0)
7
-
8
- > **v4.0.0 is a major, breaking release** focused on production identity hardening.
9
- > Read the [CHANGELOG](./CHANGELOG.md) before upgrading from `3.x`.
10
-
11
- ## What is a RUT?
12
-
13
- The **RUT** (Rol Único Tributario) is the unique Chilean identification number used for:
14
-
15
- - Tax purposes
16
- - Legal identification
17
- - Government services
18
- - Banking and financial transactions
19
4
 
20
- **Format**: `XX.XXX.XXX-Y` where:
5
+ [![npm version](https://img.shields.io/npm/v/rut.ts?color=000&label=npm)](https://www.npmjs.com/package/rut.ts)
6
+ [![downloads](https://img.shields.io/npm/dm/rut.ts?color=000)](https://www.npmjs.com/package/rut.ts)
7
+ [![bundle size](https://deno.bundlejs.com/badge?q=rut.ts&treeshake=[*])](https://bundlejs.com/?q=rut.ts)
8
+ [![types included](https://img.shields.io/npm/types/rut.ts?color=000)](https://www.npmjs.com/package/rut.ts)
9
+ ![zero dependencies](https://img.shields.io/badge/dependencies-0-000)
10
+ [![license](https://img.shields.io/npm/l/rut.ts?color=000)](./LICENSE)
21
11
 
22
- - `X` = Body (7-8 digits)
23
- - `Y` = Verifier digit (0-9 or K)
24
-
25
- **Example**: `12.345.678-5`
26
-
27
- The verifier digit is calculated using the [Modulo 11 algorithm](https://en.wikipedia.org/wiki/Rol_%C3%9Anico_Tributario) to validate the RUT's authenticity.
28
-
29
- ---
12
+ </div>
30
13
 
31
- ## Features
14
+ The complete, security-hardened toolkit for the Chilean **RUT** (Rol Único
15
+ Tributario): validate, format, clean, decompose and generate — with a
16
+ correctness contract you can rely on in production.
32
17
 
33
- - **Validation**: Check if a RUT is valid with bounded input parsing, verifier validation, and optional strict mode.
34
- - **Cleaning**: Permissively remove extraneous characters and leading zeros from RUT strings.
35
- - **Formatting**: Convert valid RUTs into a standardized format, with or without dots.
36
- - **Incremental Formatting**: Format RUTs progressively as the user types (ideal for form inputs).
37
- - **Decomposition**: Split a RUT into its body and verifier digit.
38
- - **Generation**: Generate valid random RUT numbers for testing, using Web Crypto when available.
39
- - **Calculate Verifier**: Calculate the verifier digit for a given RUT body.
40
- - **Format Detection**: Check if a bounded string looks like a RUT format with `isRutLike`.
41
- - **Safe Mode**: All safe functions support `throwOnError: false` to return `null` instead of throwing generic errors.
18
+ - 🪶 **Tiny & zero-dependency** tree-shakeable ESM, ships only what you import.
19
+ - 🔒 **Hardened by default** bounded parsing, strict mode, generic errors. No ID values leak into logs or traces.
20
+ - 🧠 **Fully typed** first-class TypeScript types, no `@types` package needed.
21
+ - 🌐 **Universal** runs in Node, the browser, Deno and Bun. Uses Web Crypto when available.
22
+ - **Battle-tested** a differential harness guards every release against regressions.
42
23
 
43
24
  ## Installation
44
25
 
45
- Using [bun](https://bun.sh/):
46
-
47
- $ bun add rut.ts
48
-
49
- Using [npm](https://www.npmjs.com/):
50
-
51
- $ npm install rut.ts
52
-
53
- Using [pnpm](https://pnpm.io/):
54
-
55
- $ pnpm install rut.ts
26
+ ```bash
27
+ npm install rut.ts
28
+ # or: bun add rut.ts · pnpm add rut.ts · yarn add rut.ts
29
+ ```
56
30
 
57
- ## Quick Examples
31
+ ## Quick start
58
32
 
59
33
  ```typescript
60
- import { validate, format, clean, isRutLike, decompose } from 'rut.ts'
34
+ import { validate, format, clean, decompose, isRutLike } from 'rut.ts'
61
35
 
62
- // Validation
36
+ // Validate — strict mode also rejects suspicious placeholder RUTs
63
37
  validate('12.345.678-5') // true
64
38
  validate('12.345.678-0') // false (wrong verifier)
65
- validate('11.111.111-1', { strict: true }) // false (strict mode rejects suspicious RUTs)
66
- validate('8.888.888-K', { strict: true }) // false (uppercase K suspicious RUT)
39
+ validate('11.111.111-1', { strict: true }) // false (suspicious)
67
40
 
68
- // Formatting
41
+ // Format — accepts compact or formatted input
69
42
  format('123456785') // '12.345.678-5'
70
43
  format('123456785', { dots: false }) // '12345678-5'
71
44
  format('123456789', { throwOnError: false }) // null (wrong verifier)
72
45
 
73
- // Incremental formatting (for form inputs)
46
+ // Format progressively as the user types (great for form inputs)
74
47
  format('1234', { incremental: true }) // '1.234'
75
- format('12345678', { incremental: true }) // '1.234.567-8' (8 chars = complete)
76
- format('123456785', { incremental: true }) // '12.345.678-5' (9 chars = complete)
48
+ format('123456785', { incremental: true }) // '12.345.678-5'
77
49
 
78
- // Cleaning
50
+ // Clean & decompose
79
51
  clean('12.345.678-5') // '123456785'
80
- // clean() normalizes shape only. Use validate() before accepting a RUT.
81
-
82
- // Decomposition
83
- const { body, verifier } = decompose('12.345.678-5')
84
- console.log(body) // '12345678'
85
- console.log(verifier) // '5'
52
+ decompose('12.345.678-5') // { body: '12345678', verifier: '5' }
86
53
 
87
- // Format detection (without full validation)
54
+ // Cheap shape check, no full validation
88
55
  isRutLike('12.345.678-5') // true
89
- isRutLike('not-a-rut') // false
90
56
 
91
- // Safe mode (returns null instead of throwing)
92
- clean('invalid', { throwOnError: false }) // null
57
+ // Safe mode everywhere — return null instead of throwing
93
58
  format('abc', { throwOnError: false }) // null
94
59
  ```
95
60
 
96
- ## Incremental Formatting
61
+ > 📚 Full guides and live examples: **[rut.arrowsw.com](https://rut.arrowsw.com/)**
97
62
 
98
- The `incremental` option in `format()` allows you to format RUTs progressively as the user types. This is useful for real-time formatting in form inputs.
63
+ ## Features
99
64
 
100
- ```typescript
101
- // Example: React input handler
102
- const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
103
- const formatted = format(e.target.value, { incremental: true })
104
- setRut(formatted)
105
- }
106
- ```
65
+ - **Validation** — verifier check with bounded input parsing and an optional `strict` mode that rejects placeholder/repeated-digit RUTs.
66
+ - **Formatting** standardized output, with or without dots.
67
+ - **Incremental formatting** progressive formatting as the user types, ideal for form inputs.
68
+ - **Cleaning** permissively strip extraneous characters and leading zeros.
69
+ - **Decomposition** — split a RUT into its body and verifier digit.
70
+ - **Generation** — cryptographically-backed random valid RUTs for tests (Web Crypto when available).
71
+ - **Calculate verifier** — compute the verifier digit for a given body.
72
+ - **Format detection** — cheap `isRutLike` check without full validation.
73
+ - **Safe mode** — every safe function supports `throwOnError: false` to return `null` instead of throwing.
74
+
75
+ <details>
76
+ <summary><strong>New to RUTs? What the format means</strong></summary>
107
77
 
108
- > **Note**: Incremental formatting will format the input progressively even for incomplete RUTs. The formatted output may not represent a valid RUT until the input is complete. Always use `validate()` to verify the final RUT before processing.
78
+ <br>
109
79
 
110
- ## Production Validation Notes
80
+ The **RUT** (Rol Único Tributario) is the unique Chilean identification number
81
+ used for tax, legal identification, government services, and banking.
111
82
 
112
- For security-sensitive identity flows, prefer `validate(input, { strict: true })` as the acceptance gate. The validator now rejects malformed dot grouping, caps oversized inputs before parsing, rejects repeated-digit placeholders in strict mode, and compares the verifier digit using the Modulo 11 algorithm.
83
+ **Format**: `XX.XXX.XXX-Y`
84
+
85
+ - `X` = Body (7–8 digits)
86
+ - `Y` = Verifier digit (`0`–`9` or `K`)
87
+
88
+ **Example**: `12.345.678-5`
113
89
 
114
- `clean()` remains intentionally permissive for input normalization. It is useful before display or storage, but it does not prove that the verifier digit is correct. `format()` validates the verifier digit in non-incremental mode and returns `null` in safe mode for invalid complete RUTs.
90
+ The verifier digit is derived from the body via the
91
+ [Modulo 11 algorithm](https://en.wikipedia.org/wiki/Rol_%C3%9Anico_Tributario),
92
+ which is what makes a RUT self-validating.
115
93
 
116
- Error messages are generic (`Invalid RUT input`) so invalid Chilean ID values are not echoed into logs, traces, or user-visible exceptions.
94
+ </details>
95
+
96
+ ## Security & correctness
97
+
98
+ `rut.ts` treats RUT validation as an identity-security boundary, not just string
99
+ formatting. That posture is the point of the library:
100
+
101
+ - **`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.
102
+ - **Errors are generic** (`Invalid RUT input`) so Chilean ID values never end up echoed into logs, traces, or user-visible exceptions.
103
+ - **`clean()` is intentionally permissive** — useful for display/storage normalization, but it does _not_ prove the verifier is correct. Always `validate()` before accepting a RUT.
117
104
 
118
105
  ### Accepted input formats (the validation contract)
119
106
 
120
- `validate()` and `isRutLike()` accept **only** these shapes (optionally with leading
121
- zeros and surrounding whitespace, verifier `k`/`K` case-insensitive):
107
+ `validate()` and `isRutLike()` accept **only** these shapes (optionally with
108
+ leading zeros and surrounding whitespace, verifier `k`/`K` case-insensitive):
122
109
 
123
- | Shape | Example | Notes |
124
- |-------|---------|-------|
125
- | Compact | `123456785` | 7–8 digit body + verifier |
126
- | Compact + hyphen | `12345678-5` | |
110
+ | Shape | Example | Notes |
111
+ | ---------------- | ----------------------------- | ------------------------------- |
112
+ | Compact | `123456785` | 7–8 digit body + verifier |
113
+ | Compact + hyphen | `12345678-5` | |
127
114
  | Canonical dotted | `12.345.678-5`, `1.234.567-4` | Chilean grouping from the right |
128
115
 
129
116
  Anything else is rejected, **including non-canonical dot grouping** that older
130
- versions accepted: `12.345678-5`, `12345.678-5`, `1.2.3.4.5.6.7.8-5`,
131
- internal spaces (`12 345 678 5`), commas, and any input longer than 64 chars.
117
+ versions accepted (`12.345678-5`, `12345.678-5`, `1.2.3.4-5`), internal spaces,
118
+ commas, and any input longer than 64 chars.
132
119
 
133
120
  > The 64-char limit is a **security bound, not a format rule**. A real RUT is
134
- > ~9 significant characters (~12 formatted), so the cap never rejects a
135
- > realistically formatted RUT — it only refuses to *process* implausibly long
136
- > strings, which neutralizes CPU/ReDoS-style abuse before any parsing runs.
137
-
138
- > ⚠️ **Migrating a large dataset?** If your upstream emits RUTs in a non-canonical
139
- > shape, normalize it to one of the three accepted forms **before** calling
140
- > `validate()`, or run the differential harness against a representative sample
141
- > first: `npm run test:differential` (see
142
- > [`tests/differential.test.ts`](./tests/differential.test.ts); it writes
143
- > `tests/differential-report.md`). `clean()`/`decompose()` stay permissive and
144
- > will still parse some of those shapes — never treat their output as
121
+ > ~9 significant characters, so the cap never rejects a realistic RUT — it just
122
+ > refuses to _process_ implausibly long strings, neutralizing CPU/ReDoS-style
123
+ > abuse before any parsing runs.
124
+
125
+ > 💡 **Migrating a dataset?** If your upstream emits RUTs in a non-canonical
126
+ > shape, normalize to one of the three accepted forms before calling
127
+ > `validate()`, or sanity-check a representative sample with
128
+ > `npm run test:differential` (writes `tests/differential-report.md`).
129
+ > `clean()` / `decompose()` stay permissive — never treat their output as
145
130
  > "validated".
146
131
 
147
- ### When to use incremental mode
148
-
149
- **✅ Use incremental when:**
150
-
151
- - Formatting user input in real-time as they type
152
- - Providing immediate visual feedback in form fields
153
- - Improving UX with progressive formatting
132
+ ## Incremental formatting
154
133
 
155
- **❌ Don't use incremental when:**
134
+ `format(input, { incremental: true })` formats a RUT progressively as the user
135
+ types — ideal for real-time feedback in form fields.
156
136
 
157
- - Formatting already complete/stored RUTs (use default `format()`)
158
- - Validating RUTs (use `validate()` instead)
159
- - Processing final form submission values
160
-
161
- ## Usage
162
-
163
- Please refer to [the documentation](https://rut.arrowsw.com/) for more detailed examples.
137
+ ```typescript
138
+ const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
139
+ setRut(format(e.target.value, { incremental: true }))
140
+ }
141
+ ```
164
142
 
165
- ## TypeScript Types
143
+ **Use it for:** real-time input formatting and visual feedback.
144
+ **Don't use it for:** validating, or formatting already-complete/stored RUTs
145
+ (use `format()` / `validate()`). Incremental output may not be a valid RUT until
146
+ the input is complete — always `validate()` the final value.
166
147
 
167
- The library exports the following types:
148
+ ## TypeScript types
168
149
 
169
150
  ```typescript
170
151
  import type { DecomposedRut, FormatOptions, SafeOptions, ValidateOptions, VerifierDigit } from 'rut.ts'
171
152
 
172
- // VerifierDigit: '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | 'K'
173
- // DecomposedRut: { body: string; verifier: string }
174
- // FormatOptions: { incremental?: boolean; dots?: boolean; throwOnError?: boolean }
175
- // ValidateOptions: { strict?: boolean }
176
- // SafeOptions: { throwOnError?: boolean }
153
+ // VerifierDigit: '0' | '1' | | '9' | 'K'
154
+ // DecomposedRut: { body: string; verifier: string }
155
+ // FormatOptions: { incremental?: boolean; dots?: boolean; throwOnError?: boolean }
156
+ // ValidateOptions:{ strict?: boolean }
157
+ // SafeOptions: { throwOnError?: boolean }
177
158
  ```
178
159
 
160
+ ## Upgrading from v3
161
+
162
+ `v4` hardens validation for production identity flows and tightens the accepted
163
+ input contract (see the table above). If you're coming from `3.x`, the
164
+ [**CHANGELOG**](./CHANGELOG.md) lists every change and how to migrate — most
165
+ codebases only need to normalize input shape before `validate()`.
166
+
179
167
  ## Contributing
180
168
 
181
- Contributions to this library are welcome. Please feel free to submit pull requests or create issues for bugs and feature requests.
169
+ Contributions are welcome feel free to open issues for bugs and feature
170
+ requests, or submit a pull request.
171
+
172
+ ## License
173
+
174
+ [MIT](./LICENSE) © rut.ts contributors
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rut.ts",
3
- "version": "4.0.0",
3
+ "version": "4.0.1",
4
4
  "type": "module",
5
5
  "description": "Handle chilean RUT values with ease.",
6
6
  "author": "arrowsw",
@@ -46,6 +46,7 @@
46
46
  "eslint-plugin-prettier": "5.5.5",
47
47
  "eslint-plugin-react": "7.37.5",
48
48
  "eslint-plugin-react-hooks": "7.0.1",
49
+ "expect-type": "1.3.0",
49
50
  "jest": "30.2.0",
50
51
  "prettier": "3.8.1",
51
52
  "terser": "5.46.0",