us-ssn-tools 1.0.0 β†’ 2.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 CHANGED
@@ -4,16 +4,18 @@ A small, well-tested TypeScript library for **working with U.S. Social Security
4
4
 
5
5
  It provides:
6
6
 
7
- * βœ… **Validation** (strict + typing-as-you-go)
8
- * πŸ” **Normalization** (canonical `###-##-####` formatting)
9
- * 🎭 **Masking** (UI-safe, best-effort, privacy-first)
7
+ * βœ… **Validation** (strict + typing-as-you-go, boolean API)
8
+ * πŸ” **Normalization** (deterministic, UI-friendly formatting)
9
+ * 🎭 **Masking** (privacy-first, best-effort, never validates)
10
10
  * 🎲 **Generation** (pre-2011, post-2011, random, and publicly advertised SSNs)
11
11
  * 🧩 **Zod & Yup adapters** for form validation
12
12
 
13
- > **Design principle:**
14
- > Validation enforces rules.
15
- > Masking never leaks data.
16
- > Normalization is deterministic and UI-friendly.
13
+ > **Design principles**
14
+ >
15
+ > * Validation answers *β€œis this valid?”* β€” nothing more.
16
+ > * Normalization formats input for display and typing UX.
17
+ > * Masking never leaks digits and never enforces validity.
18
+ > * Public safety beats convenience.
17
19
 
18
20
  ---
19
21
 
@@ -35,8 +37,8 @@ yarn add us-ssn-tools
35
37
 
36
38
  ```ts
37
39
  import {
38
- validateSsn,
39
- normalizeSsnInput,
40
+ isValidSsn,
41
+ normaliseSsn,
40
42
  formatSsnFromDigits,
41
43
  maskSsn,
42
44
  generateSsn,
@@ -50,81 +52,86 @@ import { yupSsnTyping, yupSsnSubmit } from "us-ssn-tools/yup";
50
52
 
51
53
  ## Validation
52
54
 
53
- ### `validateSsn(input, options)`
55
+ ### `isValidSsn(input, options): boolean`
54
56
 
55
- Validates an SSN according to U.S. rules.
57
+ Checks whether an SSN is valid according to U.S. rules.
58
+
59
+ * Returns **`true` or `false` only**
60
+ * Does **not** normalize
61
+ * Can validate *partial input* (β€œvalid so far”)
56
62
 
57
63
  ```ts
58
- const result = validateSsn("123-45-6789");
59
-
60
- if (result.ok) {
61
- console.log(result.normalized); // "123-45-6789"
62
- } else {
63
- console.log(result.error); // e.g. "INVALID_AREA"
64
- console.log(result.message); // human-readable explanation
65
- }
64
+ isValidSsn("123-45-6789"); // true
65
+ isValidSsn("123456789"); // false (dashes required by default)
66
66
  ```
67
67
 
68
68
  ### Options
69
69
 
70
70
  ```ts
71
71
  type ValidateSsnOptions = {
72
- allowNoDashes?: boolean; // default true
73
- allowPartial?: boolean; // default false
74
- ruleMode?: "pre2011" | "post2011" | "both"; // default "both"
72
+ requireDashes?: boolean; // default: true
73
+ allowPartial?: boolean; // default: false
74
+ ruleMode?: "pre2011" | "post2011"; // default: "post2011"
75
75
  };
76
76
  ```
77
77
 
78
- #### Examples
78
+ ### Examples
79
79
 
80
80
  ```ts
81
- validateSsn("123456789"); // ok (normalized to "123-45-6789")
81
+ isValidSsn("9", { allowPartial: true });
82
+ // true (still potentially valid)
82
83
 
83
- validateSsn("9", { allowPartial: true });
84
- // ❌ INVALID_AREA (impossible prefix)
84
+ isValidSsn("900", { allowPartial: true });
85
+ // false (invalid area)
85
86
 
86
- validateSsn("773-12-3456", { ruleMode: "pre2011" });
87
- // ❌ INVALID_AREA
87
+ isValidSsn("773-12-3456", { ruleMode: "pre2011" });
88
+ // false
88
89
 
89
- validateSsn("773-12-3456", { ruleMode: "post2011" });
90
- // βœ… ok
90
+ isValidSsn("773-12-3456", { ruleMode: "post2011" });
91
+ // true
91
92
  ```
92
93
 
93
94
  ---
94
95
 
95
96
  ## Normalization
96
97
 
97
- ### `normalizeSsnInput(input, options)`
98
+ ### `normaliseSsn(input, options): string`
98
99
 
99
- Parses and formats SSNs **without enforcing full validity**.
100
- Ideal for **typing-as-you-go**.
100
+ Formats input for **UI display**.
101
+ It does **not validate** and never throws.
101
102
 
102
- ```ts
103
- const res = normalizeSsnInput("1234", { allowPartial: true });
103
+ * Extracts digits
104
+ * Optionally inserts dashes
105
+ * Supports typing-as-you-go
106
+ * Allows overflow digits if desired
104
107
 
105
- if (res.ok) {
106
- res.digits; // "1234"
107
- res.normalized; // "123-4"
108
- }
108
+ ```ts
109
+ normaliseSsn("1234"); // "123-4"
110
+ normaliseSsn("123456789"); // "123-45-6789"
111
+ normaliseSsn("SSN: 12a3"); // "123"
109
112
  ```
110
113
 
111
114
  ### Options
112
115
 
113
116
  ```ts
114
- type NormalizeSsnOptions = {
115
- allowPartial?: boolean;
116
- allowNoDashes?: boolean;
117
+ type NormaliseSsnOptions = {
118
+ allowPartial?: boolean; // default: true
119
+ digitsOnly?: boolean; // default: false
120
+ enforceLength?: boolean; // default: false
117
121
  };
118
122
  ```
119
123
 
120
124
  ### Examples
121
125
 
122
126
  ```ts
123
- normalizeSsnInput("123456789");
124
- // β†’ { digits: "123456789", normalized: "123-45-6789" }
127
+ normaliseSsn("123456789", { digitsOnly: true });
128
+ // "123456789"
129
+
130
+ normaliseSsn("12345678999");
131
+ // "123-45-678999"
125
132
 
126
- normalizeSsnInput("123-45-6", { allowPartial: true });
127
- // β†’ { digits: "123456", normalized: "123-45-6" }
133
+ normaliseSsn("12345678999", { enforceLength: true });
134
+ // "123-45-6789"
128
135
  ```
129
136
 
130
137
  ---
@@ -134,7 +141,6 @@ normalizeSsnInput("123-45-6", { allowPartial: true });
134
141
  ### `formatSsnFromDigits(digits)`
135
142
 
136
143
  Formats a **digit string** into SSN shape.
137
- This function **does not validate**.
138
144
 
139
145
  ```ts
140
146
  formatSsnFromDigits("123"); // "123"
@@ -142,16 +148,20 @@ formatSsnFromDigits("1234"); // "123-4"
142
148
  formatSsnFromDigits("123456789"); // "123-45-6789"
143
149
  ```
144
150
 
145
- Used internally by normalization and masking, but safe to use directly for UI.
151
+ No validation is performed.
146
152
 
147
153
  ---
148
154
 
149
155
  ## Masking (UI-safe)
150
156
 
151
- ### `maskSsn(input, options)`
157
+ ### `maskSsn(input, options): string`
158
+
159
+ Masks digits after normalization.
152
160
 
153
- Masks digits while **never masking dashes**.
154
- Designed for **privacy-safe UI rendering**, not validation.
161
+ * Always normalizes first
162
+ * Never validates
163
+ * Never reveals digits unless explicitly allowed
164
+ * Safe for **any** UI context
155
165
 
156
166
  ```ts
157
167
  maskSsn("123-45-6789");
@@ -165,87 +175,80 @@ maskSsn("123-45-6789", { revealLast4: true });
165
175
 
166
176
  ```ts
167
177
  type MaskSsnOptions = {
168
- allowPartial?: boolean; // default false
169
- revealLast4?: boolean; // default false
170
- maskChar?: string; // default "*"
171
- dashMode?: "normalize" | "preserve"; // default "normalize"
172
- allowNoDashes?: boolean; // default true
178
+ allowPartial?: boolean; // default: true
179
+ revealLast4?: boolean; // default: false
180
+ maskChar?: string; // default: "*"
181
+ digitsOnly?: boolean; // default: false
182
+ enforceLength?: boolean; // default: false
173
183
  };
174
184
  ```
175
185
 
176
- ### Partial / typing behavior
186
+ ### Typing behavior
177
187
 
178
188
  ```ts
179
- maskSsn("123", { allowPartial: true });
189
+ maskSsn("123");
180
190
  // "***"
181
191
 
182
- maskSsn("123-45-6", { allowPartial: true, revealLast4: true });
192
+ maskSsn("123-45-6", { revealLast4: true });
183
193
  // "***-**-6"
184
194
  ```
185
195
 
186
- ### Important guarantees
196
+ ### Guarantees
187
197
 
188
- * βœ”οΈ **Digits are always masked** (even on invalid input)
198
+ * βœ”οΈ Digits are **always masked**
189
199
  * βœ”οΈ Dashes are never masked
190
- * βœ”οΈ No validation required β€” safe for UI display
200
+ * βœ”οΈ Invalid input is still safely masked
201
+ * βœ”οΈ No validation logic inside masking
191
202
 
192
203
  ---
193
204
 
194
205
  ## Generation
195
206
 
196
- ### `generateSsn(options)`
197
- Absolutely β€” that’s an important point, and it’s worth stating **clearly but professionally**, without sounding alarmist.
207
+ ### `generateSsn(options): string`
198
208
 
199
- Here’s a **polished replacement** for the **Generation** section note that you can drop straight into the README.
209
+ Generates SSNs for testing and development.
200
210
 
201
- ---
202
-
203
- ## Generation
211
+ ```ts
212
+ generateSsn();
213
+ // one of the publicly advertised SSNs
214
+ ```
204
215
 
205
- Generates realistic-looking SSNs for testing, demos, and development.
216
+ ### Options
206
217
 
207
218
  ```ts
208
- generateSsn();
209
- // e.g. "509-21-4837"
219
+ type GenerateSsnOptions = {
220
+ mode?: "public" | "pre2011" | "post2011" | "any"; // default: "public"
221
+ digitsOnly?: boolean; // default: false
222
+ };
210
223
  ```
211
224
 
212
225
  ### ⚠️ Important note on public usage
213
226
 
214
227
  > **Only use `mode: "public"` for any SSNs that may be displayed publicly.**
215
228
 
216
- SSNs generated with `"pre2011"`, `"post2011"`, or `"any"` modes are **syntactically valid** and may correspond to **real individuals**.
217
-
218
- If such a value is:
229
+ SSNs generated with `"pre2011"`, `"post2011"`, or `"any"` are **syntactically valid** and may correspond to **real individuals**.
219
230
 
220
- * rendered in documentation
221
- * shown in screenshots
222
- * logged to public consoles
223
- * included in sample data or demos
231
+ If such values are:
224
232
 
225
- you risk **exposing a real person to identity theft** if the generated SSN happens to match an existing one.
233
+ * shown in documentation
234
+ * used in screenshots
235
+ * logged publicly
236
+ * included in demos or examples
226
237
 
227
- To prevent this, the library includes a special `"public"` generation mode that returns **historically documented, non-random, publicly advertised SSNs** that are known to be unsafe for real-world use but safe for examples.
238
+ you risk **exposing a real person to identity theft**.
228
239
 
229
- ### Recommended usage
240
+ The `"public"` mode returns **historically documented, publicly advertised SSNs** that are known to be unsafe for real-world use but safe for examples.
230
241
 
231
242
  ```ts
232
- // βœ… Safe for docs, demos, screenshots, and public output
243
+ // βœ… Safe for docs, demos, screenshots
233
244
  generateSsn({ mode: "public" });
234
245
 
235
- // ❌ Do NOT use in any public or user-visible context
246
+ // ❌ Never display publicly
236
247
  generateSsn({ mode: "any" });
237
248
  generateSsn({ mode: "pre2011" });
238
249
  generateSsn({ mode: "post2011" });
239
250
  ```
240
251
 
241
- Use non-public modes **only** for:
242
-
243
- * internal testing
244
- * private development environments
245
- * automated test data that is never exposed
246
-
247
- This distinction exists to protect real people, not just to satisfy validation rules.
248
-
249
252
  ---
250
253
 
251
254
  ## Zod Adapters
@@ -256,7 +259,7 @@ This distinction exists to protect real people, not just to satisfy validation r
256
259
  const schema = zodSsnTyping();
257
260
 
258
261
  schema.parse("1234"); // "123-4"
259
- schema.parse("9"); // ❌ throws (impossible prefix)
262
+ schema.parse("900"); // ❌ throws
260
263
  ```
261
264
 
262
265
  ### Submit (strict)
@@ -284,14 +287,14 @@ await yupSsnSubmit().validate("123456789");
284
287
 
285
288
  ## Testing & Guarantees
286
289
 
287
- * βœ”οΈ 100% table-driven Jest tests
288
- * βœ”οΈ Deterministic RNG support
289
- * βœ”οΈ Strict separation between:
290
+ * βœ”οΈ Extensive table-driven Jest tests
291
+ * βœ”οΈ Clear separation between:
290
292
 
291
293
  * validation
292
- * formatting
294
+ * normalization
293
295
  * masking
294
296
  * generation
297
+ * βœ”οΈ UI-safe defaults everywhere
295
298
 
296
299
  ---
297
300
 
@@ -299,7 +302,7 @@ await yupSsnSubmit().validate("123456789");
299
302
 
300
303
  * ❌ No storage or encryption
301
304
  * ❌ No non-US SSNs (yet)
302
- * ❌ No implicit trimming or mutation of user input
305
+ * ❌ No mutation of user input beyond formatting
303
306
 
304
307
  ---
305
308
 
@@ -307,13 +310,15 @@ await yupSsnSubmit().validate("123456789");
307
310
 
308
311
  ISC
309
312
 
313
+ ---
314
+
310
315
  ## Support This Project
311
316
 
312
- If you find this project useful, consider supporting me to help keep it maintained and improved:
317
+ If you find this project useful, consider supporting it:
313
318
 
314
- - [Sponsor on GitHub](https://github.com/sponsors/backupbrain)
315
- - [Buy Me a Coffee](https://www.buymeacoffee.com/backupbrain)
316
- - [Ko-fi](https://ko-fi.com/backupbrain)
317
- - [Thanks.dev](https://thanks.dev/u/gh/backupbrain)
319
+ * [Sponsor on GitHub](https://github.com/sponsors/backupbrain)
320
+ * [Buy Me a Coffee](https://www.buymeacoffee.com/backupbrain)
321
+ * [Ko-fi](https://ko-fi.com/backupbrain)
322
+ * [Thanks.dev](https://thanks.dev/u/gh/backupbrain)
318
323
 
319
- Your support is greatly appreciated!
324
+ Your support is greatly appreciated πŸ™
@@ -2,22 +2,18 @@ export type GenerateSsnMode = 'pre2011' | 'post2011' | 'any' | 'public';
2
2
  export interface GenerateSsnOptions {
3
3
  /**
4
4
  * What kind of SSN to generate.
5
- * - "any" (default): may produce either pre2011-valid or post2011-valid
5
+ * - "public" (default): returns one of the publicly-advertised SSNs (intentionally invalid)
6
+ * - "any": may produce either pre2011-valid or post2011-valid
6
7
  * - "pre2011": uses stricter pre-2011 area rules
7
8
  * - "post2011": uses post-2011 relaxed area rules
8
- * - "public": returns one of the publicly-advertised SSNs (intentionally invalid)
9
9
  */
10
10
  mode?: GenerateSsnMode;
11
- /** Output format (default: "dashed") */
12
- format?: 'dashed' | 'digits';
13
11
  /**
14
- * Optional RNG injection for determinism in tests.
15
- * Must return a float in [0, 1).
12
+ * If true, output is digits-only (#########).
13
+ * If false, output is dashed (###-##-####).
14
+ *
15
+ * Default: false
16
16
  */
17
- rng?: () => number;
18
- /**
19
- * For mode="public": choose a specific advertised SSN, otherwise random.
20
- */
21
- publicValue?: '078-05-1120' | '721-07-4426' | '219-09-9999';
17
+ digitsOnly?: boolean;
22
18
  }
23
19
  export declare function generateSsn(opts?: GenerateSsnOptions): string;
package/dist/generate.js CHANGED
@@ -7,28 +7,27 @@ const PUBLICLY_ADVERTISED = [
7
7
  '219-09-9999',
8
8
  ];
9
9
  function generateSsn(opts = {}) {
10
- var _a, _b, _c, _d;
11
- const mode = (_a = opts.mode) !== null && _a !== void 0 ? _a : 'any';
12
- const format = (_b = opts.format) !== null && _b !== void 0 ? _b : 'dashed';
13
- const rng = (_c = opts.rng) !== null && _c !== void 0 ? _c : defaultRng;
10
+ var _a, _b;
11
+ const mode = (_a = opts.mode) !== null && _a !== void 0 ? _a : 'public';
12
+ const digitsOnly = (_b = opts.digitsOnly) !== null && _b !== void 0 ? _b : false;
14
13
  if (mode === 'public') {
15
- const chosen = (_d = opts.publicValue) !== null && _d !== void 0 ? _d : PUBLICLY_ADVERTISED[randomInt(rng, 0, PUBLICLY_ADVERTISED.length - 1)];
16
- return format === 'digits' ? chosen.replace(/-/g, '') : chosen;
14
+ const chosen = PUBLICLY_ADVERTISED[randomInt(0, PUBLICLY_ADVERTISED.length - 1)];
15
+ return digitsOnly ? chosen.replace(/-/g, '') : chosen;
17
16
  }
18
- // "any" => randomly choose pre or post
19
- const effectiveMode = mode === 'any' ? (rng() < 0.5 ? 'pre2011' : 'post2011') : mode;
20
- const area = generateArea(effectiveMode, rng);
21
- const group = generateNonZeroFixedWidth(rng, 2); // 01..99 (not 00)
22
- const serial = generateNonZeroFixedWidth(rng, 4); // 0001..9999 (not 0000)
23
- const dashed = `${area}-${group}-${serial}`;
24
- // Avoid accidentally returning a "publicly advertised" SSN unless explicitly requested.
25
- if (PUBLICLY_ADVERTISED.includes(dashed)) {
26
- return generateSsn(Object.assign(Object.assign({}, opts), { mode })); // retry (very unlikely)
17
+ const effectiveMode = mode === 'any' ? (randomFloat() < 0.5 ? 'pre2011' : 'post2011') : mode;
18
+ while (true) {
19
+ const area = generateArea(effectiveMode); // "001".."899" with constraints
20
+ const group = generateNonZeroFixedWidth(2); // "01".."99"
21
+ const serial = generateNonZeroFixedWidth(4); // "0001".."9999"
22
+ const dashed = `${area}-${group}-${serial}`;
23
+ // Avoid returning publicly advertised SSNs unless explicitly requested.
24
+ if (PUBLICLY_ADVERTISED.includes(dashed))
25
+ continue;
26
+ return digitsOnly ? dashed.replace(/-/g, '') : dashed;
27
27
  }
28
- return format === 'digits' ? dashed.replace(/-/g, '') : dashed;
29
28
  }
30
29
  /* ---------------------------- helpers ---------------------------- */
31
- function generateArea(mode, rng) {
30
+ function generateArea(mode) {
32
31
  // Base rules always:
33
32
  // - not 000
34
33
  // - not 666
@@ -40,7 +39,7 @@ function generateArea(mode, rng) {
40
39
  //
41
40
  // Strategy: generate until it passes constraints (fast, tiny rejection rate).
42
41
  while (true) {
43
- const n = randomInt(rng, 1, 899); // 001..899
42
+ const n = randomInt(1, 899); // 001..899
44
43
  if (n === 666)
45
44
  continue;
46
45
  if (mode === 'pre2011') {
@@ -52,28 +51,24 @@ function generateArea(mode, rng) {
52
51
  return pad3(n);
53
52
  }
54
53
  }
55
- function generateNonZeroFixedWidth(rng, width) {
54
+ function generateNonZeroFixedWidth(width) {
56
55
  if (width === 2) {
57
- // 01..99
58
- const n = randomInt(rng, 1, 99);
56
+ const n = randomInt(1, 99); // 01..99
59
57
  return n.toString().padStart(2, '0');
60
58
  }
61
- // 0001..9999
62
- const n = randomInt(rng, 1, 9999);
59
+ const n = randomInt(1, 9999); // 0001..9999
63
60
  return n.toString().padStart(4, '0');
64
61
  }
65
62
  function pad3(n) {
66
63
  return n.toString().padStart(3, '0');
67
64
  }
68
- function randomInt(rng, min, max) {
65
+ function randomInt(min, max) {
69
66
  // inclusive min/max
70
- const r = rng();
71
- return Math.floor(r * (max - min + 1)) + min;
67
+ return Math.floor(randomFloat() * (max - min + 1)) + min;
72
68
  }
73
- function defaultRng() {
69
+ function randomFloat() {
74
70
  var _a;
75
71
  // Prefer crypto when available; fall back to Math.random.
76
- // Works in browsers; in Node 19+ crypto.getRandomValues exists on globalThis.crypto.
77
72
  const g = globalThis;
78
73
  if ((_a = g.crypto) === null || _a === void 0 ? void 0 : _a.getRandomValues) {
79
74
  const buf = new Uint32Array(1);
package/dist/index.d.ts CHANGED
@@ -1,5 +1,4 @@
1
- export { validateSsn } from './validate';
2
- export { normalizeSsnInput } from './normalize';
1
+ export { isValidSsn } from './validate';
2
+ export { normalizeSsn } from './normalize';
3
3
  export { maskSsn } from './mask';
4
- export { formatSsnFromDigits } from './utils';
5
4
  export { generateSsn } from './generate';
package/dist/index.js CHANGED
@@ -1,14 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.generateSsn = exports.formatSsnFromDigits = exports.maskSsn = exports.normalizeSsnInput = exports.validateSsn = void 0;
3
+ exports.generateSsn = exports.maskSsn = exports.normalizeSsn = exports.isValidSsn = void 0;
4
4
  // Exporting all functions
5
5
  var validate_1 = require("./validate");
6
- Object.defineProperty(exports, "validateSsn", { enumerable: true, get: function () { return validate_1.validateSsn; } });
6
+ Object.defineProperty(exports, "isValidSsn", { enumerable: true, get: function () { return validate_1.isValidSsn; } });
7
7
  var normalize_1 = require("./normalize");
8
- Object.defineProperty(exports, "normalizeSsnInput", { enumerable: true, get: function () { return normalize_1.normalizeSsnInput; } });
8
+ Object.defineProperty(exports, "normalizeSsn", { enumerable: true, get: function () { return normalize_1.normalizeSsn; } });
9
9
  var mask_1 = require("./mask");
10
10
  Object.defineProperty(exports, "maskSsn", { enumerable: true, get: function () { return mask_1.maskSsn; } });
11
- var utils_1 = require("./utils");
12
- Object.defineProperty(exports, "formatSsnFromDigits", { enumerable: true, get: function () { return utils_1.formatSsnFromDigits; } });
13
11
  var generate_1 = require("./generate");
14
12
  Object.defineProperty(exports, "generateSsn", { enumerable: true, get: function () { return generate_1.generateSsn; } });
package/dist/mask.d.ts CHANGED
@@ -1,25 +1,33 @@
1
1
  export interface MaskSsnOptions {
2
- /** Allow masking partial SSNs (typing-as-you-go). */
2
+ /**
3
+ * Allow masking partial SSNs (typing-as-you-go).
4
+ * Default: true
5
+ */
3
6
  allowPartial?: boolean;
4
- /** If true, reveal last 4 digits when present (>= 4 digits typed). */
7
+ /**
8
+ * Reveal serial digits as they are typed:
9
+ * - before serial (<=5 digits): reveal nothing
10
+ * - serial (6..9 digits): reveal up to 4 digits
11
+ *
12
+ * Default: false
13
+ */
5
14
  revealLast4?: boolean;
6
- /** Mask character (default: "*"). Only first char is used. */
15
+ /**
16
+ * Mask character (default: "*").
17
+ * Only the first character is used.
18
+ */
7
19
  maskChar?: string;
8
- /** Accept digit-only input (default: true). */
9
- allowNoDashes?: boolean;
10
20
  /**
11
- * Controls dash output:
12
- * - "preserve": keep dashes only if the input already contains '-'
13
- * - "normalize": always insert dashes in ###-##-#### style (even for digits-only)
14
- *
15
- * Default: "normalize"
21
+ * Output digits only (no dashes).
22
+ * Default: false
16
23
  */
17
- dashMode?: 'preserve' | 'normalize';
24
+ digitsOnly?: boolean;
18
25
  /**
19
- * If true (default), and allowPartial=true, invalid partial input will be masked
20
- * in a best-effort way (digits masked, dashes preserved).
21
- * If false, invalid input is returned unchanged.
26
+ * If true, cap to 9 digits (SSN length).
27
+ * If false, allow overflow digits (UI testing / paste scenarios).
28
+ *
29
+ * Default: false
22
30
  */
23
- bestEffortOnInvalidPartial?: boolean;
31
+ enforceLength?: boolean;
24
32
  }
25
33
  export declare function maskSsn(input: string, opts?: MaskSsnOptions): string;
package/dist/mask.js CHANGED
@@ -5,51 +5,26 @@ const normalize_1 = require("./normalize");
5
5
  const utils_1 = require("./utils");
6
6
  function maskSsn(input, opts = {}) {
7
7
  var _a, _b, _c, _d, _e;
8
- const allowPartial = (_a = opts.allowPartial) !== null && _a !== void 0 ? _a : false;
8
+ const allowPartial = (_a = opts.allowPartial) !== null && _a !== void 0 ? _a : true;
9
9
  const revealLast4 = (_b = opts.revealLast4) !== null && _b !== void 0 ? _b : false;
10
- const maskChar = ((_c = opts.maskChar) !== null && _c !== void 0 ? _c : '*').slice(0, 1) || '*';
11
- const allowNoDashes = (_d = opts.allowNoDashes) !== null && _d !== void 0 ? _d : true;
12
- const dashMode = (_e = opts.dashMode) !== null && _e !== void 0 ? _e : 'normalize';
13
- const hadDashes = input.includes('-');
14
- const normalized = (0, normalize_1.normalizeSsnInput)(input, { allowPartial, allowNoDashes });
15
- if (!normalized.ok) {
16
- // Masking is UI-safety, not validation: never return raw digits.
17
- // Best-effort: mask digits, keep '-' unmasked, keep any other characters as-is.
18
- const masked = maskBestEffort(input, { maskChar });
19
- // If caller wants normalized dashes, we can try to normalize by extracting digits
20
- // from the best-effort masked string is not possible (digits are already masked),
21
- // so we simply return the best-effort output.
22
- return masked;
23
- }
24
- const digits = normalized.digits; // 0..9 digits (partial) or 9 digits (full)
25
- // Determine how many digits to reveal (last 4) if present.
10
+ const digitsOnly = (_c = opts.digitsOnly) !== null && _c !== void 0 ? _c : false;
11
+ const enforceLength = (_d = opts.enforceLength) !== null && _d !== void 0 ? _d : false;
12
+ const maskChar = ((_e = opts.maskChar) !== null && _e !== void 0 ? _e : '*').slice(0, 1) || '*';
13
+ // 1) Normalize β†’ DIGITS ONLY
14
+ const digits = (0, normalize_1.normalizeSsn)(input, {
15
+ allowPartial,
16
+ digitsOnly: true,
17
+ enforceLength,
18
+ });
19
+ // 2) Mask
26
20
  const total = digits.length;
27
- // digits.length = total typed digits (0..9)
28
- const serialTyped = Math.max(0, total - 5); // digits 6..9 => 1..4
21
+ // Reveal only serial digits (positions 6–9)
22
+ const serialTyped = Math.max(0, total - 5);
29
23
  const revealCount = revealLast4 ? Math.min(4, serialTyped) : 0;
30
24
  const maskedCount = Math.max(0, total - revealCount);
31
25
  const maskedDigits = maskChar.repeat(maskedCount) + digits.slice(maskedCount);
32
- // Output formatting
33
- if (dashMode === 'normalize') {
34
- // Insert dashes as ###-##-#### prefix (even while typing).
35
- return (0, utils_1.formatSsnFromDigits)(maskedDigits);
36
- }
37
- // dashMode === "preserve"
38
- if (hadDashes) {
39
- // Input had dashes -> keep dashed presentation (normalized positions).
40
- return (0, utils_1.formatSsnFromDigits)(maskedDigits);
41
- }
42
- // Input had no dashes -> keep digits-only.
43
- return maskedDigits;
44
- }
45
- function maskBestEffort(input, opts) {
46
- const m = opts.maskChar;
47
- let out = '';
48
- for (const ch of input) {
49
- if (ch >= '0' && ch <= '9')
50
- out += m;
51
- else
52
- out += ch; // dashes and any other characters preserved
53
- }
54
- return out;
26
+ // 3) Output formatting
27
+ if (digitsOnly)
28
+ return maskedDigits;
29
+ return (0, utils_1.formatSsnWithOverflow)(maskedDigits);
55
30
  }