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 +108 -103
- package/dist/generate.d.ts +7 -11
- package/dist/generate.js +23 -28
- package/dist/index.d.ts +2 -3
- package/dist/index.js +3 -5
- package/dist/mask.d.ts +23 -15
- package/dist/mask.js +17 -42
- package/dist/normalize.d.ts +30 -12
- package/dist/normalize.js +34 -53
- package/dist/utils.d.ts +6 -1
- package/dist/utils.js +11 -1
- package/dist/validate.d.ts +13 -31
- package/dist/validate.js +120 -155
- package/dist/yup.d.ts +8 -3
- package/dist/yup.js +30 -19
- package/dist/zod.d.ts +10 -3
- package/dist/zod.js +34 -13
- package/package.json +1 -1
package/dist/normalize.d.ts
CHANGED
|
@@ -1,15 +1,33 @@
|
|
|
1
1
|
export interface NormalizeSsnOptions {
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* If true, formats prefixes while typing.
|
|
4
|
+
* - dashed mode: "1234" -> "123-4"
|
|
5
|
+
* - digits-only mode: returns digits as-is
|
|
6
|
+
*
|
|
7
|
+
* Default: true
|
|
8
|
+
*/
|
|
3
9
|
allowPartial?: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* If true, return digits only (no dashes inserted).
|
|
12
|
+
* If false, insert dashes in ###-##-#### style.
|
|
13
|
+
*
|
|
14
|
+
* Default: false
|
|
15
|
+
*/
|
|
16
|
+
digitsOnly?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* If true, cap extracted digits at 9.
|
|
19
|
+
* If false, allow any number of digits (useful for UI behavior testing).
|
|
20
|
+
*
|
|
21
|
+
* Default: true
|
|
22
|
+
*/
|
|
23
|
+
enforceLength?: boolean;
|
|
4
24
|
}
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
export type NormalizeSsnResult = NormalizeSsnOk | NormalizeSsnErr;
|
|
15
|
-
export declare function normalizeSsnInput(input: string, opts?: NormalizeSsnOptions): NormalizeSsnResult;
|
|
25
|
+
/**
|
|
26
|
+
* Best-effort SSN normalization for UI display.
|
|
27
|
+
* - Returns a string only (no validity info).
|
|
28
|
+
* - Never throws.
|
|
29
|
+
* - Extracts digits from input and optionally inserts dashes.
|
|
30
|
+
* - Optionally caps to 9 digits (enforceLength).
|
|
31
|
+
* - Keeps behavior predictable for "typing-as-you-go".
|
|
32
|
+
*/
|
|
33
|
+
export declare function normalizeSsn(input: string, opts?: NormalizeSsnOptions): string;
|
package/dist/normalize.js
CHANGED
|
@@ -1,62 +1,43 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.normalizeSsn = normalizeSsn;
|
|
4
4
|
const utils_1 = require("./utils");
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
if (allowNoDashes && /^\d{9}$/.test(raw)) {
|
|
20
|
-
return {
|
|
21
|
-
ok: true,
|
|
22
|
-
digits: raw,
|
|
23
|
-
normalized: `${raw.slice(0, 3)}-${raw.slice(3, 5)}-${raw.slice(5)}`,
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
return { ok: false, message: 'Invalid SSN format.' };
|
|
27
|
-
}
|
|
28
|
-
// ---- Partial / typing-as-you-go normalization ----
|
|
29
|
-
if (!/^[0-9-]*$/.test(raw)) {
|
|
30
|
-
return { ok: false, message: "Only digits and '-' are allowed." };
|
|
31
|
-
}
|
|
5
|
+
/**
|
|
6
|
+
* Best-effort SSN normalization for UI display.
|
|
7
|
+
* - Returns a string only (no validity info).
|
|
8
|
+
* - Never throws.
|
|
9
|
+
* - Extracts digits from input and optionally inserts dashes.
|
|
10
|
+
* - Optionally caps to 9 digits (enforceLength).
|
|
11
|
+
* - Keeps behavior predictable for "typing-as-you-go".
|
|
12
|
+
*/
|
|
13
|
+
function normalizeSsn(input, opts = {}) {
|
|
14
|
+
var _a, _b, _c;
|
|
15
|
+
const allowPartial = (_a = opts.allowPartial) !== null && _a !== void 0 ? _a : true;
|
|
16
|
+
const digitsOnly = (_b = opts.digitsOnly) !== null && _b !== void 0 ? _b : false;
|
|
17
|
+
const enforceLength = (_c = opts.enforceLength) !== null && _c !== void 0 ? _c : true;
|
|
18
|
+
// Extract digits, optionally cap to 9 for SSN-shaped output.
|
|
32
19
|
let digits = '';
|
|
33
|
-
|
|
34
|
-
let sawDashAt5 = false;
|
|
35
|
-
for (const ch of raw) {
|
|
20
|
+
for (const ch of input) {
|
|
36
21
|
if (ch >= '0' && ch <= '9') {
|
|
37
22
|
digits += ch;
|
|
38
|
-
if (digits.length
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
continue;
|
|
42
|
-
}
|
|
43
|
-
// ch === '-'
|
|
44
|
-
if (!allowNoDashes) {
|
|
45
|
-
return { ok: false, message: 'Dashes are not allowed.' };
|
|
46
|
-
}
|
|
47
|
-
if (digits.length === 3 && !sawDashAt3) {
|
|
48
|
-
sawDashAt3 = true;
|
|
49
|
-
}
|
|
50
|
-
else if (digits.length === 5 && !sawDashAt5) {
|
|
51
|
-
sawDashAt5 = true;
|
|
52
|
-
}
|
|
53
|
-
else {
|
|
54
|
-
return { ok: false, message: 'Dash is in an invalid position.' };
|
|
23
|
+
if (enforceLength && digits.length === 9)
|
|
24
|
+
break;
|
|
55
25
|
}
|
|
56
26
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
digits
|
|
60
|
-
|
|
61
|
-
|
|
27
|
+
if (digitsOnly) {
|
|
28
|
+
// digits-only output: just return the extracted digits (possibly >9 if enforceLength=false)
|
|
29
|
+
return digits;
|
|
30
|
+
}
|
|
31
|
+
// dashed output
|
|
32
|
+
if (allowPartial) {
|
|
33
|
+
// Typing mode: format prefix. If digits > 9 and enforceLength=false,
|
|
34
|
+
// we format the SSN-shaped prefix and append the rest after the serial.
|
|
35
|
+
return (0, utils_1.formatSsnWithOverflow)(digits);
|
|
36
|
+
}
|
|
37
|
+
// Non-partial mode: only format if we have a full 9 digits; otherwise return input unchanged.
|
|
38
|
+
// (Prevents jumpy formatting in contexts where you don't want as-you-type behavior.)
|
|
39
|
+
if (digits.length === 9 || (enforceLength && digits.length === 9)) {
|
|
40
|
+
return (0, utils_1.formatSsnFromDigits)(digits.slice(0, 9));
|
|
41
|
+
}
|
|
42
|
+
return input;
|
|
62
43
|
}
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Format SSN from digits. Used for UI
|
|
2
|
+
* Format SSN from digits. Used for UI
|
|
3
3
|
*/
|
|
4
4
|
export declare function formatSsnFromDigits(digits: string): string;
|
|
5
|
+
/**
|
|
6
|
+
* Formats digits as ###-##-#### and if there are extra digits, appends them after the serial.
|
|
7
|
+
* Example: "12345678999" -> "123-45-678999"
|
|
8
|
+
*/
|
|
9
|
+
export declare function formatSsnWithOverflow(digits: string): string;
|
package/dist/utils.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.formatSsnFromDigits = formatSsnFromDigits;
|
|
4
|
+
exports.formatSsnWithOverflow = formatSsnWithOverflow;
|
|
4
5
|
/**
|
|
5
|
-
* Format SSN from digits. Used for UI
|
|
6
|
+
* Format SSN from digits. Used for UI
|
|
6
7
|
*/
|
|
7
8
|
function formatSsnFromDigits(digits) {
|
|
8
9
|
if (digits.length <= 3)
|
|
@@ -11,3 +12,12 @@ function formatSsnFromDigits(digits) {
|
|
|
11
12
|
return `${digits.slice(0, 3)}-${digits.slice(3)}`;
|
|
12
13
|
return `${digits.slice(0, 3)}-${digits.slice(3, 5)}-${digits.slice(5)}`;
|
|
13
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* Formats digits as ###-##-#### and if there are extra digits, appends them after the serial.
|
|
17
|
+
* Example: "12345678999" -> "123-45-678999"
|
|
18
|
+
*/
|
|
19
|
+
function formatSsnWithOverflow(digits) {
|
|
20
|
+
if (digits.length <= 9)
|
|
21
|
+
return formatSsnFromDigits(digits);
|
|
22
|
+
return `${formatSsnFromDigits(digits.slice(0, 9))}${digits.slice(9)}`;
|
|
23
|
+
}
|
package/dist/validate.d.ts
CHANGED
|
@@ -1,43 +1,25 @@
|
|
|
1
|
-
export type
|
|
2
|
-
export type SsnRuleMode = 'pre2011' | 'post2011' | 'both';
|
|
3
|
-
export type SsnValidationOkResult = {
|
|
4
|
-
ok: true;
|
|
5
|
-
normalized: string;
|
|
6
|
-
};
|
|
7
|
-
export type SsnValidationErrorResult = {
|
|
8
|
-
ok: false;
|
|
9
|
-
error: SsnValidationFailureReason;
|
|
10
|
-
message: string;
|
|
11
|
-
};
|
|
12
|
-
export type SsnValidationResult = SsnValidationOkResult | SsnValidationErrorResult;
|
|
1
|
+
export type SsnRuleMode = 'pre2011' | 'post2011';
|
|
13
2
|
export interface ValidateSsnOptions {
|
|
14
3
|
/**
|
|
15
|
-
* If true,
|
|
16
|
-
* If false,
|
|
4
|
+
* If true, input must be in ###-##-#### (or a valid prefix of it when allowPartial=true).
|
|
5
|
+
* If false, accepts either ###-##-#### or ######### (and prefixes).
|
|
6
|
+
*
|
|
7
|
+
* Default: true
|
|
17
8
|
*/
|
|
18
|
-
|
|
9
|
+
requireDashes?: boolean;
|
|
19
10
|
/**
|
|
20
|
-
*
|
|
21
|
-
* -
|
|
22
|
-
* - "post2011": do NOT apply those extra area restrictions
|
|
23
|
-
* - "both": accept either (default)
|
|
11
|
+
* Pre-2011 is stricter on area numbers (734-749 and >= 773 are invalid).
|
|
12
|
+
* Post-2011 uses only the base area rules (000, 666, 900-999 invalid).
|
|
24
13
|
*
|
|
25
|
-
*
|
|
14
|
+
* Default: "post2011"
|
|
26
15
|
*/
|
|
27
16
|
ruleMode?: SsnRuleMode;
|
|
28
17
|
/**
|
|
29
|
-
* If true,
|
|
30
|
-
*
|
|
31
|
-
*
|
|
18
|
+
* If true, accept prefixes that are still potentially valid as the user types.
|
|
19
|
+
* If false, require a complete SSN.
|
|
20
|
+
*
|
|
21
|
+
* Default: false
|
|
32
22
|
*/
|
|
33
23
|
allowPartial?: boolean;
|
|
34
24
|
}
|
|
35
|
-
/**
|
|
36
|
-
* Validates a US SSN with support for:
|
|
37
|
-
* - strict full validation
|
|
38
|
-
* - optional acceptance of no-dash input
|
|
39
|
-
* - rule modes: pre2011 | post2011 | both (default)
|
|
40
|
-
* - optional "checking-as-you-go" partial validation
|
|
41
|
-
*/ export declare function validateSsn(input: string, opts?: ValidateSsnOptions): SsnValidationResult;
|
|
42
|
-
/** Convenience boolean wrapper */
|
|
43
25
|
export declare function isValidSsn(input: string, opts?: ValidateSsnOptions): boolean;
|
package/dist/validate.js
CHANGED
|
@@ -1,179 +1,144 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.validateSsn = validateSsn;
|
|
4
3
|
exports.isValidSsn = isValidSsn;
|
|
5
|
-
const normalize_1 = require("./normalize");
|
|
6
4
|
const PUBLICLY_ADVERTISED = new Set([
|
|
7
5
|
'078-05-1120',
|
|
8
6
|
'721-07-4426',
|
|
9
7
|
'219-09-9999',
|
|
10
8
|
]);
|
|
11
|
-
|
|
12
|
-
* Validates a US SSN with support for:
|
|
13
|
-
* - strict full validation
|
|
14
|
-
* - optional acceptance of no-dash input
|
|
15
|
-
* - rule modes: pre2011 | post2011 | both (default)
|
|
16
|
-
* - optional "checking-as-you-go" partial validation
|
|
17
|
-
*/ function validateSsn(input, opts = {}) {
|
|
9
|
+
function isValidSsn(input, opts = {}) {
|
|
18
10
|
var _a, _b, _c;
|
|
19
|
-
const
|
|
20
|
-
const ruleMode = (_b = opts.ruleMode) !== null && _b !== void 0 ? _b : '
|
|
11
|
+
const requireDashes = (_a = opts.requireDashes) !== null && _a !== void 0 ? _a : true;
|
|
12
|
+
const ruleMode = (_b = opts.ruleMode) !== null && _b !== void 0 ? _b : 'post2011';
|
|
21
13
|
const allowPartial = (_c = opts.allowPartial) !== null && _c !== void 0 ? _c : false;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
14
|
+
// 1) Format/prefix checks + digit extraction
|
|
15
|
+
const parsed = parseSsnInput(input, { requireDashes, allowPartial });
|
|
16
|
+
if (!parsed.ok)
|
|
17
|
+
return false;
|
|
18
|
+
const digits = parsed.digits;
|
|
19
|
+
// In strict (non-partial) mode, require exactly 9 digits.
|
|
20
|
+
if (!allowPartial && digits.length !== 9)
|
|
21
|
+
return false;
|
|
22
|
+
// 2) Rule checks (apply progressively in partial mode)
|
|
23
|
+
// Area (first 3)
|
|
24
|
+
if (digits.length >= 3) {
|
|
25
|
+
const area = Number(digits.slice(0, 3));
|
|
26
|
+
if (!isValidArea(area, ruleMode))
|
|
27
|
+
return false;
|
|
32
28
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
return {
|
|
39
|
-
ok: false,
|
|
40
|
-
error: 'PUBLICLY_ADVERTISED',
|
|
41
|
-
message: 'This SSN is a known publicly advertised (and invalid) value.',
|
|
42
|
-
};
|
|
29
|
+
// Group (next 2)
|
|
30
|
+
if (digits.length >= 5) {
|
|
31
|
+
const group = Number(digits.slice(3, 5));
|
|
32
|
+
if (group === 0)
|
|
33
|
+
return false; // "00"
|
|
43
34
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
return validateSsn(input, opts).ok;
|
|
54
|
-
}
|
|
55
|
-
function normalizePrefix(digits) {
|
|
56
|
-
// digits: 0..9 chars
|
|
57
|
-
if (digits.length <= 3)
|
|
58
|
-
return digits;
|
|
59
|
-
if (digits.length <= 5)
|
|
60
|
-
return `${digits.slice(0, 3)}-${digits.slice(3)}`;
|
|
61
|
-
return `${digits.slice(0, 3)}-${digits.slice(3, 5)}-${digits.slice(5)}`;
|
|
62
|
-
}
|
|
63
|
-
/* -------------------------- Rules -------------------------- */
|
|
64
|
-
function validateFullSegments(area, group, serial, ruleMode) {
|
|
65
|
-
const areaNum = Number(area);
|
|
66
|
-
const groupNum = Number(group);
|
|
67
|
-
const serialNum = Number(serial);
|
|
68
|
-
// Base Rule 2: area cannot be 000, 666, or 900–999
|
|
69
|
-
if (areaNum === 0 || areaNum === 666 || areaNum >= 900) {
|
|
70
|
-
return {
|
|
71
|
-
ok: false,
|
|
72
|
-
error: 'INVALID_AREA',
|
|
73
|
-
message: 'Area number is not allowed (000, 666, and 900–999 are invalid).',
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
// Ruleset selection:
|
|
77
|
-
// - pre2011: apply extra reserved ranges
|
|
78
|
-
// - post2011: do not
|
|
79
|
-
// - both: accept if EITHER passes (so we only fail if it violates post rules AND pre rules)
|
|
80
|
-
const violatesPre2011 = (areaNum >= 734 && areaNum <= 749) || areaNum >= 773;
|
|
81
|
-
if (ruleMode === 'pre2011' && violatesPre2011) {
|
|
82
|
-
return {
|
|
83
|
-
ok: false,
|
|
84
|
-
error: 'INVALID_AREA',
|
|
85
|
-
message: 'Area number is not allowed under pre–June 25, 2011 rules (734–749 and >= 773 are invalid).',
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
if (ruleMode === 'both') {
|
|
89
|
-
// If it violates pre2011, it's still acceptable as post2011.
|
|
90
|
-
// So no action needed.
|
|
35
|
+
// Serial (last 4)
|
|
36
|
+
if (digits.length === 9) {
|
|
37
|
+
const serial = Number(digits.slice(5, 9));
|
|
38
|
+
if (serial === 0)
|
|
39
|
+
return false; // "0000"
|
|
40
|
+
// Publicly advertised SSNs are always invalid
|
|
41
|
+
const dashed = `${digits.slice(0, 3)}-${digits.slice(3, 5)}-${digits.slice(5)}`;
|
|
42
|
+
if (PUBLICLY_ADVERTISED.has(dashed))
|
|
43
|
+
return false;
|
|
91
44
|
}
|
|
92
|
-
//
|
|
93
|
-
|
|
94
|
-
return {
|
|
95
|
-
ok: false,
|
|
96
|
-
error: 'INVALID_GROUP',
|
|
97
|
-
message: 'Group number may not be 00.',
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
// Rule 4: serial cannot be 0000
|
|
101
|
-
if (serialNum === 0) {
|
|
102
|
-
return {
|
|
103
|
-
ok: false,
|
|
104
|
-
error: 'INVALID_SERIAL',
|
|
105
|
-
message: 'Serial number may not be 0000.',
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
// Rule 7: publicly advertised checked in validateSsn (full only)
|
|
109
|
-
return { ok: true, normalized: `${area}-${group}-${serial}` };
|
|
45
|
+
// If partial, any prefix that passed the progressive checks is considered valid so far.
|
|
46
|
+
return true;
|
|
110
47
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
//
|
|
114
|
-
if (area
|
|
115
|
-
return
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
48
|
+
/* ---------------- helpers ---------------- */
|
|
49
|
+
function isValidArea(area, ruleMode) {
|
|
50
|
+
// Base rules (always)
|
|
51
|
+
if (area === 0)
|
|
52
|
+
return false; // 000
|
|
53
|
+
if (area === 666)
|
|
54
|
+
return false;
|
|
55
|
+
if (area >= 900)
|
|
56
|
+
return false;
|
|
57
|
+
// Pre-2011 additional rules
|
|
58
|
+
if (ruleMode === 'pre2011') {
|
|
59
|
+
if (area >= 734 && area <= 749)
|
|
60
|
+
return false;
|
|
61
|
+
if (area >= 773)
|
|
62
|
+
return false;
|
|
120
63
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
};
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
function parseSsnInput(input, opts) {
|
|
67
|
+
const { requireDashes, allowPartial } = opts;
|
|
68
|
+
if (!allowPartial) {
|
|
69
|
+
// Full input only
|
|
70
|
+
if (/^\d{3}-\d{2}-\d{4}$/.test(input)) {
|
|
71
|
+
return { ok: true, digits: input.replace(/-/g, '') };
|
|
130
72
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
return {
|
|
134
|
-
ok: false,
|
|
135
|
-
error: 'INVALID_AREA',
|
|
136
|
-
message: 'Area number is not allowed under pre–June 25, 2011 rules (734–749 and >= 773 are invalid).',
|
|
137
|
-
};
|
|
73
|
+
if (!requireDashes && /^\d{9}$/.test(input)) {
|
|
74
|
+
return { ok: true, digits: input };
|
|
138
75
|
}
|
|
139
|
-
|
|
76
|
+
return { ok: false };
|
|
140
77
|
}
|
|
141
|
-
//
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
78
|
+
// Partial / typing-as-you-go
|
|
79
|
+
if (requireDashes) {
|
|
80
|
+
// Must be a prefix of ###-##-####
|
|
81
|
+
// Allowed prefixes include:
|
|
82
|
+
// "", "1", "12", "123", "123-", "123-4", "123-45", "123-45-", "123-45-6", ...
|
|
83
|
+
if (!/^[0-9-]*$/.test(input))
|
|
84
|
+
return { ok: false };
|
|
85
|
+
// Enforce dash positions and ordering as user types.
|
|
86
|
+
// Build digits while ensuring '-' only appears right after 3 and 5 digits (and at most once each).
|
|
87
|
+
let digits = '';
|
|
88
|
+
let sawDashAt3 = false;
|
|
89
|
+
let sawDashAt5 = false;
|
|
90
|
+
for (const ch of input) {
|
|
91
|
+
if (ch >= '0' && ch <= '9') {
|
|
92
|
+
if (digits.length >= 9)
|
|
93
|
+
return { ok: false };
|
|
94
|
+
digits += ch;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
// ch === '-'
|
|
98
|
+
if (digits.length === 3 && !sawDashAt3) {
|
|
99
|
+
sawDashAt3 = true;
|
|
100
|
+
}
|
|
101
|
+
else if (digits.length === 5 && !sawDashAt5) {
|
|
102
|
+
sawDashAt5 = true;
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
return { ok: false };
|
|
106
|
+
}
|
|
151
107
|
}
|
|
108
|
+
// Also disallow typing digits past 3 without having placed the first dash (since requireDashes=true).
|
|
109
|
+
// The loop above already enforces this implicitly: "1234" contains no dash; it's allowed as digits,
|
|
110
|
+
// but would be a prefix of digits-only, not of dashed format. If you want "1234" to be invalid
|
|
111
|
+
// when requireDashes=true, enforce it here:
|
|
112
|
+
if (digits.length > 3 && !sawDashAt3)
|
|
113
|
+
return { ok: false };
|
|
114
|
+
if (digits.length > 5 && !sawDashAt5)
|
|
115
|
+
return { ok: false };
|
|
116
|
+
return { ok: true, digits };
|
|
152
117
|
}
|
|
153
|
-
//
|
|
154
|
-
//
|
|
155
|
-
if (
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
118
|
+
// requireDashes === false in partial mode:
|
|
119
|
+
// accept either digits-only prefixes or dashed prefixes (as long as dashes are in valid positions)
|
|
120
|
+
if (!/^[0-9-]*$/.test(input))
|
|
121
|
+
return { ok: false };
|
|
122
|
+
let digits = '';
|
|
123
|
+
let sawDashAt3 = false;
|
|
124
|
+
let sawDashAt5 = false;
|
|
125
|
+
for (const ch of input) {
|
|
126
|
+
if (ch >= '0' && ch <= '9') {
|
|
127
|
+
if (digits.length >= 9)
|
|
128
|
+
return { ok: false };
|
|
129
|
+
digits += ch;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
// '-' present: enforce positions (same as dashed typing), but do not require them.
|
|
133
|
+
if (digits.length === 3 && !sawDashAt3) {
|
|
134
|
+
sawDashAt3 = true;
|
|
135
|
+
}
|
|
136
|
+
else if (digits.length === 5 && !sawDashAt5) {
|
|
137
|
+
sawDashAt5 = true;
|
|
163
138
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
if (PUBLICLY_ADVERTISED.has(fullNormalized)) {
|
|
167
|
-
return {
|
|
168
|
-
ok: false,
|
|
169
|
-
error: 'PUBLICLY_ADVERTISED',
|
|
170
|
-
message: 'This SSN is a known publicly advertised (and invalid) value.',
|
|
171
|
-
};
|
|
139
|
+
else {
|
|
140
|
+
return { ok: false };
|
|
172
141
|
}
|
|
173
142
|
}
|
|
174
|
-
|
|
175
|
-
return {
|
|
176
|
-
ok: true,
|
|
177
|
-
normalized: normalizePrefix((area + group + serial).slice(0, 9)),
|
|
178
|
-
};
|
|
143
|
+
return { ok: true, digits };
|
|
179
144
|
}
|
package/dist/yup.d.ts
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import * as yup from 'yup';
|
|
2
2
|
import { type ValidateSsnOptions } from './validate';
|
|
3
3
|
/**
|
|
4
|
-
* Yup schema for "typing":
|
|
5
|
-
*
|
|
4
|
+
* Yup schema for "typing":
|
|
5
|
+
* - transforms the input to a normalized prefix (UI-friendly)
|
|
6
|
+
* - validates "valid so far" (allowPartial=true)
|
|
6
7
|
*/
|
|
7
8
|
export declare function yupSsnTyping(opts?: Omit<ValidateSsnOptions, 'allowPartial'>): yup.StringSchema<string | undefined, yup.AnyObject, undefined, "">;
|
|
8
|
-
/**
|
|
9
|
+
/**
|
|
10
|
+
* Yup schema for full submit:
|
|
11
|
+
* - transforms into a canonical SSN representation
|
|
12
|
+
* - validates strictly (allowPartial=false)
|
|
13
|
+
*/
|
|
9
14
|
export declare function yupSsnSubmit(opts?: Omit<ValidateSsnOptions, 'allowPartial'>): yup.StringSchema<string | undefined, yup.AnyObject, undefined, "">;
|
package/dist/yup.js
CHANGED
|
@@ -36,45 +36,56 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.yupSsnTyping = yupSsnTyping;
|
|
37
37
|
exports.yupSsnSubmit = yupSsnSubmit;
|
|
38
38
|
const yup = __importStar(require("yup"));
|
|
39
|
+
const normalize_1 = require("./normalize");
|
|
39
40
|
const validate_1 = require("./validate");
|
|
40
41
|
/**
|
|
41
|
-
* Yup schema for "typing":
|
|
42
|
-
*
|
|
42
|
+
* Yup schema for "typing":
|
|
43
|
+
* - transforms the input to a normalized prefix (UI-friendly)
|
|
44
|
+
* - validates "valid so far" (allowPartial=true)
|
|
43
45
|
*/
|
|
44
46
|
function yupSsnTyping(opts = {}) {
|
|
45
47
|
return yup
|
|
46
48
|
.string()
|
|
47
49
|
.transform((value) => {
|
|
48
50
|
const v = (value !== null && value !== void 0 ? value : '').toString();
|
|
49
|
-
|
|
50
|
-
|
|
51
|
+
// Normalize for UI: prefix formatting as the user types.
|
|
52
|
+
// Keep dashed output (digitsOnly=false) by default.
|
|
53
|
+
return (0, normalize_1.normalizeSsn)(v, {
|
|
54
|
+
allowPartial: true,
|
|
55
|
+
digitsOnly: false,
|
|
56
|
+
enforceLength: false,
|
|
57
|
+
});
|
|
51
58
|
})
|
|
52
59
|
.test('ssn-typing-valid', 'Invalid SSN', function (value) {
|
|
53
60
|
const v = (value !== null && value !== void 0 ? value : '').toString();
|
|
54
|
-
const
|
|
55
|
-
return
|
|
56
|
-
? true
|
|
57
|
-
: this.createError({
|
|
58
|
-
message: res.message,
|
|
59
|
-
});
|
|
61
|
+
const ok = (0, validate_1.isValidSsn)(v, Object.assign(Object.assign({}, opts), { allowPartial: true }));
|
|
62
|
+
return ok ? true : this.createError({ message: 'Invalid SSN' });
|
|
60
63
|
});
|
|
61
64
|
}
|
|
62
|
-
/**
|
|
65
|
+
/**
|
|
66
|
+
* Yup schema for full submit:
|
|
67
|
+
* - transforms into a canonical SSN representation
|
|
68
|
+
* - validates strictly (allowPartial=false)
|
|
69
|
+
*/
|
|
63
70
|
function yupSsnSubmit(opts = {}) {
|
|
64
71
|
return yup
|
|
65
72
|
.string()
|
|
66
73
|
.transform((value) => {
|
|
74
|
+
var _a;
|
|
67
75
|
const v = (value !== null && value !== void 0 ? value : '').toString();
|
|
68
|
-
|
|
69
|
-
|
|
76
|
+
// For submit, normalize to canonical form.
|
|
77
|
+
// If requireDashes is true (default), return dashed.
|
|
78
|
+
// If requireDashes is false, you might prefer digitsOnly, but we keep dashed for consistency.
|
|
79
|
+
const digitsOnly = ((_a = opts.requireDashes) !== null && _a !== void 0 ? _a : true) ? false : true;
|
|
80
|
+
return (0, normalize_1.normalizeSsn)(v, {
|
|
81
|
+
allowPartial: false,
|
|
82
|
+
digitsOnly,
|
|
83
|
+
enforceLength: true, // for submit, capping to 9 is usually desired
|
|
84
|
+
});
|
|
70
85
|
})
|
|
71
86
|
.test('ssn-submit-valid', 'Invalid SSN', function (value) {
|
|
72
87
|
const v = (value !== null && value !== void 0 ? value : '').toString();
|
|
73
|
-
const
|
|
74
|
-
return
|
|
75
|
-
? true
|
|
76
|
-
: this.createError({
|
|
77
|
-
message: res.message,
|
|
78
|
-
});
|
|
88
|
+
const ok = (0, validate_1.isValidSsn)(v, Object.assign(Object.assign({}, opts), { allowPartial: false }));
|
|
89
|
+
return ok ? true : this.createError({ message: 'Invalid SSN' });
|
|
79
90
|
});
|
|
80
91
|
}
|