rut.ts 3.4.0 → 4.0.0
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.
- package/README.md +68 -22
- package/dist/cjs/index.d.ts +5 -3
- package/dist/cjs/index.min.js +1 -1
- package/dist/esm/index.d.ts +5 -3
- package/dist/esm/index.min.js +1 -1
- package/package.json +6 -4
package/README.md
CHANGED
|
@@ -3,15 +3,22 @@
|
|
|
3
3
|
<h1>Rut.ts: Handle chilean RUT values with ease using TypeScript.</h1>
|
|
4
4
|
</div>
|
|
5
5
|
|
|
6
|
+

|
|
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
|
+
|
|
6
11
|
## What is a RUT?
|
|
7
12
|
|
|
8
13
|
The **RUT** (Rol Único Tributario) is the unique Chilean identification number used for:
|
|
14
|
+
|
|
9
15
|
- Tax purposes
|
|
10
16
|
- Legal identification
|
|
11
17
|
- Government services
|
|
12
18
|
- Banking and financial transactions
|
|
13
19
|
|
|
14
20
|
**Format**: `XX.XXX.XXX-Y` where:
|
|
21
|
+
|
|
15
22
|
- `X` = Body (7-8 digits)
|
|
16
23
|
- `Y` = Verifier digit (0-9 or K)
|
|
17
24
|
|
|
@@ -23,15 +30,15 @@ The verifier digit is calculated using the [Modulo 11 algorithm](https://en.wiki
|
|
|
23
30
|
|
|
24
31
|
## Features
|
|
25
32
|
|
|
26
|
-
- **Validation**: Check if a RUT is valid
|
|
27
|
-
- **Cleaning**:
|
|
28
|
-
- **Formatting**: Convert RUTs into a standardized format, with or without dots.
|
|
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.
|
|
29
36
|
- **Incremental Formatting**: Format RUTs progressively as the user types (ideal for form inputs).
|
|
30
37
|
- **Decomposition**: Split a RUT into its body and verifier digit.
|
|
31
|
-
- **Generation**: Generate valid random RUT numbers.
|
|
38
|
+
- **Generation**: Generate valid random RUT numbers for testing, using Web Crypto when available.
|
|
32
39
|
- **Calculate Verifier**: Calculate the verifier digit for a given RUT body.
|
|
33
|
-
- **Format Detection**: Check if a string looks like a RUT format with `isRutLike`.
|
|
34
|
-
- **Safe Mode**: All functions support `throwOnError: false` to return `null` instead of throwing errors.
|
|
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.
|
|
35
42
|
|
|
36
43
|
## Installation
|
|
37
44
|
|
|
@@ -47,41 +54,43 @@ Using [pnpm](https://pnpm.io/):
|
|
|
47
54
|
|
|
48
55
|
$ pnpm install rut.ts
|
|
49
56
|
|
|
50
|
-
|
|
51
57
|
## Quick Examples
|
|
52
58
|
|
|
53
59
|
```typescript
|
|
54
60
|
import { validate, format, clean, isRutLike, decompose } from 'rut.ts'
|
|
55
61
|
|
|
56
62
|
// Validation
|
|
57
|
-
validate('12.345.678-5')
|
|
58
|
-
validate('12.345.678-0')
|
|
59
|
-
validate('11.111.111-1', { strict: true })
|
|
63
|
+
validate('12.345.678-5') // true
|
|
64
|
+
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)
|
|
60
67
|
|
|
61
68
|
// Formatting
|
|
62
|
-
format('123456785')
|
|
69
|
+
format('123456785') // '12.345.678-5'
|
|
63
70
|
format('123456785', { dots: false }) // '12345678-5'
|
|
71
|
+
format('123456789', { throwOnError: false }) // null (wrong verifier)
|
|
64
72
|
|
|
65
73
|
// Incremental formatting (for form inputs)
|
|
66
|
-
format('1234', { incremental: true })
|
|
74
|
+
format('1234', { incremental: true }) // '1.234'
|
|
67
75
|
format('12345678', { incremental: true }) // '1.234.567-8' (8 chars = complete)
|
|
68
76
|
format('123456785', { incremental: true }) // '12.345.678-5' (9 chars = complete)
|
|
69
77
|
|
|
70
78
|
// Cleaning
|
|
71
|
-
clean('12.345.678-5')
|
|
79
|
+
clean('12.345.678-5') // '123456785'
|
|
80
|
+
// clean() normalizes shape only. Use validate() before accepting a RUT.
|
|
72
81
|
|
|
73
82
|
// Decomposition
|
|
74
83
|
const { body, verifier } = decompose('12.345.678-5')
|
|
75
|
-
console.log(body)
|
|
76
|
-
console.log(verifier)
|
|
84
|
+
console.log(body) // '12345678'
|
|
85
|
+
console.log(verifier) // '5'
|
|
77
86
|
|
|
78
87
|
// Format detection (without full validation)
|
|
79
|
-
isRutLike('12.345.678-5')
|
|
80
|
-
isRutLike('not-a-rut')
|
|
88
|
+
isRutLike('12.345.678-5') // true
|
|
89
|
+
isRutLike('not-a-rut') // false
|
|
81
90
|
|
|
82
91
|
// Safe mode (returns null instead of throwing)
|
|
83
|
-
clean('invalid', { throwOnError: false })
|
|
84
|
-
format('abc', { throwOnError: false })
|
|
92
|
+
clean('invalid', { throwOnError: false }) // null
|
|
93
|
+
format('abc', { throwOnError: false }) // null
|
|
85
94
|
```
|
|
86
95
|
|
|
87
96
|
## Incremental Formatting
|
|
@@ -98,22 +107,60 @@ const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
|
98
107
|
|
|
99
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.
|
|
100
109
|
|
|
110
|
+
## Production Validation Notes
|
|
111
|
+
|
|
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.
|
|
113
|
+
|
|
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.
|
|
115
|
+
|
|
116
|
+
Error messages are generic (`Invalid RUT input`) so invalid Chilean ID values are not echoed into logs, traces, or user-visible exceptions.
|
|
117
|
+
|
|
118
|
+
### Accepted input formats (the validation contract)
|
|
119
|
+
|
|
120
|
+
`validate()` and `isRutLike()` accept **only** these shapes (optionally with leading
|
|
121
|
+
zeros and surrounding whitespace, verifier `k`/`K` case-insensitive):
|
|
122
|
+
|
|
123
|
+
| Shape | Example | Notes |
|
|
124
|
+
|-------|---------|-------|
|
|
125
|
+
| Compact | `123456785` | 7–8 digit body + verifier |
|
|
126
|
+
| Compact + hyphen | `12345678-5` | |
|
|
127
|
+
| Canonical dotted | `12.345.678-5`, `1.234.567-4` | Chilean grouping from the right |
|
|
128
|
+
|
|
129
|
+
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.
|
|
132
|
+
|
|
133
|
+
> 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
|
|
145
|
+
> "validated".
|
|
146
|
+
|
|
101
147
|
### When to use incremental mode
|
|
102
148
|
|
|
103
149
|
**✅ Use incremental when:**
|
|
150
|
+
|
|
104
151
|
- Formatting user input in real-time as they type
|
|
105
152
|
- Providing immediate visual feedback in form fields
|
|
106
153
|
- Improving UX with progressive formatting
|
|
107
154
|
|
|
108
155
|
**❌ Don't use incremental when:**
|
|
156
|
+
|
|
109
157
|
- Formatting already complete/stored RUTs (use default `format()`)
|
|
110
158
|
- Validating RUTs (use `validate()` instead)
|
|
111
159
|
- Processing final form submission values
|
|
112
160
|
|
|
113
161
|
## Usage
|
|
114
162
|
|
|
115
|
-
Please refer to [the documentation](https://
|
|
116
|
-
|
|
163
|
+
Please refer to [the documentation](https://rut.arrowsw.com/) for more detailed examples.
|
|
117
164
|
|
|
118
165
|
## TypeScript Types
|
|
119
166
|
|
|
@@ -129,7 +176,6 @@ import type { DecomposedRut, FormatOptions, SafeOptions, ValidateOptions, Verifi
|
|
|
129
176
|
// SafeOptions: { throwOnError?: boolean }
|
|
130
177
|
```
|
|
131
178
|
|
|
132
|
-
|
|
133
179
|
## Contributing
|
|
134
180
|
|
|
135
181
|
Contributions to this library are welcome. Please feel free to submit pull requests or create issues for bugs and feature requests.
|
package/dist/cjs/index.d.ts
CHANGED
|
@@ -14,9 +14,10 @@ type ValidateOptions = {
|
|
|
14
14
|
strict?: boolean;
|
|
15
15
|
};
|
|
16
16
|
type VerifierDigit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | 'K';
|
|
17
|
-
export declare const getInvalidRutError: (
|
|
17
|
+
export declare const getInvalidRutError: (_rut?: unknown) => string;
|
|
18
18
|
/**
|
|
19
19
|
* Cleans the input string by removing leading zeros, non-numeric characters, and ensures the RUT is uppercased.
|
|
20
|
+
* This is a permissive normalization helper and does not validate the verifier digit.
|
|
20
21
|
* @param {string} rut - The RUT string to clean.
|
|
21
22
|
* @param {SafeOptions} [options] - Configuration options.
|
|
22
23
|
* @param {boolean} [options.throwOnError=true] - If true (default), throws an error for invalid RUTs. If false, returns null.
|
|
@@ -84,10 +85,10 @@ declare function decompose(rut: string, options?: SafeOptions): DecomposedRut |
|
|
|
84
85
|
/**
|
|
85
86
|
* Checks if a string has a valid RUT format (without validating the verifier digit).
|
|
86
87
|
* Useful for quick format validation in UI before full validation.
|
|
87
|
-
* @param {
|
|
88
|
+
* @param {unknown} rut - The value to check.
|
|
88
89
|
* @returns {boolean} True if the string looks like a RUT format, false otherwise.
|
|
89
90
|
*/
|
|
90
|
-
declare const isRutLike: (rut:
|
|
91
|
+
declare const isRutLike: (rut: unknown) => boolean;
|
|
91
92
|
/**
|
|
92
93
|
* Calculates the verifier digit for a given RUT body.
|
|
93
94
|
* @param {string} rutBody - The body of the RUT for which to calculate the verifier.
|
|
@@ -138,6 +139,7 @@ declare function format(rut: string, options: FormatOptions & {
|
|
|
138
139
|
declare function format(rut: string, options?: FormatOptions): string | null;
|
|
139
140
|
/**
|
|
140
141
|
* Generates a random valid RUT string.
|
|
142
|
+
* Uses Web Crypto when available, and falls back to Math.random in older runtimes.
|
|
141
143
|
* @returns {string} A randomly generated, valid RUT string.
|
|
142
144
|
*/
|
|
143
145
|
declare const generate: () => string;
|
package/dist/cjs/index.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.isRutLike=exports.generate=exports.
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.isRutLike=exports.generate=exports.validate=exports.getInvalidRutError=void 0,exports.clean=clean,exports.format=format,exports.calculateVerifier=calculateVerifier,exports.getBody=getBody,exports.getVerifier=getVerifier,exports.decompose=decompose;const getInvalidRutError=t=>"Invalid RUT input";exports.getInvalidRutError=getInvalidRutError;const withThrowOption=t=>({throwOnError:null==t||t}),MIN_RUT_LENGTH=8,MAX_RUT_LENGTH=9,MIN_BODY_LENGTH=7,MAX_BODY_LENGTH=8,MAX_RUT_INPUT_LENGTH=64,MIN_GENERATED_BODY=1e7,MAX_GENERATED_BODY=99999999,UINT32_RANGE=4294967296,patterns={compact:/^0*\d{7,8}[\dkK]$/,compactWithHyphen:/^0*\d{7,8}-[\dkK]$/,dotted:/^0*\d{1,3}\.\d{3}\.\d{3}-?[\dkK]$/,invalidRutChars:/[^0-9kK]+/g,bodySeparators:/[.\-\s]+/g,bodyDigits:/^\d+$/},fail=(t,e)=>{if(e)throw new Error((0,exports.getInvalidRutError)(t));return null},isBoundedString=t=>"string"==typeof t&&t.length>0&&t.length<=64,normalizeRutValue=t=>t.replace(patterns.invalidRutChars,"").replace(/^0+/,"").toUpperCase(),isCleanRut=t=>{if(t.length<MIN_RUT_LENGTH||t.length>9)return!1;const e=t.slice(0,-1),r=t.slice(-1);return e.length>=7&&e.length<=8&&patterns.bodyDigits.test(e)&&/^[\dK]$/.test(r)},isVerifierDigit=t=>/^[\dK]$/.test(t),parseRutLike=t=>{if(!isBoundedString(t))return null;const e=t.trim();if(0===e.length)return null;if(!(patterns.compact.test(e)||patterns.compactWithHyphen.test(e)||patterns.dotted.test(e)))return null;const r=normalizeRutValue(e);return isCleanRut(r)?{body:r.slice(0,-1),verifier:r.slice(-1)}:null},cleanRaw=t=>{const e=normalizeRutValue(t.slice(0,64)),r=e.replace(/K/g,"");return(e.endsWith("K")?`${r}K`:r).slice(0,9)},normalizeRutBody=t=>{if(!isBoundedString(t))return null;const e=t.replace(patterns.bodySeparators,"").replace(/^0+/,"");return e.length<7||e.length>8?null:patterns.bodyDigits.test(e)?e:null},VERIFIER_BY_CHECK_DIGIT={1:"1",2:"2",3:"3",4:"4",5:"5",6:"6",7:"7",8:"8",9:"9",10:"K",11:"0"},calculateVerifierForBody=t=>{let e=0,r=2;for(let n=t.length-1;n>=0;n-=1)e+=(t.charCodeAt(n)-48)*r,r=7===r?2:r+1;return VERIFIER_BY_CHECK_DIGIT[11-e%11]},isSuspicious=t=>{const e=t[0];for(let r=1;r<t.length;r+=1)if(t[r]!==e)return!1;return!0},randomIntInclusive=(t,e)=>{const r=e-t+1,n=globalThis.crypto;if(null==n?void 0:n.getRandomValues){const e=Math.floor(4294967296/r)*r,l=new Uint32Array(1);let i=0;do{n.getRandomValues(l),i=l[0]}while(i>=e);return t+i%r}return t+Math.floor(Math.random()*r)};function clean(t,e){var r;const n=null===(r=null==e?void 0:e.throwOnError)||void 0===r||r;if(!isBoundedString(t))return fail(t,n);const l=normalizeRutValue(t);return isCleanRut(l)?l:fail(t,n)}function getBody(t,e){var r;const n=clean(t,withThrowOption(null==e?void 0:e.throwOnError));return null!==(r=null==n?void 0:n.slice(0,-1))&&void 0!==r?r:null}function getVerifier(t,e){const r=clean(t,withThrowOption(null==e?void 0:e.throwOnError));if(null===r)return null;const n=r.slice(-1);return isVerifierDigit(n)?n:null}function decompose(t,e){const r=withThrowOption(null==e?void 0:e.throwOnError),n=getBody(t,r),l=getVerifier(t,r);return null===n||null===l?null:{body:n,verifier:l}}const isRutLike=t=>null!==parseRutLike(t);function calculateVerifier(t,e){const r=withThrowOption(null==e?void 0:e.throwOnError),n=normalizeRutBody(t);return null===n?fail(t,r.throwOnError):calculateVerifierForBody(n)}exports.isRutLike=isRutLike;const validate=(t,e)=>{const r=parseRutLike(t);return!!r&&((!(null==e?void 0:e.strict)||!isSuspicious(r.body))&&calculateVerifierForBody(r.body)===r.verifier)};function format(t,e){var r,n,l;const i={incremental:null!==(r=null==e?void 0:e.incremental)&&void 0!==r&&r,dots:null===(n=null==e?void 0:e.dots)||void 0===n||n,throwOnError:null===(l=null==e?void 0:e.throwOnError)||void 0===l||l};if("string"!=typeof t)return fail(t,i.throwOnError);if(0===t.length)return"";if(i.incremental){const e=cleanRaw(t);if(0===e.length)return"";if(e.length<MIN_RUT_LENGTH){if(!i.dots||e.length<=3)return e;let t=e.slice(-3);for(let r=3;r<e.length;r+=3){const n=e.length-3-r<0?0:e.length-3-r;t=e.slice(n,e.length-r)+"."+t}return t}let r=e.slice(-4,-1)+"-"+e.slice(-1);for(let t=4;t<e.length;t+=3){const n=e.length-3-t<0?0:e.length-3-t;r=i.dots?e.slice(n,e.length-t)+"."+r:e.slice(n,e.length-t)+r}return r}const o=clean(t,withThrowOption(i.throwOnError));if(null===o)return null;const s=o.slice(0,-1),u=o.slice(-1);if(calculateVerifierForBody(s)!==u)return fail(t,i.throwOnError);if(i.dots){let t=o.slice(-4,-1)+"-"+o.substring(o.length-1);for(let e=4;e<o.length;e+=3)t=o.slice(-3-e,-e)+"."+t;return t}return o.slice(0,-1)+"-"+o.substring(o.length-1)}exports.validate=validate;const generate=()=>{let t=randomIntInclusive(1e7,99999999).toString();for(;isSuspicious(t);)t=randomIntInclusive(1e7,99999999).toString();return format(t+calculateVerifierForBody(t))};exports.generate=generate;
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -14,9 +14,10 @@ type ValidateOptions = {
|
|
|
14
14
|
strict?: boolean;
|
|
15
15
|
};
|
|
16
16
|
type VerifierDigit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | 'K';
|
|
17
|
-
export declare const getInvalidRutError: (
|
|
17
|
+
export declare const getInvalidRutError: (_rut?: unknown) => string;
|
|
18
18
|
/**
|
|
19
19
|
* Cleans the input string by removing leading zeros, non-numeric characters, and ensures the RUT is uppercased.
|
|
20
|
+
* This is a permissive normalization helper and does not validate the verifier digit.
|
|
20
21
|
* @param {string} rut - The RUT string to clean.
|
|
21
22
|
* @param {SafeOptions} [options] - Configuration options.
|
|
22
23
|
* @param {boolean} [options.throwOnError=true] - If true (default), throws an error for invalid RUTs. If false, returns null.
|
|
@@ -84,10 +85,10 @@ declare function decompose(rut: string, options?: SafeOptions): DecomposedRut |
|
|
|
84
85
|
/**
|
|
85
86
|
* Checks if a string has a valid RUT format (without validating the verifier digit).
|
|
86
87
|
* Useful for quick format validation in UI before full validation.
|
|
87
|
-
* @param {
|
|
88
|
+
* @param {unknown} rut - The value to check.
|
|
88
89
|
* @returns {boolean} True if the string looks like a RUT format, false otherwise.
|
|
89
90
|
*/
|
|
90
|
-
declare const isRutLike: (rut:
|
|
91
|
+
declare const isRutLike: (rut: unknown) => boolean;
|
|
91
92
|
/**
|
|
92
93
|
* Calculates the verifier digit for a given RUT body.
|
|
93
94
|
* @param {string} rutBody - The body of the RUT for which to calculate the verifier.
|
|
@@ -138,6 +139,7 @@ declare function format(rut: string, options: FormatOptions & {
|
|
|
138
139
|
declare function format(rut: string, options?: FormatOptions): string | null;
|
|
139
140
|
/**
|
|
140
141
|
* Generates a random valid RUT string.
|
|
142
|
+
* Uses Web Crypto when available, and falls back to Math.random in older runtimes.
|
|
141
143
|
* @returns {string} A randomly generated, valid RUT string.
|
|
142
144
|
*/
|
|
143
145
|
declare const generate: () => string;
|
package/dist/esm/index.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const getInvalidRutError=
|
|
1
|
+
export const getInvalidRutError=t=>"Invalid RUT input";const withThrowOption=t=>({throwOnError:null==t||t}),MIN_RUT_LENGTH=8,MAX_RUT_LENGTH=9,MIN_BODY_LENGTH=7,MAX_BODY_LENGTH=8,MAX_RUT_INPUT_LENGTH=64,MIN_GENERATED_BODY=1e7,MAX_GENERATED_BODY=99999999,UINT32_RANGE=4294967296,patterns={compact:/^0*\d{7,8}[\dkK]$/,compactWithHyphen:/^0*\d{7,8}-[\dkK]$/,dotted:/^0*\d{1,3}\.\d{3}\.\d{3}-?[\dkK]$/,invalidRutChars:/[^0-9kK]+/g,bodySeparators:/[.\-\s]+/g,bodyDigits:/^\d+$/},fail=(t,e)=>{if(e)throw new Error(getInvalidRutError());return null},isBoundedString=t=>"string"==typeof t&&t.length>0&&t.length<=64,normalizeRutValue=t=>t.replace(patterns.invalidRutChars,"").replace(/^0+/,"").toUpperCase(),isCleanRut=t=>{if(t.length<8||t.length>9)return!1;const e=t.slice(0,-1),r=t.slice(-1);return e.length>=7&&e.length<=8&&patterns.bodyDigits.test(e)&&/^[\dK]$/.test(r)},isVerifierDigit=t=>/^[\dK]$/.test(t),parseRutLike=t=>{if(!isBoundedString(t))return null;const e=t.trim();if(0===e.length)return null;if(!(patterns.compact.test(e)||patterns.compactWithHyphen.test(e)||patterns.dotted.test(e)))return null;const r=normalizeRutValue(e);return isCleanRut(r)?{body:r.slice(0,-1),verifier:r.slice(-1)}:null},cleanRaw=t=>{const e=normalizeRutValue(t.slice(0,64)),r=e.replace(/K/g,"");return(e.endsWith("K")?`${r}K`:r).slice(0,9)},normalizeRutBody=t=>{if(!isBoundedString(t))return null;const e=t.replace(patterns.bodySeparators,"").replace(/^0+/,"");return e.length<7||e.length>8?null:patterns.bodyDigits.test(e)?e:null},VERIFIER_BY_CHECK_DIGIT={1:"1",2:"2",3:"3",4:"4",5:"5",6:"6",7:"7",8:"8",9:"9",10:"K",11:"0"},calculateVerifierForBody=t=>{let e=0,r=2;for(let n=t.length-1;n>=0;n-=1)e+=(t.charCodeAt(n)-48)*r,r=7===r?2:r+1;return VERIFIER_BY_CHECK_DIGIT[11-e%11]},isSuspicious=t=>{const e=t[0];for(let r=1;r<t.length;r+=1)if(t[r]!==e)return!1;return!0},randomIntInclusive=(t,e)=>{const r=e-t+1,n=globalThis.crypto;if(null==n?void 0:n.getRandomValues){const e=Math.floor(4294967296/r)*r,l=new Uint32Array(1);let i=0;do{n.getRandomValues(l),i=l[0]}while(i>=e);return t+i%r}return t+Math.floor(Math.random()*r)};function clean(t,e){var r;const n=null===(r=null==e?void 0:e.throwOnError)||void 0===r||r;if(!isBoundedString(t))return fail(0,n);const l=normalizeRutValue(t);return isCleanRut(l)?l:fail(0,n)}function getBody(t,e){var r;const n=clean(t,withThrowOption(null==e?void 0:e.throwOnError));return null!==(r=null==n?void 0:n.slice(0,-1))&&void 0!==r?r:null}function getVerifier(t,e){const r=clean(t,withThrowOption(null==e?void 0:e.throwOnError));if(null===r)return null;const n=r.slice(-1);return isVerifierDigit(n)?n:null}function decompose(t,e){const r=withThrowOption(null==e?void 0:e.throwOnError),n=getBody(t,r),l=getVerifier(t,r);return null===n||null===l?null:{body:n,verifier:l}}const isRutLike=t=>null!==parseRutLike(t);function calculateVerifier(t,e){const r=withThrowOption(null==e?void 0:e.throwOnError),n=normalizeRutBody(t);return null===n?fail(0,r.throwOnError):calculateVerifierForBody(n)}const validate=(t,e)=>{const r=parseRutLike(t);return!!r&&((!(null==e?void 0:e.strict)||!isSuspicious(r.body))&&calculateVerifierForBody(r.body)===r.verifier)};function format(t,e){var r,n,l;const i={incremental:null!==(r=null==e?void 0:e.incremental)&&void 0!==r&&r,dots:null===(n=null==e?void 0:e.dots)||void 0===n||n,throwOnError:null===(l=null==e?void 0:e.throwOnError)||void 0===l||l};if("string"!=typeof t)return fail(0,i.throwOnError);if(0===t.length)return"";if(i.incremental){const e=cleanRaw(t);if(0===e.length)return"";if(e.length<8){if(!i.dots||e.length<=3)return e;let t=e.slice(-3);for(let r=3;r<e.length;r+=3){const n=e.length-3-r<0?0:e.length-3-r;t=e.slice(n,e.length-r)+"."+t}return t}let r=e.slice(-4,-1)+"-"+e.slice(-1);for(let t=4;t<e.length;t+=3){const n=e.length-3-t<0?0:e.length-3-t;r=i.dots?e.slice(n,e.length-t)+"."+r:e.slice(n,e.length-t)+r}return r}const o=clean(t,withThrowOption(i.throwOnError));if(null===o)return null;const u=o.slice(0,-1),s=o.slice(-1);if(calculateVerifierForBody(u)!==s)return fail(0,i.throwOnError);if(i.dots){let t=o.slice(-4,-1)+"-"+o.substring(o.length-1);for(let e=4;e<o.length;e+=3)t=o.slice(-3-e,-e)+"."+t;return t}return o.slice(0,-1)+"-"+o.substring(o.length-1)}const generate=()=>{let t=randomIntInclusive(1e7,99999999).toString();for(;isSuspicious(t);)t=randomIntInclusive(1e7,99999999).toString();return format(t+calculateVerifierForBody(t))};export{validate,clean,format,calculateVerifier,getBody,getVerifier,decompose,generate,isRutLike};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rut.ts",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Handle chilean RUT values with ease.",
|
|
6
6
|
"author": "arrowsw",
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"lint": "eslint \"{**/*,*}.{js,ts,jsx,tsx}\"",
|
|
29
29
|
"prettier": "prettier --write \"{src,tests,example/src}/**/*.{js,ts,jsx,tsx}\"",
|
|
30
30
|
"test": "jest --config jest.config.cjs",
|
|
31
|
+
"test:differential": "RUN_DIFFERENTIAL=1 jest --config jest.config.cjs tests/differential.test.ts",
|
|
31
32
|
"prepare": "npm run build",
|
|
32
33
|
"prepublishOnly": "npm run test && npm run prettier && npm run lint",
|
|
33
34
|
"minify:esm": "terser dist/esm/index.js -o dist/esm/index.min.js -c -m",
|
|
@@ -49,7 +50,8 @@
|
|
|
49
50
|
"prettier": "3.8.1",
|
|
50
51
|
"terser": "5.46.0",
|
|
51
52
|
"ts-jest": "29.4.6",
|
|
52
|
-
"typescript": "5.9.3"
|
|
53
|
+
"typescript": "5.9.3",
|
|
54
|
+
"typescript-eslint": "8.53.1"
|
|
53
55
|
},
|
|
54
56
|
"keywords": [
|
|
55
57
|
"validation",
|
|
@@ -64,7 +66,7 @@
|
|
|
64
66
|
"deconstruct"
|
|
65
67
|
],
|
|
66
68
|
"repository": {
|
|
67
|
-
"url": "https://github.com/
|
|
69
|
+
"url": "https://github.com/arrowsw/rut.ts.git",
|
|
68
70
|
"type": "git"
|
|
69
71
|
},
|
|
70
72
|
"files": [
|
|
@@ -74,4 +76,4 @@
|
|
|
74
76
|
"LICENSE",
|
|
75
77
|
"README.md"
|
|
76
78
|
]
|
|
77
|
-
}
|
|
79
|
+
}
|