@indodev/toolkit 0.4.1 → 0.4.2
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/dist/index.cjs +40 -33
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +40 -33
- package/dist/index.js.map +1 -1
- package/dist/nik/index.cjs +51 -33
- package/dist/nik/index.cjs.map +1 -1
- package/dist/nik/index.d.cts +23 -1
- package/dist/nik/index.d.ts +23 -1
- package/dist/nik/index.js +51 -34
- package/dist/nik/index.js.map +1 -1
- package/package.json +1 -1
package/dist/nik/index.cjs
CHANGED
|
@@ -59,53 +59,66 @@ var REGENCIES = {
|
|
|
59
59
|
}
|
|
60
60
|
};
|
|
61
61
|
|
|
62
|
-
// src/nik/
|
|
63
|
-
function
|
|
64
|
-
if (
|
|
65
|
-
return
|
|
66
|
-
}
|
|
67
|
-
const provinceCode = nik.substring(0, 2);
|
|
68
|
-
if (!PROVINCES[provinceCode]) {
|
|
69
|
-
return false;
|
|
62
|
+
// src/nik/utils/date.ts
|
|
63
|
+
function parseNIKDate(nik) {
|
|
64
|
+
if (nik.length !== 16) {
|
|
65
|
+
return null;
|
|
70
66
|
}
|
|
71
67
|
const yearStr = nik.substring(6, 8);
|
|
72
68
|
const monthStr = nik.substring(8, 10);
|
|
73
|
-
const
|
|
69
|
+
const dayEncodedStr = nik.substring(10, 12);
|
|
74
70
|
const year = parseInt(yearStr, 10);
|
|
71
|
+
if (isNaN(year)) return null;
|
|
75
72
|
const fullYear = year > 30 ? 1900 + year : 2e3 + year;
|
|
76
73
|
const month = parseInt(monthStr, 10);
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
74
|
+
if (isNaN(month)) return null;
|
|
75
|
+
const dayEncoded = parseInt(dayEncodedStr, 10);
|
|
76
|
+
if (isNaN(dayEncoded)) return null;
|
|
77
|
+
const gender = dayEncoded > 40 ? "female" : "male";
|
|
78
|
+
const day = dayEncoded > 40 ? dayEncoded - 40 : dayEncoded;
|
|
79
|
+
return { year, fullYear, month, day, gender, dayEncoded };
|
|
80
|
+
}
|
|
81
|
+
function validateNIKDateComponents(year, month, day) {
|
|
82
|
+
if (month < 1 || month > 12) return false;
|
|
83
|
+
if (day < 1 || day > 31) return false;
|
|
84
|
+
const testDate = new Date(year, month - 1, day);
|
|
85
|
+
return testDate.getFullYear() === year && testDate.getMonth() === month - 1 && testDate.getDate() === day;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// src/nik/validate.ts
|
|
89
|
+
var NIK_PATTERN = /^\d{16}$/;
|
|
90
|
+
function validateNIK(nik) {
|
|
91
|
+
if (!NIK_PATTERN.test(nik)) {
|
|
92
|
+
return false;
|
|
80
93
|
}
|
|
81
|
-
|
|
94
|
+
const provinceCode = nik.substring(0, 2);
|
|
95
|
+
if (!PROVINCES[provinceCode]) {
|
|
82
96
|
return false;
|
|
83
97
|
}
|
|
84
|
-
|
|
98
|
+
const parsed = parseNIKDate(nik);
|
|
99
|
+
if (!parsed) {
|
|
85
100
|
return false;
|
|
86
101
|
}
|
|
87
|
-
const
|
|
88
|
-
if (
|
|
102
|
+
const { fullYear, month, day } = parsed;
|
|
103
|
+
if (!validateNIKDateComponents(fullYear, month, day)) {
|
|
89
104
|
return false;
|
|
90
105
|
}
|
|
91
106
|
const now = /* @__PURE__ */ new Date();
|
|
92
|
-
if (
|
|
107
|
+
if (new Date(fullYear, month - 1, day) > now || fullYear < 1900) {
|
|
93
108
|
return false;
|
|
94
109
|
}
|
|
95
110
|
return true;
|
|
96
111
|
}
|
|
97
112
|
|
|
98
113
|
// src/nik/parse.ts
|
|
114
|
+
var NIK_PATTERN2 = /^\d{16}$/;
|
|
99
115
|
function parseNIK(nik) {
|
|
100
|
-
if (
|
|
116
|
+
if (!NIK_PATTERN2.test(nik)) {
|
|
101
117
|
return null;
|
|
102
118
|
}
|
|
103
119
|
const provinceCode = nik.substring(0, 2);
|
|
104
120
|
const regencyCode = nik.substring(2, 4);
|
|
105
121
|
const districtCode = nik.substring(4, 6);
|
|
106
|
-
const yearStr = nik.substring(6, 8);
|
|
107
|
-
const monthStr = nik.substring(8, 10);
|
|
108
|
-
const dayStr = nik.substring(10, 12);
|
|
109
122
|
const serialNumber = nik.substring(12, 16);
|
|
110
123
|
const province = PROVINCES[provinceCode];
|
|
111
124
|
if (!province) {
|
|
@@ -113,21 +126,15 @@ function parseNIK(nik) {
|
|
|
113
126
|
}
|
|
114
127
|
const regencies = REGENCIES[provinceCode] || {};
|
|
115
128
|
const regency = regencies[regencyCode] || "Unknown";
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
let gender = null;
|
|
120
|
-
if (day > 40) {
|
|
121
|
-
gender = "female";
|
|
122
|
-
day -= 40;
|
|
123
|
-
} else {
|
|
124
|
-
gender = "male";
|
|
129
|
+
const parsed = parseNIKDate(nik);
|
|
130
|
+
if (!parsed) {
|
|
131
|
+
return null;
|
|
125
132
|
}
|
|
126
|
-
const fullYear
|
|
127
|
-
|
|
128
|
-
if (birthDate.getFullYear() !== fullYear || birthDate.getMonth() !== month - 1 || birthDate.getDate() !== day) {
|
|
133
|
+
const { fullYear, month, day, gender } = parsed;
|
|
134
|
+
if (!validateNIKDateComponents(fullYear, month, day)) {
|
|
129
135
|
return null;
|
|
130
136
|
}
|
|
137
|
+
const birthDate = new Date(fullYear, month - 1, day);
|
|
131
138
|
return {
|
|
132
139
|
province: {
|
|
133
140
|
code: provinceCode,
|
|
@@ -250,6 +257,17 @@ function isValidForBirthDate(nik, birthDate) {
|
|
|
250
257
|
return info.birthDate.getFullYear() === birthDate.getFullYear() && info.birthDate.getMonth() === birthDate.getMonth() && info.birthDate.getDate() === birthDate.getDate();
|
|
251
258
|
}
|
|
252
259
|
|
|
260
|
+
// src/nik/types.ts
|
|
261
|
+
var InvalidNIKError = class extends Error {
|
|
262
|
+
constructor(message = "Invalid NIK provided") {
|
|
263
|
+
super(message);
|
|
264
|
+
/** Error code for programmatic identification */
|
|
265
|
+
this.code = "INVALID_NIK";
|
|
266
|
+
this.name = "InvalidNIKError";
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
exports.InvalidNIKError = InvalidNIKError;
|
|
253
271
|
exports.formatBirthDate = formatBirthDate;
|
|
254
272
|
exports.formatNIK = formatNIK;
|
|
255
273
|
exports.getAge = getAge;
|
package/dist/nik/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/nik/constants.ts","../../src/nik/validate.ts","../../src/nik/parse.ts","../../src/nik/format.ts","../../src/nik/utils.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Indonesian province codes and names\n * Based on Dukcapil Kemendagri data\n */\nexport const PROVINCES: Record<string, string> = {\n '11': 'Aceh',\n '12': 'Sumatera Utara',\n '13': 'Sumatera Barat',\n '14': 'Riau',\n '15': 'Jambi',\n '16': 'Sumatera Selatan',\n '17': 'Bengkulu',\n '18': 'Lampung',\n '19': 'Kepulauan Bangka Belitung',\n '21': 'Kepulauan Riau',\n '31': 'DKI Jakarta',\n '32': 'Jawa Barat',\n '33': 'Jawa Tengah',\n '34': 'DI Yogyakarta',\n '35': 'Jawa Timur',\n '36': 'Banten',\n '51': 'Bali',\n '52': 'Nusa Tenggara Barat',\n '53': 'Nusa Tenggara Timur',\n '61': 'Kalimantan Barat',\n '62': 'Kalimantan Tengah',\n '63': 'Kalimantan Selatan',\n '64': 'Kalimantan Timur',\n '65': 'Kalimantan Utara',\n '71': 'Sulawesi Utara',\n '72': 'Sulawesi Tengah',\n '73': 'Sulawesi Selatan',\n '74': 'Sulawesi Tenggara',\n '75': 'Gorontalo',\n '76': 'Sulawesi Barat',\n '81': 'Maluku',\n '82': 'Maluku Utara',\n '91': 'Papua',\n '92': 'Papua Barat',\n '93': 'Papua Selatan',\n '94': 'Papua Tengah',\n '95': 'Papua Pegunungan',\n '96': 'Papua Barat Daya',\n};\n\n/**\n * Regency codes for each province (simplified - only major ones)\n * In a real implementation, you'd have complete data\n */\nexport const REGENCIES: Record<string, Record<string, string>> = {\n '32': {\n '01': 'Kab. Bogor',\n '02': 'Kab. Sukabumi',\n '03': 'Kab. Cianjur',\n '71': 'Kota Bandung',\n '72': 'Kota Bekasi',\n '73': 'Kota Depok',\n },\n '31': { \n '01': 'Kota Jakarta Selatan',\n '02': 'Kota Jakarta Timur',\n '03': 'Kota Jakarta Pusat',\n '04': 'Kota Jakarta Barat',\n '05': 'Kota Jakarta Utara',\n },\n};","import { PROVINCES } from './constants';\n\n/**\n * Validates a NIK (Nomor Induk Kependudukan) format.\n *\n * A valid NIK must:\n * - Be exactly 16 digits\n * - Have a valid province code (positions 1-2)\n * - Have a valid date (positions 7-12)\n * - Not be in the future\n * - Not be before 1900\n *\n * For female NIKs, the day is encoded as (actual day + 40).\n * For example, a female born on the 15th would have day = 55.\n *\n * @param nik - The 16-digit NIK string to validate\n * @returns `true` if the NIK is valid, `false` otherwise\n *\n * @example\n * ```typescript\n * validateNIK('3201234567890123'); // true - valid NIK\n * validateNIK('1234'); // false - wrong length\n * validateNIK('9912345678901234'); // false - invalid province\n * ```\n *\n * @public\n */\nexport function validateNIK(nik: string): boolean {\n if (!/^\\d{16}$/.test(nik)) {\n return false;\n }\n\n const provinceCode = nik.substring(0, 2);\n if (!PROVINCES[provinceCode]) {\n return false;\n }\n\n const yearStr = nik.substring(6, 8);\n const monthStr = nik.substring(8, 10);\n const dayStr = nik.substring(10, 12);\n\n const year = parseInt(yearStr, 10);\n const fullYear = year > 30 ? 1900 + year : 2000 + year;\n\n const month = parseInt(monthStr, 10);\n let day = parseInt(dayStr, 10);\n\n if (day > 40) {\n day = day - 40;\n }\n\n if (month < 1 || month > 12) {\n return false;\n }\n\n if (day < 1 || day > 31) {\n return false;\n }\n\n const testDate = new Date(fullYear, month - 1, day);\n if (\n testDate.getFullYear() !== fullYear ||\n testDate.getMonth() !== month - 1 ||\n testDate.getDate() !== day\n ) {\n return false;\n }\n\n const now = new Date();\n if (testDate > now || testDate < new Date(1900, 0, 1)) {\n return false;\n }\n\n return true;\n}\n","import { PROVINCES, REGENCIES } from './constants';\nimport { NIKInfo } from './types';\n\n/**\n * Parses a NIK and extracts all embedded information.\n *\n * Extracts province, regency, district codes, birth date, gender,\n * and serial number from a 16-digit NIK string.\n *\n * @param nik - The 16-digit NIK string to parse\n * @returns Parsed NIK information, or `null` if the NIK format is invalid\n *\n * @example\n * Parse a valid male NIK:\n * ```typescript\n * const info = parseNIK('3201018901310123');\n * console.log(info);\n * // {\n * // province: { code: '32', name: 'Jawa Barat' },\n * // regency: { code: '01', name: 'Kab. Bogor' },\n * // district: { code: '01', name: null },\n * // birthDate: Date(1989, 0, 31), // Jan 31, 1989\n * // gender: 'male',\n * // serialNumber: '0123',\n * // isValid: true\n * // }\n * ```\n *\n * @example\n * Parse a female NIK (day + 40):\n * ```typescript\n * const info = parseNIK('3201019508550123');\n * console.log(info.gender); // 'female'\n * console.log(info.birthDate); // Date(1995, 7, 15) - Aug 15, 1995\n * ```\n *\n * @example\n * Invalid NIK returns null:\n * ```typescript\n * const info = parseNIK('invalid');\n * console.log(info); // null\n * ```\n *\n * @public\n */\nexport function parseNIK(nik: string): NIKInfo | null {\n if (!/^\\d{16}$/.test(nik)) {\n return null;\n }\n\n const provinceCode = nik.substring(0, 2);\n const regencyCode = nik.substring(2, 4);\n const districtCode = nik.substring(4, 6);\n const yearStr = nik.substring(6, 8);\n const monthStr = nik.substring(8, 10);\n const dayStr = nik.substring(10, 12);\n const serialNumber = nik.substring(12, 16);\n\n const province = PROVINCES[provinceCode];\n if (!province) {\n return null;\n }\n\n const regencies = REGENCIES[provinceCode] || {};\n const regency = regencies[regencyCode] || 'Unknown';\n\n let day = parseInt(dayStr, 10);\n const month = parseInt(monthStr, 10);\n const year = parseInt(yearStr, 10);\n\n let gender: 'male' | 'female' | null = null;\n if (day > 40) {\n gender = 'female';\n day -= 40;\n } else {\n gender = 'male';\n }\n\n const fullYear = year > 30 ? 1900 + year : 2000 + year;\n\n const birthDate = new Date(fullYear, month - 1, day);\n if (\n birthDate.getFullYear() !== fullYear ||\n birthDate.getMonth() !== month - 1 ||\n birthDate.getDate() !== day\n ) {\n return null;\n }\n\n return {\n province: {\n code: provinceCode,\n name: province,\n },\n regency: {\n code: regencyCode,\n name: regency,\n },\n district: {\n code: districtCode,\n name: null,\n },\n birthDate,\n gender,\n serialNumber,\n isValid: true,\n };\n}","import { MaskOptions } from './types';\n\n/**\n * Formats a NIK with separators for better readability.\n *\n * Groups the NIK into logical segments: province, regency, district,\n * year, month, day, and serial number.\n *\n * @param nik - The 16-digit NIK string to format\n * @param separator - Character to use as separator\n * @returns Formatted NIK string, or original string if invalid format\n *\n * @example\n * Default separator (dash):\n * ```typescript\n * formatNIK('3201234567890123');\n * // '32-01-23-45-67-89-0123'\n * ```\n *\n * @example\n * Custom separator:\n * ```typescript\n * formatNIK('3201234567890123', ' ');\n * // '32 01 23 45 67 89 0123'\n * ```\n *\n * @example\n * Invalid NIK returns as-is:\n * ```typescript\n * formatNIK('1234');\n * // '1234'\n * ```\n *\n * @public\n */\nexport function formatNIK(nik: string, separator: string = '-'): string {\n if (!/^\\d{16}$/.test(nik)) {\n return nik;\n }\n\n // Format: PP-KK-DD-YY-MM-DD-XXXX\n // PP = Province (2 digits)\n // KK = Regency (2 digits)\n // DD = District (2 digits)\n // YY = Year (2 digits)\n // MM = Month (2 digits)\n // DD = Day (2 digits, +40 for female)\n // XXXX = Serial number (4 digits)\n return [\n nik.substring(0, 2), // Province\n nik.substring(2, 4), // Regency\n nik.substring(4, 6), // District\n nik.substring(6, 8), // Year\n nik.substring(8, 10), // Month\n nik.substring(10, 12), // Day\n nik.substring(12, 16), // Serial\n ].join(separator);\n}\n\n/**\n * Masks a NIK to protect privacy while keeping partial visibility.\n *\n * By default, shows the first 4 and last 4 digits, masking the middle 8.\n * Optionally formats the masked NIK with separators.\n *\n * @param nik - The 16-digit NIK string to mask\n * @param options - Masking configuration options\n * @returns Masked NIK string, or original string if invalid format\n *\n * @example\n * Default masking (first 4, last 4):\n * ```typescript\n * maskNIK('3201234567890123');\n * // '3201********0123'\n * ```\n *\n * @example\n * Custom mask character:\n * ```typescript\n * maskNIK('3201234567890123', { char: 'X' });\n * // '3201XXXXXXXX0123'\n * ```\n *\n * @example\n * With separator:\n * ```typescript\n * maskNIK('3201234567890123', { separator: '-' });\n * // '32-01-**-**-**-**-0123'\n * ```\n *\n * @example\n * Custom start and end:\n * ```typescript\n * maskNIK('3201234567890123', { start: 6, end: 4 });\n * // '320123******0123'\n * ```\n *\n * @public\n */\nexport function maskNIK(nik: string, options: MaskOptions = {}): string {\n if (!/^\\d{16}$/.test(nik)) {\n return nik;\n }\n\n const { start = 4, end = 4, char = '*', separator } = options;\n\n if (start + end >= 16) {\n return nik;\n }\n\n if (separator) {\n // Format with separator first, then apply masking\n const formatted = formatNIK(nik, separator);\n const parts = formatted.split(separator);\n\n // Calculate which parts to mask\n // Format: PP-KK-DD-YY-MM-DD-XXXX (7 parts)\n // Mask parts based on character positions\n let charCount = 0;\n const maskedParts = parts.map((part) => {\n const partStart = charCount;\n const partEnd = charCount + part.length;\n charCount += part.length;\n\n // Check if this part should be fully/partially masked\n if (partEnd <= start) {\n // Fully visible (before start)\n return part;\n } else if (partStart >= 16 - end) {\n // Fully visible (after end)\n return part;\n } else if (partStart >= start && partEnd <= 16 - end) {\n // Fully masked\n return char.repeat(part.length);\n } else {\n // Partially masked\n return part\n .split('')\n .map((ch, idx) => {\n const pos = partStart + idx;\n if (pos < start || pos >= 16 - end) {\n return ch;\n }\n return char;\n })\n .join('');\n }\n });\n\n return maskedParts.join(separator);\n }\n\n // Without separator: simple masking\n const startPart = nik.substring(0, start);\n const endPart = nik.substring(16 - end);\n const maskLength = 16 - start - end;\n return startPart + char.repeat(maskLength) + endPart;\n}","import { parseNIK } from './parse';\n\n/**\n * Calculates the age of a person based on their NIK.\n *\n * @param nik - The 16-digit NIK string\n * @param referenceDate - The date to calculate age from (default: current date)\n * @returns The age in years, or null if the NIK is invalid or birth date cannot be parsed\n *\n * @example\n * ```typescript\n * getAge('3201018901310123'); // 35 (as of 2024)\n * ```\n */\nexport function getAge(\n nik: string,\n referenceDate: Date = new Date()\n): number | null {\n const info = parseNIK(nik);\n if (!info || !info.birthDate) {\n return null;\n }\n\n const birthDate = info.birthDate;\n let age = referenceDate.getFullYear() - birthDate.getFullYear();\n const m = referenceDate.getMonth() - birthDate.getMonth();\n\n if (m < 0 || (m === 0 && referenceDate.getDate() < birthDate.getDate())) {\n age--;\n }\n\n return age;\n}\n\n/**\n * Formats the birth date from a NIK into a human-readable string.\n *\n * @param nik - The 16-digit NIK string\n * @param locale - The locale to use for formatting (default: 'id-ID')\n * @returns Formatted birth date string, or null if invalid\n *\n * @example\n * ```typescript\n * formatBirthDate('3201018901310123'); // '31 Januari 1989'\n * ```\n */\nexport function formatBirthDate(\n nik: string,\n options: Intl.DateTimeFormatOptions = {\n day: 'numeric',\n month: 'long',\n year: 'numeric',\n },\n locale: string = 'id-ID'\n): string | null {\n const info = parseNIK(nik);\n if (!info || !info.birthDate) {\n return null;\n }\n\n return new Intl.DateTimeFormat(locale, options).format(info.birthDate);\n}\n\n/**\n * Checks if a NIK matches a specific gender.\n *\n * @param nik - The 16-digit NIK string\n * @param gender - The gender to check ('male' | 'female')\n * @returns True if the NIK matches the gender, false otherwise\n */\nexport function isValidForGender(\n nik: string,\n gender: 'male' | 'female'\n): boolean {\n const info = parseNIK(nik);\n if (!info) {\n return false;\n }\n return info.gender === gender;\n}\n\n/**\n * Checks if a NIK matches a specific birth date.\n *\n * @param nik - The 16-digit NIK string\n * @param birthDate - The birth date to check\n * @returns True if the NIK matches the birth date, false otherwise\n */\nexport function isValidForBirthDate(nik: string, birthDate: Date): boolean {\n const info = parseNIK(nik);\n if (!info || !info.birthDate) {\n return false;\n }\n\n return (\n info.birthDate.getFullYear() === birthDate.getFullYear() &&\n info.birthDate.getMonth() === birthDate.getMonth() &&\n info.birthDate.getDate() === birthDate.getDate()\n );\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/nik/constants.ts","../../src/nik/utils/date.ts","../../src/nik/validate.ts","../../src/nik/parse.ts","../../src/nik/format.ts","../../src/nik/utils.ts","../../src/nik/types.ts"],"names":["NIK_PATTERN"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Indonesian province codes and names\n * Based on Dukcapil Kemendagri data\n */\nexport const PROVINCES: Record<string, string> = {\n '11': 'Aceh',\n '12': 'Sumatera Utara',\n '13': 'Sumatera Barat',\n '14': 'Riau',\n '15': 'Jambi',\n '16': 'Sumatera Selatan',\n '17': 'Bengkulu',\n '18': 'Lampung',\n '19': 'Kepulauan Bangka Belitung',\n '21': 'Kepulauan Riau',\n '31': 'DKI Jakarta',\n '32': 'Jawa Barat',\n '33': 'Jawa Tengah',\n '34': 'DI Yogyakarta',\n '35': 'Jawa Timur',\n '36': 'Banten',\n '51': 'Bali',\n '52': 'Nusa Tenggara Barat',\n '53': 'Nusa Tenggara Timur',\n '61': 'Kalimantan Barat',\n '62': 'Kalimantan Tengah',\n '63': 'Kalimantan Selatan',\n '64': 'Kalimantan Timur',\n '65': 'Kalimantan Utara',\n '71': 'Sulawesi Utara',\n '72': 'Sulawesi Tengah',\n '73': 'Sulawesi Selatan',\n '74': 'Sulawesi Tenggara',\n '75': 'Gorontalo',\n '76': 'Sulawesi Barat',\n '81': 'Maluku',\n '82': 'Maluku Utara',\n '91': 'Papua',\n '92': 'Papua Barat',\n '93': 'Papua Selatan',\n '94': 'Papua Tengah',\n '95': 'Papua Pegunungan',\n '96': 'Papua Barat Daya',\n};\n\n/**\n * Regency codes for each province (simplified - only major ones)\n * In a real implementation, you'd have complete data\n */\nexport const REGENCIES: Record<string, Record<string, string>> = {\n '32': {\n '01': 'Kab. Bogor',\n '02': 'Kab. Sukabumi',\n '03': 'Kab. Cianjur',\n '71': 'Kota Bandung',\n '72': 'Kota Bekasi',\n '73': 'Kota Depok',\n },\n '31': { \n '01': 'Kota Jakarta Selatan',\n '02': 'Kota Jakarta Timur',\n '03': 'Kota Jakarta Pusat',\n '04': 'Kota Jakarta Barat',\n '05': 'Kota Jakarta Utara',\n },\n};","/**\n * Shared date parsing and validation utilities for NIK module.\n *\n * @module nik/utils/date\n * @packageDocumentation\n */\n\n/**\n * Result of parsing date components from a NIK string.\n *\n * @public\n */\nexport interface ParsedNIKDate {\n /** 2-digit year code (00-99) from NIK positions 7-8 */\n year: number;\n /** Full 4-digit year (1900-1999 or 2000-2099) */\n fullYear: number;\n /** Month (1-12) from NIK positions 9-10 */\n month: number;\n /** Actual day (1-31), decoded from encoded day */\n day: number;\n /** Gender derived from day encoding */\n gender: 'male' | 'female';\n /** Original encoded day (1-31 for male, 41-71 for female) */\n dayEncoded: number;\n}\n\n/**\n * Parses date components from a NIK string.\n *\n * Extracts year, month, and encoded day from positions 7-12 of the NIK.\n * For females, the day is encoded as (actual day + 40).\n *\n * @param nik - 16-digit NIK string\n * @returns ParsedNIKDate or null if NIK format is invalid\n *\n * @example\n * ```typescript\n * const result = parseNIKDate('3201018901310123');\n * // { year: 89, fullYear: 1989, month: 1, day: 31, gender: 'male', dayEncoded: 31 }\n * ```\n *\n * @public\n */\nexport function parseNIKDate(nik: string): ParsedNIKDate | null {\n if (nik.length !== 16) {\n return null;\n }\n\n const yearStr = nik.substring(6, 8);\n const monthStr = nik.substring(8, 10);\n const dayEncodedStr = nik.substring(10, 12);\n\n const year = parseInt(yearStr, 10);\n if (isNaN(year)) return null;\n\n const fullYear = year > 30 ? 1900 + year : 2000 + year;\n\n const month = parseInt(monthStr, 10);\n if (isNaN(month)) return null;\n\n const dayEncoded = parseInt(dayEncodedStr, 10);\n if (isNaN(dayEncoded)) return null;\n\n const gender: 'male' | 'female' = dayEncoded > 40 ? 'female' : 'male';\n const day = dayEncoded > 40 ? dayEncoded - 40 : dayEncoded;\n\n return { year, fullYear, month, day, gender, dayEncoded };\n}\n\n/**\n * Validates if year/month/day form a valid calendar date.\n *\n * Uses JavaScript's Date object to check if the date actually exists\n * (handles leap years, invalid month/day combinations).\n *\n * @param year - Full 4-digit year\n * @param month - Month (1-12)\n * @param day - Day (1-31)\n * @returns true if valid calendar date\n *\n * @example\n * ```typescript\n * validateNIKDateComponents(2024, 2, 29); // true (leap year)\n * validateNIKDateComponents(2023, 2, 29); // false\n * validateNIKDateComponents(2024, 13, 1); // false\n * ```\n *\n * @public\n */\nexport function validateNIKDateComponents(\n year: number,\n month: number,\n day: number\n): boolean {\n if (month < 1 || month > 12) return false;\n if (day < 1 || day > 31) return false;\n\n const testDate = new Date(year, month - 1, day);\n return (\n testDate.getFullYear() === year &&\n testDate.getMonth() === month - 1 &&\n testDate.getDate() === day\n );\n}\n","import { PROVINCES } from './constants';\nimport { parseNIKDate, validateNIKDateComponents } from './utils/date';\n\nconst NIK_PATTERN = /^\\d{16}$/;\n\n/**\n * Validates a NIK (Nomor Induk Kependudukan) format.\n *\n * A valid NIK must:\n * - Be exactly 16 digits\n * - Have a valid province code (positions 1-2)\n * - Have a valid date (positions 7-12)\n * - Not be in the future\n * - Not be before 1900\n *\n * For female NIKs, the day is encoded as (actual day + 40).\n * For example, a female born on the 15th would have day = 55.\n *\n * @param nik - The 16-digit NIK string to validate\n * @returns `true` if the NIK is valid, `false` otherwise\n *\n * @example\n * ```typescript\n * validateNIK('3201234567890123'); // true - valid NIK\n * validateNIK('1234'); // false - wrong length\n * validateNIK('9912345678901234'); // false - invalid province\n * ```\n *\n * @public\n */\nexport function validateNIK(nik: string): boolean {\n if (!NIK_PATTERN.test(nik)) {\n return false;\n }\n\n const provinceCode = nik.substring(0, 2);\n if (!PROVINCES[provinceCode]) {\n return false;\n }\n\n const parsed = parseNIKDate(nik);\n if (!parsed) {\n return false;\n }\n\n const { fullYear, month, day } = parsed;\n\n if (!validateNIKDateComponents(fullYear, month, day)) {\n return false;\n }\n\n const now = new Date();\n if (new Date(fullYear, month - 1, day) > now || fullYear < 1900) {\n return false;\n }\n\n return true;\n}\n","import { PROVINCES, REGENCIES } from './constants';\nimport { NIKInfo } from './types';\nimport { parseNIKDate, validateNIKDateComponents } from './utils/date';\n\nconst NIK_PATTERN = /^\\d{16}$/;\n\n/**\n * Parses a NIK and extracts all embedded information.\n *\n * Extracts province, regency, district codes, birth date, gender,\n * and serial number from a 16-digit NIK string.\n *\n * @param nik - The 16-digit NIK string to parse\n * @returns Parsed NIK information, or `null` if the NIK format is invalid\n *\n * @example\n * Parse a valid male NIK:\n * ```typescript\n * const info = parseNIK('3201018901310123');\n * console.log(info);\n * // {\n * // province: { code: '32', name: 'Jawa Barat' },\n * // regency: { code: '01', name: 'Kab. Bogor' },\n * // district: { code: '01', name: null },\n * // birthDate: Date(1989, 0, 31), // Jan 31, 1989\n * // gender: 'male',\n * // serialNumber: '0123',\n * // isValid: true\n * // }\n * ```\n *\n * @example\n * Parse a female NIK (day + 40):\n * ```typescript\n * const info = parseNIK('3201019508550123');\n * console.log(info.gender); // 'female'\n * console.log(info.birthDate); // Date(1995, 7, 15) - Aug 15, 1995\n * ```\n *\n * @example\n * Invalid NIK returns null:\n * ```typescript\n * const info = parseNIK('invalid');\n * console.log(info); // null\n * ```\n *\n * @public\n */\nexport function parseNIK(nik: string): NIKInfo | null {\n if (!NIK_PATTERN.test(nik)) {\n return null;\n }\n\n const provinceCode = nik.substring(0, 2);\n const regencyCode = nik.substring(2, 4);\n const districtCode = nik.substring(4, 6);\n const serialNumber = nik.substring(12, 16);\n\n const province = PROVINCES[provinceCode];\n if (!province) {\n return null;\n }\n\n const regencies = REGENCIES[provinceCode] || {};\n const regency = regencies[regencyCode] || 'Unknown';\n\n const parsed = parseNIKDate(nik);\n if (!parsed) {\n return null;\n }\n\n const { fullYear, month, day, gender } = parsed;\n\n if (!validateNIKDateComponents(fullYear, month, day)) {\n return null;\n }\n\n const birthDate = new Date(fullYear, month - 1, day);\n\n return {\n province: {\n code: provinceCode,\n name: province,\n },\n regency: {\n code: regencyCode,\n name: regency,\n },\n district: {\n code: districtCode,\n name: null,\n },\n birthDate,\n gender,\n serialNumber,\n isValid: true,\n };\n}\n","import { MaskOptions } from './types';\n\n/**\n * Formats a NIK with separators for better readability.\n *\n * Groups the NIK into logical segments: province, regency, district,\n * year, month, day, and serial number.\n *\n * @param nik - The 16-digit NIK string to format\n * @param separator - Character to use as separator\n * @returns Formatted NIK string, or original string if invalid format\n *\n * @example\n * Default separator (dash):\n * ```typescript\n * formatNIK('3201234567890123');\n * // '32-01-23-45-67-89-0123'\n * ```\n *\n * @example\n * Custom separator:\n * ```typescript\n * formatNIK('3201234567890123', ' ');\n * // '32 01 23 45 67 89 0123'\n * ```\n *\n * @example\n * Invalid NIK returns as-is:\n * ```typescript\n * formatNIK('1234');\n * // '1234'\n * ```\n *\n * @public\n */\nexport function formatNIK(nik: string, separator: string = '-'): string {\n if (!/^\\d{16}$/.test(nik)) {\n return nik;\n }\n\n // Format: PP-KK-DD-YY-MM-DD-XXXX\n // PP = Province (2 digits)\n // KK = Regency (2 digits)\n // DD = District (2 digits)\n // YY = Year (2 digits)\n // MM = Month (2 digits)\n // DD = Day (2 digits, +40 for female)\n // XXXX = Serial number (4 digits)\n return [\n nik.substring(0, 2), // Province\n nik.substring(2, 4), // Regency\n nik.substring(4, 6), // District\n nik.substring(6, 8), // Year\n nik.substring(8, 10), // Month\n nik.substring(10, 12), // Day\n nik.substring(12, 16), // Serial\n ].join(separator);\n}\n\n/**\n * Masks a NIK to protect privacy while keeping partial visibility.\n *\n * By default, shows the first 4 and last 4 digits, masking the middle 8.\n * Optionally formats the masked NIK with separators.\n *\n * @param nik - The 16-digit NIK string to mask\n * @param options - Masking configuration options\n * @returns Masked NIK string, or original string if invalid format\n *\n * @example\n * Default masking (first 4, last 4):\n * ```typescript\n * maskNIK('3201234567890123');\n * // '3201********0123'\n * ```\n *\n * @example\n * Custom mask character:\n * ```typescript\n * maskNIK('3201234567890123', { char: 'X' });\n * // '3201XXXXXXXX0123'\n * ```\n *\n * @example\n * With separator:\n * ```typescript\n * maskNIK('3201234567890123', { separator: '-' });\n * // '32-01-**-**-**-**-0123'\n * ```\n *\n * @example\n * Custom start and end:\n * ```typescript\n * maskNIK('3201234567890123', { start: 6, end: 4 });\n * // '320123******0123'\n * ```\n *\n * @public\n */\nexport function maskNIK(nik: string, options: MaskOptions = {}): string {\n if (!/^\\d{16}$/.test(nik)) {\n return nik;\n }\n\n const { start = 4, end = 4, char = '*', separator } = options;\n\n if (start + end >= 16) {\n return nik;\n }\n\n if (separator) {\n // Format with separator first, then apply masking\n const formatted = formatNIK(nik, separator);\n const parts = formatted.split(separator);\n\n // Calculate which parts to mask\n // Format: PP-KK-DD-YY-MM-DD-XXXX (7 parts)\n // Mask parts based on character positions\n let charCount = 0;\n const maskedParts = parts.map((part) => {\n const partStart = charCount;\n const partEnd = charCount + part.length;\n charCount += part.length;\n\n // Check if this part should be fully/partially masked\n if (partEnd <= start) {\n // Fully visible (before start)\n return part;\n } else if (partStart >= 16 - end) {\n // Fully visible (after end)\n return part;\n } else if (partStart >= start && partEnd <= 16 - end) {\n // Fully masked\n return char.repeat(part.length);\n } else {\n // Partially masked\n return part\n .split('')\n .map((ch, idx) => {\n const pos = partStart + idx;\n if (pos < start || pos >= 16 - end) {\n return ch;\n }\n return char;\n })\n .join('');\n }\n });\n\n return maskedParts.join(separator);\n }\n\n // Without separator: simple masking\n const startPart = nik.substring(0, start);\n const endPart = nik.substring(16 - end);\n const maskLength = 16 - start - end;\n return startPart + char.repeat(maskLength) + endPart;\n}","import { parseNIK } from './parse';\n\n/**\n * Calculates the age of a person based on their NIK.\n *\n * @param nik - The 16-digit NIK string\n * @param referenceDate - The date to calculate age from (default: current date)\n * @returns The age in years, or null if the NIK is invalid or birth date cannot be parsed\n *\n * @example\n * ```typescript\n * getAge('3201018901310123'); // 35 (as of 2024)\n * ```\n */\nexport function getAge(\n nik: string,\n referenceDate: Date = new Date()\n): number | null {\n const info = parseNIK(nik);\n if (!info || !info.birthDate) {\n return null;\n }\n\n const birthDate = info.birthDate;\n let age = referenceDate.getFullYear() - birthDate.getFullYear();\n const m = referenceDate.getMonth() - birthDate.getMonth();\n\n if (m < 0 || (m === 0 && referenceDate.getDate() < birthDate.getDate())) {\n age--;\n }\n\n return age;\n}\n\n/**\n * Formats the birth date from a NIK into a human-readable string.\n *\n * @param nik - The 16-digit NIK string\n * @param locale - The locale to use for formatting (default: 'id-ID')\n * @returns Formatted birth date string, or null if invalid\n *\n * @example\n * ```typescript\n * formatBirthDate('3201018901310123'); // '31 Januari 1989'\n * ```\n */\nexport function formatBirthDate(\n nik: string,\n options: Intl.DateTimeFormatOptions = {\n day: 'numeric',\n month: 'long',\n year: 'numeric',\n },\n locale: string = 'id-ID'\n): string | null {\n const info = parseNIK(nik);\n if (!info || !info.birthDate) {\n return null;\n }\n\n return new Intl.DateTimeFormat(locale, options).format(info.birthDate);\n}\n\n/**\n * Checks if a NIK matches a specific gender.\n *\n * @param nik - The 16-digit NIK string\n * @param gender - The gender to check ('male' | 'female')\n * @returns True if the NIK matches the gender, false otherwise\n */\nexport function isValidForGender(\n nik: string,\n gender: 'male' | 'female'\n): boolean {\n const info = parseNIK(nik);\n if (!info) {\n return false;\n }\n return info.gender === gender;\n}\n\n/**\n * Checks if a NIK matches a specific birth date.\n *\n * @param nik - The 16-digit NIK string\n * @param birthDate - The birth date to check\n * @returns True if the NIK matches the birth date, false otherwise\n */\nexport function isValidForBirthDate(nik: string, birthDate: Date): boolean {\n const info = parseNIK(nik);\n if (!info || !info.birthDate) {\n return false;\n }\n\n return (\n info.birthDate.getFullYear() === birthDate.getFullYear() &&\n info.birthDate.getMonth() === birthDate.getMonth() &&\n info.birthDate.getDate() === birthDate.getDate()\n );\n}\n","/**\n * Information extracted from a valid NIK.\n *\n * Contains parsed data including location codes, birth date, gender,\n * and serial number from a 16-digit NIK string.\n *\n * @public\n */\nexport interface NIKInfo {\n /**\n * Province information extracted from positions 1-2 of the NIK.\n *\n * @example\n * ```typescript\n * { code: '32', name: 'Jawa Barat' }\n * ```\n */\n province: {\n /** Two-digit province code (e.g., '32') */\n code: string;\n /** Full province name (e.g., 'Jawa Barat') */\n name: string;\n };\n\n /**\n * Regency (Kabupaten/Kota) information extracted from positions 3-4 of the NIK.\n *\n * @example\n * ```typescript\n * { code: '01', name: 'Kab. Bogor' }\n * ```\n */\n regency: {\n /** Two-digit regency code (e.g., '01') */\n code: string;\n /** Full regency name (e.g., 'Kab. Bogor') */\n name: string;\n };\n\n /**\n * District (Kecamatan) information extracted from positions 5-6 of the NIK.\n * May be `null` if district data is not available.\n *\n * @example\n * ```typescript\n * { code: '23', name: 'Ciawi' }\n * ```\n */\n district: {\n /** Two-digit district code (e.g., '23') */\n code: string;\n /** Full district name, or `null` if data unavailable */\n name: string | null;\n };\n\n /**\n * Birth date extracted from positions 7-12 of the NIK.\n * For females, the day is encoded as (actual day + 40).\n * Returns `null` if the date is invalid.\n *\n * @example\n * ```typescript\n * new Date(1989, 0, 31) // January 31, 1989\n * ```\n */\n birthDate: Date | null;\n\n /**\n * Gender derived from the day encoding in the NIK.\n * - 'male': day is 1-31\n * - 'female': day is 41-71 (actual day + 40)\n * - `null`: if unable to determine\n */\n gender: 'male' | 'female' | null;\n\n /**\n * Serial number from positions 13-16 of the NIK.\n * Uniquely identifies individuals with the same location and birth date.\n * Returns `null` if unable to extract.\n *\n * @example\n * ```typescript\n * '0123'\n * ```\n */\n serialNumber: string | null;\n\n /**\n * Whether the NIK passed validation checks.\n * If `false`, other fields may be `null` or contain partial data.\n */\n isValid: boolean;\n}\n\n/**\n * Options for masking a NIK to protect privacy.\n *\n * Controls how many characters to show at the start and end,\n * what character to use for masking, and optional separators.\n *\n * @example\n * ```typescript\n * // Default: shows first 4 and last 4 digits\n * { start: 4, end: 4, char: '*' }\n * // Result: '3201********0123'\n *\n * // With separator\n * { start: 4, end: 4, char: '*', separator: '-' }\n * // Result: '3201-****-****-0123'\n * ```\n *\n * @public\n */\nexport interface MaskOptions {\n /**\n * Number of characters to show at the start.\n *\n * @defaultValue 4\n */\n start?: number;\n\n /**\n * Number of characters to show at the end.\n *\n * @defaultValue 4\n */\n end?: number;\n\n /**\n * Character to use for masking hidden digits.\n *\n * @defaultValue '*'\n */\n char?: string;\n\n /**\n * Optional separator to add between groups of digits.\n * If provided, the NIK will be formatted with separators.\n *\n * @defaultValue undefined (no separator)\n *\n * @example\n * ```typescript\n * '-' // Results in format: '3201-****-****-0123'\n * ' ' // Results in format: '3201 **** **** 0123'\n * ```\n */\n separator?: string;\n}\n\n/**\n * Error thrown when an invalid NIK is provided to a function.\n * Extends native Error with a `code` property for programmatic error handling.\n *\n * @example\n * ```typescript\n * try {\n * requireNIK('invalid');\n * } catch (error) {\n * if (error instanceof InvalidNIKError) {\n * console.log(error.code); // 'INVALID_NIK'\n * }\n * }\n * ```\n *\n * @public\n */\nexport class InvalidNIKError extends Error {\n /** Error code for programmatic identification */\n readonly code = 'INVALID_NIK' as const;\n\n constructor(message: string = 'Invalid NIK provided') {\n super(message);\n this.name = 'InvalidNIKError';\n }\n}\n"]}
|
package/dist/nik/index.d.cts
CHANGED
|
@@ -164,6 +164,28 @@ interface MaskOptions {
|
|
|
164
164
|
*/
|
|
165
165
|
separator?: string;
|
|
166
166
|
}
|
|
167
|
+
/**
|
|
168
|
+
* Error thrown when an invalid NIK is provided to a function.
|
|
169
|
+
* Extends native Error with a `code` property for programmatic error handling.
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* ```typescript
|
|
173
|
+
* try {
|
|
174
|
+
* requireNIK('invalid');
|
|
175
|
+
* } catch (error) {
|
|
176
|
+
* if (error instanceof InvalidNIKError) {
|
|
177
|
+
* console.log(error.code); // 'INVALID_NIK'
|
|
178
|
+
* }
|
|
179
|
+
* }
|
|
180
|
+
* ```
|
|
181
|
+
*
|
|
182
|
+
* @public
|
|
183
|
+
*/
|
|
184
|
+
declare class InvalidNIKError extends Error {
|
|
185
|
+
/** Error code for programmatic identification */
|
|
186
|
+
readonly code: "INVALID_NIK";
|
|
187
|
+
constructor(message?: string);
|
|
188
|
+
}
|
|
167
189
|
|
|
168
190
|
/**
|
|
169
191
|
* Parses a NIK and extracts all embedded information.
|
|
@@ -328,4 +350,4 @@ declare function isValidForGender(nik: string, gender: 'male' | 'female'): boole
|
|
|
328
350
|
*/
|
|
329
351
|
declare function isValidForBirthDate(nik: string, birthDate: Date): boolean;
|
|
330
352
|
|
|
331
|
-
export { type MaskOptions, type NIKInfo, formatBirthDate, formatNIK, getAge, isValidForBirthDate, isValidForGender, maskNIK, parseNIK, validateNIK };
|
|
353
|
+
export { InvalidNIKError, type MaskOptions, type NIKInfo, formatBirthDate, formatNIK, getAge, isValidForBirthDate, isValidForGender, maskNIK, parseNIK, validateNIK };
|
package/dist/nik/index.d.ts
CHANGED
|
@@ -164,6 +164,28 @@ interface MaskOptions {
|
|
|
164
164
|
*/
|
|
165
165
|
separator?: string;
|
|
166
166
|
}
|
|
167
|
+
/**
|
|
168
|
+
* Error thrown when an invalid NIK is provided to a function.
|
|
169
|
+
* Extends native Error with a `code` property for programmatic error handling.
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* ```typescript
|
|
173
|
+
* try {
|
|
174
|
+
* requireNIK('invalid');
|
|
175
|
+
* } catch (error) {
|
|
176
|
+
* if (error instanceof InvalidNIKError) {
|
|
177
|
+
* console.log(error.code); // 'INVALID_NIK'
|
|
178
|
+
* }
|
|
179
|
+
* }
|
|
180
|
+
* ```
|
|
181
|
+
*
|
|
182
|
+
* @public
|
|
183
|
+
*/
|
|
184
|
+
declare class InvalidNIKError extends Error {
|
|
185
|
+
/** Error code for programmatic identification */
|
|
186
|
+
readonly code: "INVALID_NIK";
|
|
187
|
+
constructor(message?: string);
|
|
188
|
+
}
|
|
167
189
|
|
|
168
190
|
/**
|
|
169
191
|
* Parses a NIK and extracts all embedded information.
|
|
@@ -328,4 +350,4 @@ declare function isValidForGender(nik: string, gender: 'male' | 'female'): boole
|
|
|
328
350
|
*/
|
|
329
351
|
declare function isValidForBirthDate(nik: string, birthDate: Date): boolean;
|
|
330
352
|
|
|
331
|
-
export { type MaskOptions, type NIKInfo, formatBirthDate, formatNIK, getAge, isValidForBirthDate, isValidForGender, maskNIK, parseNIK, validateNIK };
|
|
353
|
+
export { InvalidNIKError, type MaskOptions, type NIKInfo, formatBirthDate, formatNIK, getAge, isValidForBirthDate, isValidForGender, maskNIK, parseNIK, validateNIK };
|
package/dist/nik/index.js
CHANGED
|
@@ -57,53 +57,66 @@ var REGENCIES = {
|
|
|
57
57
|
}
|
|
58
58
|
};
|
|
59
59
|
|
|
60
|
-
// src/nik/
|
|
61
|
-
function
|
|
62
|
-
if (
|
|
63
|
-
return
|
|
64
|
-
}
|
|
65
|
-
const provinceCode = nik.substring(0, 2);
|
|
66
|
-
if (!PROVINCES[provinceCode]) {
|
|
67
|
-
return false;
|
|
60
|
+
// src/nik/utils/date.ts
|
|
61
|
+
function parseNIKDate(nik) {
|
|
62
|
+
if (nik.length !== 16) {
|
|
63
|
+
return null;
|
|
68
64
|
}
|
|
69
65
|
const yearStr = nik.substring(6, 8);
|
|
70
66
|
const monthStr = nik.substring(8, 10);
|
|
71
|
-
const
|
|
67
|
+
const dayEncodedStr = nik.substring(10, 12);
|
|
72
68
|
const year = parseInt(yearStr, 10);
|
|
69
|
+
if (isNaN(year)) return null;
|
|
73
70
|
const fullYear = year > 30 ? 1900 + year : 2e3 + year;
|
|
74
71
|
const month = parseInt(monthStr, 10);
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
72
|
+
if (isNaN(month)) return null;
|
|
73
|
+
const dayEncoded = parseInt(dayEncodedStr, 10);
|
|
74
|
+
if (isNaN(dayEncoded)) return null;
|
|
75
|
+
const gender = dayEncoded > 40 ? "female" : "male";
|
|
76
|
+
const day = dayEncoded > 40 ? dayEncoded - 40 : dayEncoded;
|
|
77
|
+
return { year, fullYear, month, day, gender, dayEncoded };
|
|
78
|
+
}
|
|
79
|
+
function validateNIKDateComponents(year, month, day) {
|
|
80
|
+
if (month < 1 || month > 12) return false;
|
|
81
|
+
if (day < 1 || day > 31) return false;
|
|
82
|
+
const testDate = new Date(year, month - 1, day);
|
|
83
|
+
return testDate.getFullYear() === year && testDate.getMonth() === month - 1 && testDate.getDate() === day;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// src/nik/validate.ts
|
|
87
|
+
var NIK_PATTERN = /^\d{16}$/;
|
|
88
|
+
function validateNIK(nik) {
|
|
89
|
+
if (!NIK_PATTERN.test(nik)) {
|
|
90
|
+
return false;
|
|
78
91
|
}
|
|
79
|
-
|
|
92
|
+
const provinceCode = nik.substring(0, 2);
|
|
93
|
+
if (!PROVINCES[provinceCode]) {
|
|
80
94
|
return false;
|
|
81
95
|
}
|
|
82
|
-
|
|
96
|
+
const parsed = parseNIKDate(nik);
|
|
97
|
+
if (!parsed) {
|
|
83
98
|
return false;
|
|
84
99
|
}
|
|
85
|
-
const
|
|
86
|
-
if (
|
|
100
|
+
const { fullYear, month, day } = parsed;
|
|
101
|
+
if (!validateNIKDateComponents(fullYear, month, day)) {
|
|
87
102
|
return false;
|
|
88
103
|
}
|
|
89
104
|
const now = /* @__PURE__ */ new Date();
|
|
90
|
-
if (
|
|
105
|
+
if (new Date(fullYear, month - 1, day) > now || fullYear < 1900) {
|
|
91
106
|
return false;
|
|
92
107
|
}
|
|
93
108
|
return true;
|
|
94
109
|
}
|
|
95
110
|
|
|
96
111
|
// src/nik/parse.ts
|
|
112
|
+
var NIK_PATTERN2 = /^\d{16}$/;
|
|
97
113
|
function parseNIK(nik) {
|
|
98
|
-
if (
|
|
114
|
+
if (!NIK_PATTERN2.test(nik)) {
|
|
99
115
|
return null;
|
|
100
116
|
}
|
|
101
117
|
const provinceCode = nik.substring(0, 2);
|
|
102
118
|
const regencyCode = nik.substring(2, 4);
|
|
103
119
|
const districtCode = nik.substring(4, 6);
|
|
104
|
-
const yearStr = nik.substring(6, 8);
|
|
105
|
-
const monthStr = nik.substring(8, 10);
|
|
106
|
-
const dayStr = nik.substring(10, 12);
|
|
107
120
|
const serialNumber = nik.substring(12, 16);
|
|
108
121
|
const province = PROVINCES[provinceCode];
|
|
109
122
|
if (!province) {
|
|
@@ -111,21 +124,15 @@ function parseNIK(nik) {
|
|
|
111
124
|
}
|
|
112
125
|
const regencies = REGENCIES[provinceCode] || {};
|
|
113
126
|
const regency = regencies[regencyCode] || "Unknown";
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
let gender = null;
|
|
118
|
-
if (day > 40) {
|
|
119
|
-
gender = "female";
|
|
120
|
-
day -= 40;
|
|
121
|
-
} else {
|
|
122
|
-
gender = "male";
|
|
127
|
+
const parsed = parseNIKDate(nik);
|
|
128
|
+
if (!parsed) {
|
|
129
|
+
return null;
|
|
123
130
|
}
|
|
124
|
-
const fullYear
|
|
125
|
-
|
|
126
|
-
if (birthDate.getFullYear() !== fullYear || birthDate.getMonth() !== month - 1 || birthDate.getDate() !== day) {
|
|
131
|
+
const { fullYear, month, day, gender } = parsed;
|
|
132
|
+
if (!validateNIKDateComponents(fullYear, month, day)) {
|
|
127
133
|
return null;
|
|
128
134
|
}
|
|
135
|
+
const birthDate = new Date(fullYear, month - 1, day);
|
|
129
136
|
return {
|
|
130
137
|
province: {
|
|
131
138
|
code: provinceCode,
|
|
@@ -248,6 +255,16 @@ function isValidForBirthDate(nik, birthDate) {
|
|
|
248
255
|
return info.birthDate.getFullYear() === birthDate.getFullYear() && info.birthDate.getMonth() === birthDate.getMonth() && info.birthDate.getDate() === birthDate.getDate();
|
|
249
256
|
}
|
|
250
257
|
|
|
251
|
-
|
|
258
|
+
// src/nik/types.ts
|
|
259
|
+
var InvalidNIKError = class extends Error {
|
|
260
|
+
constructor(message = "Invalid NIK provided") {
|
|
261
|
+
super(message);
|
|
262
|
+
/** Error code for programmatic identification */
|
|
263
|
+
this.code = "INVALID_NIK";
|
|
264
|
+
this.name = "InvalidNIKError";
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
export { InvalidNIKError, formatBirthDate, formatNIK, getAge, isValidForBirthDate, isValidForGender, maskNIK, parseNIK, validateNIK };
|
|
252
269
|
//# sourceMappingURL=index.js.map
|
|
253
270
|
//# sourceMappingURL=index.js.map
|
package/dist/nik/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/nik/constants.ts","../../src/nik/validate.ts","../../src/nik/parse.ts","../../src/nik/format.ts","../../src/nik/utils.ts"],"names":[],"mappings":";AAIO,IAAM,SAAA,GAAoC;AAAA,EAC/C,IAAA,EAAM,MAAA;AAAA,EACN,IAAA,EAAM,gBAAA;AAAA,EACN,IAAA,EAAM,gBAAA;AAAA,EACN,IAAA,EAAM,MAAA;AAAA,EACN,IAAA,EAAM,OAAA;AAAA,EACN,IAAA,EAAM,kBAAA;AAAA,EACN,IAAA,EAAM,UAAA;AAAA,EACN,IAAA,EAAM,SAAA;AAAA,EACN,IAAA,EAAM,2BAAA;AAAA,EACN,IAAA,EAAM,gBAAA;AAAA,EACN,IAAA,EAAM,aAAA;AAAA,EACN,IAAA,EAAM,YAAA;AAAA,EACN,IAAA,EAAM,aAAA;AAAA,EACN,IAAA,EAAM,eAAA;AAAA,EACN,IAAA,EAAM,YAAA;AAAA,EACN,IAAA,EAAM,QAAA;AAAA,EACN,IAAA,EAAM,MAAA;AAAA,EACN,IAAA,EAAM,qBAAA;AAAA,EACN,IAAA,EAAM,qBAAA;AAAA,EACN,IAAA,EAAM,kBAAA;AAAA,EACN,IAAA,EAAM,mBAAA;AAAA,EACN,IAAA,EAAM,oBAAA;AAAA,EACN,IAAA,EAAM,kBAAA;AAAA,EACN,IAAA,EAAM,kBAAA;AAAA,EACN,IAAA,EAAM,gBAAA;AAAA,EACN,IAAA,EAAM,iBAAA;AAAA,EACN,IAAA,EAAM,kBAAA;AAAA,EACN,IAAA,EAAM,mBAAA;AAAA,EACN,IAAA,EAAM,WAAA;AAAA,EACN,IAAA,EAAM,gBAAA;AAAA,EACN,IAAA,EAAM,QAAA;AAAA,EACN,IAAA,EAAM,cAAA;AAAA,EACN,IAAA,EAAM,OAAA;AAAA,EACN,IAAA,EAAM,aAAA;AAAA,EACN,IAAA,EAAM,eAAA;AAAA,EACN,IAAA,EAAM,cAAA;AAAA,EACN,IAAA,EAAM,kBAAA;AAAA,EACN,IAAA,EAAM;AACR,CAAA;AAMO,IAAM,SAAA,GAAoD;AAAA,EAC/D,IAAA,EAAM;AAAA,IACJ,IAAA,EAAM,YAAA;AAAA,IACN,IAAA,EAAM,eAAA;AAAA,IACN,IAAA,EAAM,cAAA;AAAA,IACN,IAAA,EAAM,cAAA;AAAA,IACN,IAAA,EAAM,aAAA;AAAA,IACN,IAAA,EAAM;AAAA,GACR;AAAA,EACA,IAAA,EAAM;AAAA,IACJ,IAAA,EAAM,sBAAA;AAAA,IACN,IAAA,EAAM,oBAAA;AAAA,IACN,IAAA,EAAM,oBAAA;AAAA,IACN,IAAA,EAAM,oBAAA;AAAA,IACN,IAAA,EAAM;AAAA;AAEV,CAAA;;;ACtCO,SAAS,YAAY,GAAA,EAAsB;AAChD,EAAA,IAAI,CAAC,UAAA,CAAW,IAAA,CAAK,GAAG,CAAA,EAAG;AACzB,IAAA,OAAO,KAAA;AAAA,EACT;AAEA,EAAA,MAAM,YAAA,GAAe,GAAA,CAAI,SAAA,CAAU,CAAA,EAAG,CAAC,CAAA;AACvC,EAAA,IAAI,CAAC,SAAA,CAAU,YAAY,CAAA,EAAG;AAC5B,IAAA,OAAO,KAAA;AAAA,EACT;AAEA,EAAA,MAAM,OAAA,GAAU,GAAA,CAAI,SAAA,CAAU,CAAA,EAAG,CAAC,CAAA;AAClC,EAAA,MAAM,QAAA,GAAW,GAAA,CAAI,SAAA,CAAU,CAAA,EAAG,EAAE,CAAA;AACpC,EAAA,MAAM,MAAA,GAAS,GAAA,CAAI,SAAA,CAAU,EAAA,EAAI,EAAE,CAAA;AAEnC,EAAA,MAAM,IAAA,GAAO,QAAA,CAAS,OAAA,EAAS,EAAE,CAAA;AACjC,EAAA,MAAM,QAAA,GAAW,IAAA,GAAO,EAAA,GAAK,IAAA,GAAO,OAAO,GAAA,GAAO,IAAA;AAElD,EAAA,MAAM,KAAA,GAAQ,QAAA,CAAS,QAAA,EAAU,EAAE,CAAA;AACnC,EAAA,IAAI,GAAA,GAAM,QAAA,CAAS,MAAA,EAAQ,EAAE,CAAA;AAE7B,EAAA,IAAI,MAAM,EAAA,EAAI;AACZ,IAAA,GAAA,GAAM,GAAA,GAAM,EAAA;AAAA,EACd;AAEA,EAAA,IAAI,KAAA,GAAQ,CAAA,IAAK,KAAA,GAAQ,EAAA,EAAI;AAC3B,IAAA,OAAO,KAAA;AAAA,EACT;AAEA,EAAA,IAAI,GAAA,GAAM,CAAA,IAAK,GAAA,GAAM,EAAA,EAAI;AACvB,IAAA,OAAO,KAAA;AAAA,EACT;AAEA,EAAA,MAAM,WAAW,IAAI,IAAA,CAAK,QAAA,EAAU,KAAA,GAAQ,GAAG,GAAG,CAAA;AAClD,EAAA,IACE,QAAA,CAAS,WAAA,EAAY,KAAM,QAAA,IAC3B,QAAA,CAAS,QAAA,EAAS,KAAM,KAAA,GAAQ,CAAA,IAChC,QAAA,CAAS,OAAA,EAAQ,KAAM,GAAA,EACvB;AACA,IAAA,OAAO,KAAA;AAAA,EACT;AAEA,EAAA,MAAM,GAAA,uBAAU,IAAA,EAAK;AACrB,EAAA,IAAI,QAAA,GAAW,OAAO,QAAA,GAAW,IAAI,KAAK,IAAA,EAAM,CAAA,EAAG,CAAC,CAAA,EAAG;AACrD,IAAA,OAAO,KAAA;AAAA,EACT;AAEA,EAAA,OAAO,IAAA;AACT;;;AC7BO,SAAS,SAAS,GAAA,EAA6B;AACpD,EAAA,IAAI,CAAC,UAAA,CAAW,IAAA,CAAK,GAAG,CAAA,EAAG;AACzB,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,MAAM,YAAA,GAAe,GAAA,CAAI,SAAA,CAAU,CAAA,EAAG,CAAC,CAAA;AACvC,EAAA,MAAM,WAAA,GAAc,GAAA,CAAI,SAAA,CAAU,CAAA,EAAG,CAAC,CAAA;AACtC,EAAA,MAAM,YAAA,GAAe,GAAA,CAAI,SAAA,CAAU,CAAA,EAAG,CAAC,CAAA;AACvC,EAAA,MAAM,OAAA,GAAU,GAAA,CAAI,SAAA,CAAU,CAAA,EAAG,CAAC,CAAA;AAClC,EAAA,MAAM,QAAA,GAAW,GAAA,CAAI,SAAA,CAAU,CAAA,EAAG,EAAE,CAAA;AACpC,EAAA,MAAM,MAAA,GAAS,GAAA,CAAI,SAAA,CAAU,EAAA,EAAI,EAAE,CAAA;AACnC,EAAA,MAAM,YAAA,GAAe,GAAA,CAAI,SAAA,CAAU,EAAA,EAAI,EAAE,CAAA;AAEzC,EAAA,MAAM,QAAA,GAAW,UAAU,YAAY,CAAA;AACvC,EAAA,IAAI,CAAC,QAAA,EAAU;AACb,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,MAAM,SAAA,GAAY,SAAA,CAAU,YAAY,CAAA,IAAK,EAAC;AAC9C,EAAA,MAAM,OAAA,GAAU,SAAA,CAAU,WAAW,CAAA,IAAK,SAAA;AAE1C,EAAA,IAAI,GAAA,GAAM,QAAA,CAAS,MAAA,EAAQ,EAAE,CAAA;AAC7B,EAAA,MAAM,KAAA,GAAQ,QAAA,CAAS,QAAA,EAAU,EAAE,CAAA;AACnC,EAAA,MAAM,IAAA,GAAO,QAAA,CAAS,OAAA,EAAS,EAAE,CAAA;AAEjC,EAAA,IAAI,MAAA,GAAmC,IAAA;AACvC,EAAA,IAAI,MAAM,EAAA,EAAI;AACZ,IAAA,MAAA,GAAS,QAAA;AACT,IAAA,GAAA,IAAO,EAAA;AAAA,EACT,CAAA,MAAO;AACL,IAAA,MAAA,GAAS,MAAA;AAAA,EACX;AAEA,EAAA,MAAM,QAAA,GAAW,IAAA,GAAO,EAAA,GAAK,IAAA,GAAO,OAAO,GAAA,GAAO,IAAA;AAElD,EAAA,MAAM,YAAY,IAAI,IAAA,CAAK,QAAA,EAAU,KAAA,GAAQ,GAAG,GAAG,CAAA;AACnD,EAAA,IACE,SAAA,CAAU,WAAA,EAAY,KAAM,QAAA,IAC5B,SAAA,CAAU,QAAA,EAAS,KAAM,KAAA,GAAQ,CAAA,IACjC,SAAA,CAAU,OAAA,EAAQ,KAAM,GAAA,EACxB;AACA,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,OAAO;AAAA,IACL,QAAA,EAAU;AAAA,MACR,IAAA,EAAM,YAAA;AAAA,MACN,IAAA,EAAM;AAAA,KACR;AAAA,IACA,OAAA,EAAS;AAAA,MACP,IAAA,EAAM,WAAA;AAAA,MACN,IAAA,EAAM;AAAA,KACR;AAAA,IACA,QAAA,EAAU;AAAA,MACR,IAAA,EAAM,YAAA;AAAA,MACN,IAAA,EAAM;AAAA,KACR;AAAA,IACA,SAAA;AAAA,IACA,MAAA;AAAA,IACA,YAAA;AAAA,IACA,OAAA,EAAS;AAAA,GACX;AACF;;;ACxEO,SAAS,SAAA,CAAU,GAAA,EAAa,SAAA,GAAoB,GAAA,EAAa;AACtE,EAAA,IAAI,CAAC,UAAA,CAAW,IAAA,CAAK,GAAG,CAAA,EAAG;AACzB,IAAA,OAAO,GAAA;AAAA,EACT;AAUA,EAAA,OAAO;AAAA,IACL,GAAA,CAAI,SAAA,CAAU,CAAA,EAAG,CAAC,CAAA;AAAA;AAAA,IAClB,GAAA,CAAI,SAAA,CAAU,CAAA,EAAG,CAAC,CAAA;AAAA;AAAA,IAClB,GAAA,CAAI,SAAA,CAAU,CAAA,EAAG,CAAC,CAAA;AAAA;AAAA,IAClB,GAAA,CAAI,SAAA,CAAU,CAAA,EAAG,CAAC,CAAA;AAAA;AAAA,IAClB,GAAA,CAAI,SAAA,CAAU,CAAA,EAAG,EAAE,CAAA;AAAA;AAAA,IACnB,GAAA,CAAI,SAAA,CAAU,EAAA,EAAI,EAAE,CAAA;AAAA;AAAA,IACpB,GAAA,CAAI,SAAA,CAAU,EAAA,EAAI,EAAE;AAAA;AAAA,GACtB,CAAE,KAAK,SAAS,CAAA;AAClB;AA0CO,SAAS,OAAA,CAAQ,GAAA,EAAa,OAAA,GAAuB,EAAC,EAAW;AACtE,EAAA,IAAI,CAAC,UAAA,CAAW,IAAA,CAAK,GAAG,CAAA,EAAG;AACzB,IAAA,OAAO,GAAA;AAAA,EACT;AAEA,EAAA,MAAM,EAAE,QAAQ,CAAA,EAAG,GAAA,GAAM,GAAG,IAAA,GAAO,GAAA,EAAK,WAAU,GAAI,OAAA;AAEtD,EAAA,IAAI,KAAA,GAAQ,OAAO,EAAA,EAAI;AACrB,IAAA,OAAO,GAAA;AAAA,EACT;AAEA,EAAA,IAAI,SAAA,EAAW;AAEb,IAAA,MAAM,SAAA,GAAY,SAAA,CAAU,GAAA,EAAK,SAAS,CAAA;AAC1C,IAAA,MAAM,KAAA,GAAQ,SAAA,CAAU,KAAA,CAAM,SAAS,CAAA;AAKvC,IAAA,IAAI,SAAA,GAAY,CAAA;AAChB,IAAA,MAAM,WAAA,GAAc,KAAA,CAAM,GAAA,CAAI,CAAC,IAAA,KAAS;AACtC,MAAA,MAAM,SAAA,GAAY,SAAA;AAClB,MAAA,MAAM,OAAA,GAAU,YAAY,IAAA,CAAK,MAAA;AACjC,MAAA,SAAA,IAAa,IAAA,CAAK,MAAA;AAGlB,MAAA,IAAI,WAAW,KAAA,EAAO;AAEpB,QAAA,OAAO,IAAA;AAAA,MACT,CAAA,MAAA,IAAW,SAAA,IAAa,EAAA,GAAK,GAAA,EAAK;AAEhC,QAAA,OAAO,IAAA;AAAA,MACT,CAAA,MAAA,IAAW,SAAA,IAAa,KAAA,IAAS,OAAA,IAAW,KAAK,GAAA,EAAK;AAEpD,QAAA,OAAO,IAAA,CAAK,MAAA,CAAO,IAAA,CAAK,MAAM,CAAA;AAAA,MAChC,CAAA,MAAO;AAEL,QAAA,OAAO,KACJ,KAAA,CAAM,EAAE,EACR,GAAA,CAAI,CAAC,IAAI,GAAA,KAAQ;AAChB,UAAA,MAAM,MAAM,SAAA,GAAY,GAAA;AACxB,UAAA,IAAI,GAAA,GAAM,KAAA,IAAS,GAAA,IAAO,EAAA,GAAK,GAAA,EAAK;AAClC,YAAA,OAAO,EAAA;AAAA,UACT;AACA,UAAA,OAAO,IAAA;AAAA,QACT,CAAC,CAAA,CACA,IAAA,CAAK,EAAE,CAAA;AAAA,MACZ;AAAA,IACF,CAAC,CAAA;AAED,IAAA,OAAO,WAAA,CAAY,KAAK,SAAS,CAAA;AAAA,EACnC;AAGA,EAAA,MAAM,SAAA,GAAY,GAAA,CAAI,SAAA,CAAU,CAAA,EAAG,KAAK,CAAA;AACxC,EAAA,MAAM,OAAA,GAAU,GAAA,CAAI,SAAA,CAAU,EAAA,GAAK,GAAG,CAAA;AACtC,EAAA,MAAM,UAAA,GAAa,KAAK,KAAA,GAAQ,GAAA;AAChC,EAAA,OAAO,SAAA,GAAY,IAAA,CAAK,MAAA,CAAO,UAAU,CAAA,GAAI,OAAA;AAC/C;;;AC/IO,SAAS,MAAA,CACd,GAAA,EACA,aAAA,mBAAsB,IAAI,MAAK,EAChB;AACf,EAAA,MAAM,IAAA,GAAO,SAAS,GAAG,CAAA;AACzB,EAAA,IAAI,CAAC,IAAA,IAAQ,CAAC,IAAA,CAAK,SAAA,EAAW;AAC5B,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,MAAM,YAAY,IAAA,CAAK,SAAA;AACvB,EAAA,IAAI,GAAA,GAAM,aAAA,CAAc,WAAA,EAAY,GAAI,UAAU,WAAA,EAAY;AAC9D,EAAA,MAAM,CAAA,GAAI,aAAA,CAAc,QAAA,EAAS,GAAI,UAAU,QAAA,EAAS;AAExD,EAAA,IAAI,CAAA,GAAI,KAAM,CAAA,KAAM,CAAA,IAAK,cAAc,OAAA,EAAQ,GAAI,SAAA,CAAU,OAAA,EAAQ,EAAI;AACvE,IAAA,GAAA,EAAA;AAAA,EACF;AAEA,EAAA,OAAO,GAAA;AACT;AAcO,SAAS,eAAA,CACd,KACA,OAAA,GAAsC;AAAA,EACpC,GAAA,EAAK,SAAA;AAAA,EACL,KAAA,EAAO,MAAA;AAAA,EACP,IAAA,EAAM;AACR,CAAA,EACA,SAAiB,OAAA,EACF;AACf,EAAA,MAAM,IAAA,GAAO,SAAS,GAAG,CAAA;AACzB,EAAA,IAAI,CAAC,IAAA,IAAQ,CAAC,IAAA,CAAK,SAAA,EAAW;AAC5B,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,OAAO,IAAI,KAAK,cAAA,CAAe,MAAA,EAAQ,OAAO,CAAA,CAAE,MAAA,CAAO,KAAK,SAAS,CAAA;AACvE;AASO,SAAS,gBAAA,CACd,KACA,MAAA,EACS;AACT,EAAA,MAAM,IAAA,GAAO,SAAS,GAAG,CAAA;AACzB,EAAA,IAAI,CAAC,IAAA,EAAM;AACT,IAAA,OAAO,KAAA;AAAA,EACT;AACA,EAAA,OAAO,KAAK,MAAA,KAAW,MAAA;AACzB;AASO,SAAS,mBAAA,CAAoB,KAAa,SAAA,EAA0B;AACzE,EAAA,MAAM,IAAA,GAAO,SAAS,GAAG,CAAA;AACzB,EAAA,IAAI,CAAC,IAAA,IAAQ,CAAC,IAAA,CAAK,SAAA,EAAW;AAC5B,IAAA,OAAO,KAAA;AAAA,EACT;AAEA,EAAA,OACE,KAAK,SAAA,CAAU,WAAA,OAAkB,SAAA,CAAU,WAAA,MAC3C,IAAA,CAAK,SAAA,CAAU,UAAS,KAAM,SAAA,CAAU,UAAS,IACjD,IAAA,CAAK,UAAU,OAAA,EAAQ,KAAM,UAAU,OAAA,EAAQ;AAEnD","file":"index.js","sourcesContent":["/**\n * Indonesian province codes and names\n * Based on Dukcapil Kemendagri data\n */\nexport const PROVINCES: Record<string, string> = {\n '11': 'Aceh',\n '12': 'Sumatera Utara',\n '13': 'Sumatera Barat',\n '14': 'Riau',\n '15': 'Jambi',\n '16': 'Sumatera Selatan',\n '17': 'Bengkulu',\n '18': 'Lampung',\n '19': 'Kepulauan Bangka Belitung',\n '21': 'Kepulauan Riau',\n '31': 'DKI Jakarta',\n '32': 'Jawa Barat',\n '33': 'Jawa Tengah',\n '34': 'DI Yogyakarta',\n '35': 'Jawa Timur',\n '36': 'Banten',\n '51': 'Bali',\n '52': 'Nusa Tenggara Barat',\n '53': 'Nusa Tenggara Timur',\n '61': 'Kalimantan Barat',\n '62': 'Kalimantan Tengah',\n '63': 'Kalimantan Selatan',\n '64': 'Kalimantan Timur',\n '65': 'Kalimantan Utara',\n '71': 'Sulawesi Utara',\n '72': 'Sulawesi Tengah',\n '73': 'Sulawesi Selatan',\n '74': 'Sulawesi Tenggara',\n '75': 'Gorontalo',\n '76': 'Sulawesi Barat',\n '81': 'Maluku',\n '82': 'Maluku Utara',\n '91': 'Papua',\n '92': 'Papua Barat',\n '93': 'Papua Selatan',\n '94': 'Papua Tengah',\n '95': 'Papua Pegunungan',\n '96': 'Papua Barat Daya',\n};\n\n/**\n * Regency codes for each province (simplified - only major ones)\n * In a real implementation, you'd have complete data\n */\nexport const REGENCIES: Record<string, Record<string, string>> = {\n '32': {\n '01': 'Kab. Bogor',\n '02': 'Kab. Sukabumi',\n '03': 'Kab. Cianjur',\n '71': 'Kota Bandung',\n '72': 'Kota Bekasi',\n '73': 'Kota Depok',\n },\n '31': { \n '01': 'Kota Jakarta Selatan',\n '02': 'Kota Jakarta Timur',\n '03': 'Kota Jakarta Pusat',\n '04': 'Kota Jakarta Barat',\n '05': 'Kota Jakarta Utara',\n },\n};","import { PROVINCES } from './constants';\n\n/**\n * Validates a NIK (Nomor Induk Kependudukan) format.\n *\n * A valid NIK must:\n * - Be exactly 16 digits\n * - Have a valid province code (positions 1-2)\n * - Have a valid date (positions 7-12)\n * - Not be in the future\n * - Not be before 1900\n *\n * For female NIKs, the day is encoded as (actual day + 40).\n * For example, a female born on the 15th would have day = 55.\n *\n * @param nik - The 16-digit NIK string to validate\n * @returns `true` if the NIK is valid, `false` otherwise\n *\n * @example\n * ```typescript\n * validateNIK('3201234567890123'); // true - valid NIK\n * validateNIK('1234'); // false - wrong length\n * validateNIK('9912345678901234'); // false - invalid province\n * ```\n *\n * @public\n */\nexport function validateNIK(nik: string): boolean {\n if (!/^\\d{16}$/.test(nik)) {\n return false;\n }\n\n const provinceCode = nik.substring(0, 2);\n if (!PROVINCES[provinceCode]) {\n return false;\n }\n\n const yearStr = nik.substring(6, 8);\n const monthStr = nik.substring(8, 10);\n const dayStr = nik.substring(10, 12);\n\n const year = parseInt(yearStr, 10);\n const fullYear = year > 30 ? 1900 + year : 2000 + year;\n\n const month = parseInt(monthStr, 10);\n let day = parseInt(dayStr, 10);\n\n if (day > 40) {\n day = day - 40;\n }\n\n if (month < 1 || month > 12) {\n return false;\n }\n\n if (day < 1 || day > 31) {\n return false;\n }\n\n const testDate = new Date(fullYear, month - 1, day);\n if (\n testDate.getFullYear() !== fullYear ||\n testDate.getMonth() !== month - 1 ||\n testDate.getDate() !== day\n ) {\n return false;\n }\n\n const now = new Date();\n if (testDate > now || testDate < new Date(1900, 0, 1)) {\n return false;\n }\n\n return true;\n}\n","import { PROVINCES, REGENCIES } from './constants';\nimport { NIKInfo } from './types';\n\n/**\n * Parses a NIK and extracts all embedded information.\n *\n * Extracts province, regency, district codes, birth date, gender,\n * and serial number from a 16-digit NIK string.\n *\n * @param nik - The 16-digit NIK string to parse\n * @returns Parsed NIK information, or `null` if the NIK format is invalid\n *\n * @example\n * Parse a valid male NIK:\n * ```typescript\n * const info = parseNIK('3201018901310123');\n * console.log(info);\n * // {\n * // province: { code: '32', name: 'Jawa Barat' },\n * // regency: { code: '01', name: 'Kab. Bogor' },\n * // district: { code: '01', name: null },\n * // birthDate: Date(1989, 0, 31), // Jan 31, 1989\n * // gender: 'male',\n * // serialNumber: '0123',\n * // isValid: true\n * // }\n * ```\n *\n * @example\n * Parse a female NIK (day + 40):\n * ```typescript\n * const info = parseNIK('3201019508550123');\n * console.log(info.gender); // 'female'\n * console.log(info.birthDate); // Date(1995, 7, 15) - Aug 15, 1995\n * ```\n *\n * @example\n * Invalid NIK returns null:\n * ```typescript\n * const info = parseNIK('invalid');\n * console.log(info); // null\n * ```\n *\n * @public\n */\nexport function parseNIK(nik: string): NIKInfo | null {\n if (!/^\\d{16}$/.test(nik)) {\n return null;\n }\n\n const provinceCode = nik.substring(0, 2);\n const regencyCode = nik.substring(2, 4);\n const districtCode = nik.substring(4, 6);\n const yearStr = nik.substring(6, 8);\n const monthStr = nik.substring(8, 10);\n const dayStr = nik.substring(10, 12);\n const serialNumber = nik.substring(12, 16);\n\n const province = PROVINCES[provinceCode];\n if (!province) {\n return null;\n }\n\n const regencies = REGENCIES[provinceCode] || {};\n const regency = regencies[regencyCode] || 'Unknown';\n\n let day = parseInt(dayStr, 10);\n const month = parseInt(monthStr, 10);\n const year = parseInt(yearStr, 10);\n\n let gender: 'male' | 'female' | null = null;\n if (day > 40) {\n gender = 'female';\n day -= 40;\n } else {\n gender = 'male';\n }\n\n const fullYear = year > 30 ? 1900 + year : 2000 + year;\n\n const birthDate = new Date(fullYear, month - 1, day);\n if (\n birthDate.getFullYear() !== fullYear ||\n birthDate.getMonth() !== month - 1 ||\n birthDate.getDate() !== day\n ) {\n return null;\n }\n\n return {\n province: {\n code: provinceCode,\n name: province,\n },\n regency: {\n code: regencyCode,\n name: regency,\n },\n district: {\n code: districtCode,\n name: null,\n },\n birthDate,\n gender,\n serialNumber,\n isValid: true,\n };\n}","import { MaskOptions } from './types';\n\n/**\n * Formats a NIK with separators for better readability.\n *\n * Groups the NIK into logical segments: province, regency, district,\n * year, month, day, and serial number.\n *\n * @param nik - The 16-digit NIK string to format\n * @param separator - Character to use as separator\n * @returns Formatted NIK string, or original string if invalid format\n *\n * @example\n * Default separator (dash):\n * ```typescript\n * formatNIK('3201234567890123');\n * // '32-01-23-45-67-89-0123'\n * ```\n *\n * @example\n * Custom separator:\n * ```typescript\n * formatNIK('3201234567890123', ' ');\n * // '32 01 23 45 67 89 0123'\n * ```\n *\n * @example\n * Invalid NIK returns as-is:\n * ```typescript\n * formatNIK('1234');\n * // '1234'\n * ```\n *\n * @public\n */\nexport function formatNIK(nik: string, separator: string = '-'): string {\n if (!/^\\d{16}$/.test(nik)) {\n return nik;\n }\n\n // Format: PP-KK-DD-YY-MM-DD-XXXX\n // PP = Province (2 digits)\n // KK = Regency (2 digits)\n // DD = District (2 digits)\n // YY = Year (2 digits)\n // MM = Month (2 digits)\n // DD = Day (2 digits, +40 for female)\n // XXXX = Serial number (4 digits)\n return [\n nik.substring(0, 2), // Province\n nik.substring(2, 4), // Regency\n nik.substring(4, 6), // District\n nik.substring(6, 8), // Year\n nik.substring(8, 10), // Month\n nik.substring(10, 12), // Day\n nik.substring(12, 16), // Serial\n ].join(separator);\n}\n\n/**\n * Masks a NIK to protect privacy while keeping partial visibility.\n *\n * By default, shows the first 4 and last 4 digits, masking the middle 8.\n * Optionally formats the masked NIK with separators.\n *\n * @param nik - The 16-digit NIK string to mask\n * @param options - Masking configuration options\n * @returns Masked NIK string, or original string if invalid format\n *\n * @example\n * Default masking (first 4, last 4):\n * ```typescript\n * maskNIK('3201234567890123');\n * // '3201********0123'\n * ```\n *\n * @example\n * Custom mask character:\n * ```typescript\n * maskNIK('3201234567890123', { char: 'X' });\n * // '3201XXXXXXXX0123'\n * ```\n *\n * @example\n * With separator:\n * ```typescript\n * maskNIK('3201234567890123', { separator: '-' });\n * // '32-01-**-**-**-**-0123'\n * ```\n *\n * @example\n * Custom start and end:\n * ```typescript\n * maskNIK('3201234567890123', { start: 6, end: 4 });\n * // '320123******0123'\n * ```\n *\n * @public\n */\nexport function maskNIK(nik: string, options: MaskOptions = {}): string {\n if (!/^\\d{16}$/.test(nik)) {\n return nik;\n }\n\n const { start = 4, end = 4, char = '*', separator } = options;\n\n if (start + end >= 16) {\n return nik;\n }\n\n if (separator) {\n // Format with separator first, then apply masking\n const formatted = formatNIK(nik, separator);\n const parts = formatted.split(separator);\n\n // Calculate which parts to mask\n // Format: PP-KK-DD-YY-MM-DD-XXXX (7 parts)\n // Mask parts based on character positions\n let charCount = 0;\n const maskedParts = parts.map((part) => {\n const partStart = charCount;\n const partEnd = charCount + part.length;\n charCount += part.length;\n\n // Check if this part should be fully/partially masked\n if (partEnd <= start) {\n // Fully visible (before start)\n return part;\n } else if (partStart >= 16 - end) {\n // Fully visible (after end)\n return part;\n } else if (partStart >= start && partEnd <= 16 - end) {\n // Fully masked\n return char.repeat(part.length);\n } else {\n // Partially masked\n return part\n .split('')\n .map((ch, idx) => {\n const pos = partStart + idx;\n if (pos < start || pos >= 16 - end) {\n return ch;\n }\n return char;\n })\n .join('');\n }\n });\n\n return maskedParts.join(separator);\n }\n\n // Without separator: simple masking\n const startPart = nik.substring(0, start);\n const endPart = nik.substring(16 - end);\n const maskLength = 16 - start - end;\n return startPart + char.repeat(maskLength) + endPart;\n}","import { parseNIK } from './parse';\n\n/**\n * Calculates the age of a person based on their NIK.\n *\n * @param nik - The 16-digit NIK string\n * @param referenceDate - The date to calculate age from (default: current date)\n * @returns The age in years, or null if the NIK is invalid or birth date cannot be parsed\n *\n * @example\n * ```typescript\n * getAge('3201018901310123'); // 35 (as of 2024)\n * ```\n */\nexport function getAge(\n nik: string,\n referenceDate: Date = new Date()\n): number | null {\n const info = parseNIK(nik);\n if (!info || !info.birthDate) {\n return null;\n }\n\n const birthDate = info.birthDate;\n let age = referenceDate.getFullYear() - birthDate.getFullYear();\n const m = referenceDate.getMonth() - birthDate.getMonth();\n\n if (m < 0 || (m === 0 && referenceDate.getDate() < birthDate.getDate())) {\n age--;\n }\n\n return age;\n}\n\n/**\n * Formats the birth date from a NIK into a human-readable string.\n *\n * @param nik - The 16-digit NIK string\n * @param locale - The locale to use for formatting (default: 'id-ID')\n * @returns Formatted birth date string, or null if invalid\n *\n * @example\n * ```typescript\n * formatBirthDate('3201018901310123'); // '31 Januari 1989'\n * ```\n */\nexport function formatBirthDate(\n nik: string,\n options: Intl.DateTimeFormatOptions = {\n day: 'numeric',\n month: 'long',\n year: 'numeric',\n },\n locale: string = 'id-ID'\n): string | null {\n const info = parseNIK(nik);\n if (!info || !info.birthDate) {\n return null;\n }\n\n return new Intl.DateTimeFormat(locale, options).format(info.birthDate);\n}\n\n/**\n * Checks if a NIK matches a specific gender.\n *\n * @param nik - The 16-digit NIK string\n * @param gender - The gender to check ('male' | 'female')\n * @returns True if the NIK matches the gender, false otherwise\n */\nexport function isValidForGender(\n nik: string,\n gender: 'male' | 'female'\n): boolean {\n const info = parseNIK(nik);\n if (!info) {\n return false;\n }\n return info.gender === gender;\n}\n\n/**\n * Checks if a NIK matches a specific birth date.\n *\n * @param nik - The 16-digit NIK string\n * @param birthDate - The birth date to check\n * @returns True if the NIK matches the birth date, false otherwise\n */\nexport function isValidForBirthDate(nik: string, birthDate: Date): boolean {\n const info = parseNIK(nik);\n if (!info || !info.birthDate) {\n return false;\n }\n\n return (\n info.birthDate.getFullYear() === birthDate.getFullYear() &&\n info.birthDate.getMonth() === birthDate.getMonth() &&\n info.birthDate.getDate() === birthDate.getDate()\n );\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/nik/constants.ts","../../src/nik/utils/date.ts","../../src/nik/validate.ts","../../src/nik/parse.ts","../../src/nik/format.ts","../../src/nik/utils.ts","../../src/nik/types.ts"],"names":["NIK_PATTERN"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Indonesian province codes and names\n * Based on Dukcapil Kemendagri data\n */\nexport const PROVINCES: Record<string, string> = {\n '11': 'Aceh',\n '12': 'Sumatera Utara',\n '13': 'Sumatera Barat',\n '14': 'Riau',\n '15': 'Jambi',\n '16': 'Sumatera Selatan',\n '17': 'Bengkulu',\n '18': 'Lampung',\n '19': 'Kepulauan Bangka Belitung',\n '21': 'Kepulauan Riau',\n '31': 'DKI Jakarta',\n '32': 'Jawa Barat',\n '33': 'Jawa Tengah',\n '34': 'DI Yogyakarta',\n '35': 'Jawa Timur',\n '36': 'Banten',\n '51': 'Bali',\n '52': 'Nusa Tenggara Barat',\n '53': 'Nusa Tenggara Timur',\n '61': 'Kalimantan Barat',\n '62': 'Kalimantan Tengah',\n '63': 'Kalimantan Selatan',\n '64': 'Kalimantan Timur',\n '65': 'Kalimantan Utara',\n '71': 'Sulawesi Utara',\n '72': 'Sulawesi Tengah',\n '73': 'Sulawesi Selatan',\n '74': 'Sulawesi Tenggara',\n '75': 'Gorontalo',\n '76': 'Sulawesi Barat',\n '81': 'Maluku',\n '82': 'Maluku Utara',\n '91': 'Papua',\n '92': 'Papua Barat',\n '93': 'Papua Selatan',\n '94': 'Papua Tengah',\n '95': 'Papua Pegunungan',\n '96': 'Papua Barat Daya',\n};\n\n/**\n * Regency codes for each province (simplified - only major ones)\n * In a real implementation, you'd have complete data\n */\nexport const REGENCIES: Record<string, Record<string, string>> = {\n '32': {\n '01': 'Kab. Bogor',\n '02': 'Kab. Sukabumi',\n '03': 'Kab. Cianjur',\n '71': 'Kota Bandung',\n '72': 'Kota Bekasi',\n '73': 'Kota Depok',\n },\n '31': { \n '01': 'Kota Jakarta Selatan',\n '02': 'Kota Jakarta Timur',\n '03': 'Kota Jakarta Pusat',\n '04': 'Kota Jakarta Barat',\n '05': 'Kota Jakarta Utara',\n },\n};","/**\n * Shared date parsing and validation utilities for NIK module.\n *\n * @module nik/utils/date\n * @packageDocumentation\n */\n\n/**\n * Result of parsing date components from a NIK string.\n *\n * @public\n */\nexport interface ParsedNIKDate {\n /** 2-digit year code (00-99) from NIK positions 7-8 */\n year: number;\n /** Full 4-digit year (1900-1999 or 2000-2099) */\n fullYear: number;\n /** Month (1-12) from NIK positions 9-10 */\n month: number;\n /** Actual day (1-31), decoded from encoded day */\n day: number;\n /** Gender derived from day encoding */\n gender: 'male' | 'female';\n /** Original encoded day (1-31 for male, 41-71 for female) */\n dayEncoded: number;\n}\n\n/**\n * Parses date components from a NIK string.\n *\n * Extracts year, month, and encoded day from positions 7-12 of the NIK.\n * For females, the day is encoded as (actual day + 40).\n *\n * @param nik - 16-digit NIK string\n * @returns ParsedNIKDate or null if NIK format is invalid\n *\n * @example\n * ```typescript\n * const result = parseNIKDate('3201018901310123');\n * // { year: 89, fullYear: 1989, month: 1, day: 31, gender: 'male', dayEncoded: 31 }\n * ```\n *\n * @public\n */\nexport function parseNIKDate(nik: string): ParsedNIKDate | null {\n if (nik.length !== 16) {\n return null;\n }\n\n const yearStr = nik.substring(6, 8);\n const monthStr = nik.substring(8, 10);\n const dayEncodedStr = nik.substring(10, 12);\n\n const year = parseInt(yearStr, 10);\n if (isNaN(year)) return null;\n\n const fullYear = year > 30 ? 1900 + year : 2000 + year;\n\n const month = parseInt(monthStr, 10);\n if (isNaN(month)) return null;\n\n const dayEncoded = parseInt(dayEncodedStr, 10);\n if (isNaN(dayEncoded)) return null;\n\n const gender: 'male' | 'female' = dayEncoded > 40 ? 'female' : 'male';\n const day = dayEncoded > 40 ? dayEncoded - 40 : dayEncoded;\n\n return { year, fullYear, month, day, gender, dayEncoded };\n}\n\n/**\n * Validates if year/month/day form a valid calendar date.\n *\n * Uses JavaScript's Date object to check if the date actually exists\n * (handles leap years, invalid month/day combinations).\n *\n * @param year - Full 4-digit year\n * @param month - Month (1-12)\n * @param day - Day (1-31)\n * @returns true if valid calendar date\n *\n * @example\n * ```typescript\n * validateNIKDateComponents(2024, 2, 29); // true (leap year)\n * validateNIKDateComponents(2023, 2, 29); // false\n * validateNIKDateComponents(2024, 13, 1); // false\n * ```\n *\n * @public\n */\nexport function validateNIKDateComponents(\n year: number,\n month: number,\n day: number\n): boolean {\n if (month < 1 || month > 12) return false;\n if (day < 1 || day > 31) return false;\n\n const testDate = new Date(year, month - 1, day);\n return (\n testDate.getFullYear() === year &&\n testDate.getMonth() === month - 1 &&\n testDate.getDate() === day\n );\n}\n","import { PROVINCES } from './constants';\nimport { parseNIKDate, validateNIKDateComponents } from './utils/date';\n\nconst NIK_PATTERN = /^\\d{16}$/;\n\n/**\n * Validates a NIK (Nomor Induk Kependudukan) format.\n *\n * A valid NIK must:\n * - Be exactly 16 digits\n * - Have a valid province code (positions 1-2)\n * - Have a valid date (positions 7-12)\n * - Not be in the future\n * - Not be before 1900\n *\n * For female NIKs, the day is encoded as (actual day + 40).\n * For example, a female born on the 15th would have day = 55.\n *\n * @param nik - The 16-digit NIK string to validate\n * @returns `true` if the NIK is valid, `false` otherwise\n *\n * @example\n * ```typescript\n * validateNIK('3201234567890123'); // true - valid NIK\n * validateNIK('1234'); // false - wrong length\n * validateNIK('9912345678901234'); // false - invalid province\n * ```\n *\n * @public\n */\nexport function validateNIK(nik: string): boolean {\n if (!NIK_PATTERN.test(nik)) {\n return false;\n }\n\n const provinceCode = nik.substring(0, 2);\n if (!PROVINCES[provinceCode]) {\n return false;\n }\n\n const parsed = parseNIKDate(nik);\n if (!parsed) {\n return false;\n }\n\n const { fullYear, month, day } = parsed;\n\n if (!validateNIKDateComponents(fullYear, month, day)) {\n return false;\n }\n\n const now = new Date();\n if (new Date(fullYear, month - 1, day) > now || fullYear < 1900) {\n return false;\n }\n\n return true;\n}\n","import { PROVINCES, REGENCIES } from './constants';\nimport { NIKInfo } from './types';\nimport { parseNIKDate, validateNIKDateComponents } from './utils/date';\n\nconst NIK_PATTERN = /^\\d{16}$/;\n\n/**\n * Parses a NIK and extracts all embedded information.\n *\n * Extracts province, regency, district codes, birth date, gender,\n * and serial number from a 16-digit NIK string.\n *\n * @param nik - The 16-digit NIK string to parse\n * @returns Parsed NIK information, or `null` if the NIK format is invalid\n *\n * @example\n * Parse a valid male NIK:\n * ```typescript\n * const info = parseNIK('3201018901310123');\n * console.log(info);\n * // {\n * // province: { code: '32', name: 'Jawa Barat' },\n * // regency: { code: '01', name: 'Kab. Bogor' },\n * // district: { code: '01', name: null },\n * // birthDate: Date(1989, 0, 31), // Jan 31, 1989\n * // gender: 'male',\n * // serialNumber: '0123',\n * // isValid: true\n * // }\n * ```\n *\n * @example\n * Parse a female NIK (day + 40):\n * ```typescript\n * const info = parseNIK('3201019508550123');\n * console.log(info.gender); // 'female'\n * console.log(info.birthDate); // Date(1995, 7, 15) - Aug 15, 1995\n * ```\n *\n * @example\n * Invalid NIK returns null:\n * ```typescript\n * const info = parseNIK('invalid');\n * console.log(info); // null\n * ```\n *\n * @public\n */\nexport function parseNIK(nik: string): NIKInfo | null {\n if (!NIK_PATTERN.test(nik)) {\n return null;\n }\n\n const provinceCode = nik.substring(0, 2);\n const regencyCode = nik.substring(2, 4);\n const districtCode = nik.substring(4, 6);\n const serialNumber = nik.substring(12, 16);\n\n const province = PROVINCES[provinceCode];\n if (!province) {\n return null;\n }\n\n const regencies = REGENCIES[provinceCode] || {};\n const regency = regencies[regencyCode] || 'Unknown';\n\n const parsed = parseNIKDate(nik);\n if (!parsed) {\n return null;\n }\n\n const { fullYear, month, day, gender } = parsed;\n\n if (!validateNIKDateComponents(fullYear, month, day)) {\n return null;\n }\n\n const birthDate = new Date(fullYear, month - 1, day);\n\n return {\n province: {\n code: provinceCode,\n name: province,\n },\n regency: {\n code: regencyCode,\n name: regency,\n },\n district: {\n code: districtCode,\n name: null,\n },\n birthDate,\n gender,\n serialNumber,\n isValid: true,\n };\n}\n","import { MaskOptions } from './types';\n\n/**\n * Formats a NIK with separators for better readability.\n *\n * Groups the NIK into logical segments: province, regency, district,\n * year, month, day, and serial number.\n *\n * @param nik - The 16-digit NIK string to format\n * @param separator - Character to use as separator\n * @returns Formatted NIK string, or original string if invalid format\n *\n * @example\n * Default separator (dash):\n * ```typescript\n * formatNIK('3201234567890123');\n * // '32-01-23-45-67-89-0123'\n * ```\n *\n * @example\n * Custom separator:\n * ```typescript\n * formatNIK('3201234567890123', ' ');\n * // '32 01 23 45 67 89 0123'\n * ```\n *\n * @example\n * Invalid NIK returns as-is:\n * ```typescript\n * formatNIK('1234');\n * // '1234'\n * ```\n *\n * @public\n */\nexport function formatNIK(nik: string, separator: string = '-'): string {\n if (!/^\\d{16}$/.test(nik)) {\n return nik;\n }\n\n // Format: PP-KK-DD-YY-MM-DD-XXXX\n // PP = Province (2 digits)\n // KK = Regency (2 digits)\n // DD = District (2 digits)\n // YY = Year (2 digits)\n // MM = Month (2 digits)\n // DD = Day (2 digits, +40 for female)\n // XXXX = Serial number (4 digits)\n return [\n nik.substring(0, 2), // Province\n nik.substring(2, 4), // Regency\n nik.substring(4, 6), // District\n nik.substring(6, 8), // Year\n nik.substring(8, 10), // Month\n nik.substring(10, 12), // Day\n nik.substring(12, 16), // Serial\n ].join(separator);\n}\n\n/**\n * Masks a NIK to protect privacy while keeping partial visibility.\n *\n * By default, shows the first 4 and last 4 digits, masking the middle 8.\n * Optionally formats the masked NIK with separators.\n *\n * @param nik - The 16-digit NIK string to mask\n * @param options - Masking configuration options\n * @returns Masked NIK string, or original string if invalid format\n *\n * @example\n * Default masking (first 4, last 4):\n * ```typescript\n * maskNIK('3201234567890123');\n * // '3201********0123'\n * ```\n *\n * @example\n * Custom mask character:\n * ```typescript\n * maskNIK('3201234567890123', { char: 'X' });\n * // '3201XXXXXXXX0123'\n * ```\n *\n * @example\n * With separator:\n * ```typescript\n * maskNIK('3201234567890123', { separator: '-' });\n * // '32-01-**-**-**-**-0123'\n * ```\n *\n * @example\n * Custom start and end:\n * ```typescript\n * maskNIK('3201234567890123', { start: 6, end: 4 });\n * // '320123******0123'\n * ```\n *\n * @public\n */\nexport function maskNIK(nik: string, options: MaskOptions = {}): string {\n if (!/^\\d{16}$/.test(nik)) {\n return nik;\n }\n\n const { start = 4, end = 4, char = '*', separator } = options;\n\n if (start + end >= 16) {\n return nik;\n }\n\n if (separator) {\n // Format with separator first, then apply masking\n const formatted = formatNIK(nik, separator);\n const parts = formatted.split(separator);\n\n // Calculate which parts to mask\n // Format: PP-KK-DD-YY-MM-DD-XXXX (7 parts)\n // Mask parts based on character positions\n let charCount = 0;\n const maskedParts = parts.map((part) => {\n const partStart = charCount;\n const partEnd = charCount + part.length;\n charCount += part.length;\n\n // Check if this part should be fully/partially masked\n if (partEnd <= start) {\n // Fully visible (before start)\n return part;\n } else if (partStart >= 16 - end) {\n // Fully visible (after end)\n return part;\n } else if (partStart >= start && partEnd <= 16 - end) {\n // Fully masked\n return char.repeat(part.length);\n } else {\n // Partially masked\n return part\n .split('')\n .map((ch, idx) => {\n const pos = partStart + idx;\n if (pos < start || pos >= 16 - end) {\n return ch;\n }\n return char;\n })\n .join('');\n }\n });\n\n return maskedParts.join(separator);\n }\n\n // Without separator: simple masking\n const startPart = nik.substring(0, start);\n const endPart = nik.substring(16 - end);\n const maskLength = 16 - start - end;\n return startPart + char.repeat(maskLength) + endPart;\n}","import { parseNIK } from './parse';\n\n/**\n * Calculates the age of a person based on their NIK.\n *\n * @param nik - The 16-digit NIK string\n * @param referenceDate - The date to calculate age from (default: current date)\n * @returns The age in years, or null if the NIK is invalid or birth date cannot be parsed\n *\n * @example\n * ```typescript\n * getAge('3201018901310123'); // 35 (as of 2024)\n * ```\n */\nexport function getAge(\n nik: string,\n referenceDate: Date = new Date()\n): number | null {\n const info = parseNIK(nik);\n if (!info || !info.birthDate) {\n return null;\n }\n\n const birthDate = info.birthDate;\n let age = referenceDate.getFullYear() - birthDate.getFullYear();\n const m = referenceDate.getMonth() - birthDate.getMonth();\n\n if (m < 0 || (m === 0 && referenceDate.getDate() < birthDate.getDate())) {\n age--;\n }\n\n return age;\n}\n\n/**\n * Formats the birth date from a NIK into a human-readable string.\n *\n * @param nik - The 16-digit NIK string\n * @param locale - The locale to use for formatting (default: 'id-ID')\n * @returns Formatted birth date string, or null if invalid\n *\n * @example\n * ```typescript\n * formatBirthDate('3201018901310123'); // '31 Januari 1989'\n * ```\n */\nexport function formatBirthDate(\n nik: string,\n options: Intl.DateTimeFormatOptions = {\n day: 'numeric',\n month: 'long',\n year: 'numeric',\n },\n locale: string = 'id-ID'\n): string | null {\n const info = parseNIK(nik);\n if (!info || !info.birthDate) {\n return null;\n }\n\n return new Intl.DateTimeFormat(locale, options).format(info.birthDate);\n}\n\n/**\n * Checks if a NIK matches a specific gender.\n *\n * @param nik - The 16-digit NIK string\n * @param gender - The gender to check ('male' | 'female')\n * @returns True if the NIK matches the gender, false otherwise\n */\nexport function isValidForGender(\n nik: string,\n gender: 'male' | 'female'\n): boolean {\n const info = parseNIK(nik);\n if (!info) {\n return false;\n }\n return info.gender === gender;\n}\n\n/**\n * Checks if a NIK matches a specific birth date.\n *\n * @param nik - The 16-digit NIK string\n * @param birthDate - The birth date to check\n * @returns True if the NIK matches the birth date, false otherwise\n */\nexport function isValidForBirthDate(nik: string, birthDate: Date): boolean {\n const info = parseNIK(nik);\n if (!info || !info.birthDate) {\n return false;\n }\n\n return (\n info.birthDate.getFullYear() === birthDate.getFullYear() &&\n info.birthDate.getMonth() === birthDate.getMonth() &&\n info.birthDate.getDate() === birthDate.getDate()\n );\n}\n","/**\n * Information extracted from a valid NIK.\n *\n * Contains parsed data including location codes, birth date, gender,\n * and serial number from a 16-digit NIK string.\n *\n * @public\n */\nexport interface NIKInfo {\n /**\n * Province information extracted from positions 1-2 of the NIK.\n *\n * @example\n * ```typescript\n * { code: '32', name: 'Jawa Barat' }\n * ```\n */\n province: {\n /** Two-digit province code (e.g., '32') */\n code: string;\n /** Full province name (e.g., 'Jawa Barat') */\n name: string;\n };\n\n /**\n * Regency (Kabupaten/Kota) information extracted from positions 3-4 of the NIK.\n *\n * @example\n * ```typescript\n * { code: '01', name: 'Kab. Bogor' }\n * ```\n */\n regency: {\n /** Two-digit regency code (e.g., '01') */\n code: string;\n /** Full regency name (e.g., 'Kab. Bogor') */\n name: string;\n };\n\n /**\n * District (Kecamatan) information extracted from positions 5-6 of the NIK.\n * May be `null` if district data is not available.\n *\n * @example\n * ```typescript\n * { code: '23', name: 'Ciawi' }\n * ```\n */\n district: {\n /** Two-digit district code (e.g., '23') */\n code: string;\n /** Full district name, or `null` if data unavailable */\n name: string | null;\n };\n\n /**\n * Birth date extracted from positions 7-12 of the NIK.\n * For females, the day is encoded as (actual day + 40).\n * Returns `null` if the date is invalid.\n *\n * @example\n * ```typescript\n * new Date(1989, 0, 31) // January 31, 1989\n * ```\n */\n birthDate: Date | null;\n\n /**\n * Gender derived from the day encoding in the NIK.\n * - 'male': day is 1-31\n * - 'female': day is 41-71 (actual day + 40)\n * - `null`: if unable to determine\n */\n gender: 'male' | 'female' | null;\n\n /**\n * Serial number from positions 13-16 of the NIK.\n * Uniquely identifies individuals with the same location and birth date.\n * Returns `null` if unable to extract.\n *\n * @example\n * ```typescript\n * '0123'\n * ```\n */\n serialNumber: string | null;\n\n /**\n * Whether the NIK passed validation checks.\n * If `false`, other fields may be `null` or contain partial data.\n */\n isValid: boolean;\n}\n\n/**\n * Options for masking a NIK to protect privacy.\n *\n * Controls how many characters to show at the start and end,\n * what character to use for masking, and optional separators.\n *\n * @example\n * ```typescript\n * // Default: shows first 4 and last 4 digits\n * { start: 4, end: 4, char: '*' }\n * // Result: '3201********0123'\n *\n * // With separator\n * { start: 4, end: 4, char: '*', separator: '-' }\n * // Result: '3201-****-****-0123'\n * ```\n *\n * @public\n */\nexport interface MaskOptions {\n /**\n * Number of characters to show at the start.\n *\n * @defaultValue 4\n */\n start?: number;\n\n /**\n * Number of characters to show at the end.\n *\n * @defaultValue 4\n */\n end?: number;\n\n /**\n * Character to use for masking hidden digits.\n *\n * @defaultValue '*'\n */\n char?: string;\n\n /**\n * Optional separator to add between groups of digits.\n * If provided, the NIK will be formatted with separators.\n *\n * @defaultValue undefined (no separator)\n *\n * @example\n * ```typescript\n * '-' // Results in format: '3201-****-****-0123'\n * ' ' // Results in format: '3201 **** **** 0123'\n * ```\n */\n separator?: string;\n}\n\n/**\n * Error thrown when an invalid NIK is provided to a function.\n * Extends native Error with a `code` property for programmatic error handling.\n *\n * @example\n * ```typescript\n * try {\n * requireNIK('invalid');\n * } catch (error) {\n * if (error instanceof InvalidNIKError) {\n * console.log(error.code); // 'INVALID_NIK'\n * }\n * }\n * ```\n *\n * @public\n */\nexport class InvalidNIKError extends Error {\n /** Error code for programmatic identification */\n readonly code = 'INVALID_NIK' as const;\n\n constructor(message: string = 'Invalid NIK provided') {\n super(message);\n this.name = 'InvalidNIKError';\n }\n}\n"]}
|