nik-id 1.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/LICENSE +21 -0
- package/README.md +492 -0
- package/dist/generate.cjs +95 -0
- package/dist/generate.d.cts +83 -0
- package/dist/generate.d.ts +83 -0
- package/dist/generate.js +70 -0
- package/dist/index.cjs +183 -0
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +154 -0
- package/dist/parse.cjs +111 -0
- package/dist/parse.d.cts +81 -0
- package/dist/parse.d.ts +81 -0
- package/dist/parse.js +84 -0
- package/dist/types.cjs +18 -0
- package/dist/types.d.cts +183 -0
- package/dist/types.d.ts +183 -0
- package/dist/types.js +0 -0
- package/dist/validate.cjs +81 -0
- package/dist/validate.d.cts +81 -0
- package/dist/validate.d.ts +81 -0
- package/dist/validate.js +54 -0
- package/package.json +106 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { GenerateOptions } from './types.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Modul generator NIK (Nomor Induk Kependudukan).
|
|
5
|
+
*
|
|
6
|
+
* Menyediakan fungsi {@link generateNIK} untuk membuat NIK yang valid
|
|
7
|
+
* secara format, baik full random maupun dengan parameter tertentu
|
|
8
|
+
* (wilayah, gender, tanggal lahir).
|
|
9
|
+
*
|
|
10
|
+
* NIK yang dihasilkan valid secara **format** — semua aturan digit terpenuhi —
|
|
11
|
+
* tapi **bukan** NIK asli milik orang sungguhan. Cocok untuk testing,
|
|
12
|
+
* seeding database, atau demo.
|
|
13
|
+
*
|
|
14
|
+
* @module generate
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* import { generateNIK } from 'nik-id/generate';
|
|
19
|
+
*
|
|
20
|
+
* // Full random
|
|
21
|
+
* const nik1 = generateNIK();
|
|
22
|
+
*
|
|
23
|
+
* // Perempuan lahir 25 Agustus 1985
|
|
24
|
+
* const nik2 = generateNIK({ gender: "F", birthDate: new Date("1985-08-25") });
|
|
25
|
+
*
|
|
26
|
+
* // Wilayah spesifik
|
|
27
|
+
* const nik3 = generateNIK({
|
|
28
|
+
* provinceCode: "32",
|
|
29
|
+
* regencyCode: "3204",
|
|
30
|
+
* districtCode: "320407",
|
|
31
|
+
* });
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Generate NIK (Nomor Induk Kependudukan) yang valid secara format.
|
|
37
|
+
*
|
|
38
|
+
* Semua parameter opsional — yang tidak diisi akan di-random.
|
|
39
|
+
* NIK yang dihasilkan memenuhi semua aturan format:
|
|
40
|
+
* - Kode provinsi dalam range 11-97
|
|
41
|
+
* - Tanggal lahir valid secara kalender
|
|
42
|
+
* - Gender tercermin di encoding DD (perempuan: DD + 40)
|
|
43
|
+
* - Sequence number dalam range 0001-9999
|
|
44
|
+
*
|
|
45
|
+
* **Catatan:** NIK ini bukan data asli — hanya valid secara format,
|
|
46
|
+
* cocok untuk testing dan demo.
|
|
47
|
+
*
|
|
48
|
+
* @param options - Opsi untuk men-generate NIK (semua opsional)
|
|
49
|
+
* @returns String NIK 16 digit yang valid secara format
|
|
50
|
+
* @throws {Error} Jika `provinceCode` di luar range 11-97
|
|
51
|
+
* @throws {Error} Jika `regencyCode` tidak diawali `provinceCode`
|
|
52
|
+
* @throws {Error} Jika `districtCode` tidak diawali `regencyCode`
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```typescript
|
|
56
|
+
* // Full random
|
|
57
|
+
* generateNIK();
|
|
58
|
+
* // "3204071508900123"
|
|
59
|
+
*
|
|
60
|
+
* // Gender spesifik
|
|
61
|
+
* generateNIK({ gender: "F" });
|
|
62
|
+
* // "xxxxxx5508900456" (DD + 40 untuk perempuan)
|
|
63
|
+
*
|
|
64
|
+
* // Tanggal lahir spesifik
|
|
65
|
+
* generateNIK({ birthDate: new Date("1985-08-25") });
|
|
66
|
+
* // "xxxxxx2508850789"
|
|
67
|
+
*
|
|
68
|
+
* // Gender dan tanggal lahir
|
|
69
|
+
* generateNIK({ gender: "F", birthDate: new Date("1985-08-25") });
|
|
70
|
+
* // "xxxxxx6508850234" (25 + 40 = 65 untuk perempuan)
|
|
71
|
+
*
|
|
72
|
+
* // Wilayah lengkap
|
|
73
|
+
* generateNIK({
|
|
74
|
+
* provinceCode: "32",
|
|
75
|
+
* regencyCode: "3204",
|
|
76
|
+
* districtCode: "320407",
|
|
77
|
+
* });
|
|
78
|
+
* // "3204071508900001"
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
declare function generateNIK(options?: GenerateOptions): string;
|
|
82
|
+
|
|
83
|
+
export { generateNIK };
|
package/dist/generate.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// src/generate.ts
|
|
2
|
+
var MIN_PROVINCE_CODE = 11;
|
|
3
|
+
var MAX_PROVINCE_CODE = 97;
|
|
4
|
+
var MIN_BIRTH_YEAR = 1950;
|
|
5
|
+
var MAX_BIRTH_YEAR = 2005;
|
|
6
|
+
function randomInt(min, max) {
|
|
7
|
+
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
8
|
+
}
|
|
9
|
+
function padZero(num, length) {
|
|
10
|
+
return String(num).padStart(length, "0");
|
|
11
|
+
}
|
|
12
|
+
function getDaysInMonth(year, month) {
|
|
13
|
+
return new Date(year, month, 0).getDate();
|
|
14
|
+
}
|
|
15
|
+
function randomBirthDate() {
|
|
16
|
+
const year = randomInt(MIN_BIRTH_YEAR, MAX_BIRTH_YEAR);
|
|
17
|
+
const month = randomInt(1, 12);
|
|
18
|
+
const maxDay = getDaysInMonth(year, month);
|
|
19
|
+
const day = randomInt(1, maxDay);
|
|
20
|
+
return new Date(year, month - 1, day);
|
|
21
|
+
}
|
|
22
|
+
function generateNIK(options = {}) {
|
|
23
|
+
const { gender, birthDate } = options;
|
|
24
|
+
let provinceCode;
|
|
25
|
+
if (options.provinceCode !== void 0) {
|
|
26
|
+
const pc = Number.parseInt(options.provinceCode, 10);
|
|
27
|
+
if (Number.isNaN(pc) || pc < MIN_PROVINCE_CODE || pc > MAX_PROVINCE_CODE) {
|
|
28
|
+
throw new Error("Kode provinsi tidak valid (harus 11-97)");
|
|
29
|
+
}
|
|
30
|
+
provinceCode = padZero(pc, 2);
|
|
31
|
+
} else {
|
|
32
|
+
provinceCode = padZero(randomInt(MIN_PROVINCE_CODE, MAX_PROVINCE_CODE), 2);
|
|
33
|
+
}
|
|
34
|
+
let regencyCode;
|
|
35
|
+
if (options.regencyCode !== void 0) {
|
|
36
|
+
if (!options.regencyCode.startsWith(provinceCode)) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
`Kode kabupaten/kota "${options.regencyCode}" tidak sesuai dengan kode provinsi "${provinceCode}"`
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
regencyCode = options.regencyCode;
|
|
42
|
+
} else {
|
|
43
|
+
regencyCode = provinceCode + padZero(randomInt(1, 99), 2);
|
|
44
|
+
}
|
|
45
|
+
let districtCode;
|
|
46
|
+
if (options.districtCode !== void 0) {
|
|
47
|
+
if (!options.districtCode.startsWith(regencyCode)) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
`Kode kecamatan "${options.districtCode}" tidak sesuai dengan kode kabupaten/kota "${regencyCode}"`
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
districtCode = options.districtCode;
|
|
53
|
+
} else {
|
|
54
|
+
districtCode = regencyCode + padZero(randomInt(1, 99), 2);
|
|
55
|
+
}
|
|
56
|
+
const birth = birthDate ?? randomBirthDate();
|
|
57
|
+
const day = birth.getDate();
|
|
58
|
+
const month = birth.getMonth() + 1;
|
|
59
|
+
const year = birth.getFullYear();
|
|
60
|
+
const selectedGender = gender ?? (Math.random() < 0.5 ? "M" : "F");
|
|
61
|
+
const encodedDay = selectedGender === "F" ? day + 40 : day;
|
|
62
|
+
const ddStr = padZero(encodedDay, 2);
|
|
63
|
+
const mmStr = padZero(month, 2);
|
|
64
|
+
const yyStr = padZero(year % 100, 2);
|
|
65
|
+
const seq = padZero(randomInt(1, 9999), 4);
|
|
66
|
+
return districtCode + ddStr + mmStr + yyStr + seq;
|
|
67
|
+
}
|
|
68
|
+
export {
|
|
69
|
+
generateNIK
|
|
70
|
+
};
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
generateNIK: () => generateNIK,
|
|
24
|
+
parseNIK: () => parseNIK,
|
|
25
|
+
validateNIK: () => validateNIK
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(index_exports);
|
|
28
|
+
|
|
29
|
+
// src/generate.ts
|
|
30
|
+
var MIN_PROVINCE_CODE = 11;
|
|
31
|
+
var MAX_PROVINCE_CODE = 97;
|
|
32
|
+
var MIN_BIRTH_YEAR = 1950;
|
|
33
|
+
var MAX_BIRTH_YEAR = 2005;
|
|
34
|
+
function randomInt(min, max) {
|
|
35
|
+
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
36
|
+
}
|
|
37
|
+
function padZero(num, length) {
|
|
38
|
+
return String(num).padStart(length, "0");
|
|
39
|
+
}
|
|
40
|
+
function getDaysInMonth(year, month) {
|
|
41
|
+
return new Date(year, month, 0).getDate();
|
|
42
|
+
}
|
|
43
|
+
function randomBirthDate() {
|
|
44
|
+
const year = randomInt(MIN_BIRTH_YEAR, MAX_BIRTH_YEAR);
|
|
45
|
+
const month = randomInt(1, 12);
|
|
46
|
+
const maxDay = getDaysInMonth(year, month);
|
|
47
|
+
const day = randomInt(1, maxDay);
|
|
48
|
+
return new Date(year, month - 1, day);
|
|
49
|
+
}
|
|
50
|
+
function generateNIK(options = {}) {
|
|
51
|
+
const { gender, birthDate } = options;
|
|
52
|
+
let provinceCode;
|
|
53
|
+
if (options.provinceCode !== void 0) {
|
|
54
|
+
const pc = Number.parseInt(options.provinceCode, 10);
|
|
55
|
+
if (Number.isNaN(pc) || pc < MIN_PROVINCE_CODE || pc > MAX_PROVINCE_CODE) {
|
|
56
|
+
throw new Error("Kode provinsi tidak valid (harus 11-97)");
|
|
57
|
+
}
|
|
58
|
+
provinceCode = padZero(pc, 2);
|
|
59
|
+
} else {
|
|
60
|
+
provinceCode = padZero(randomInt(MIN_PROVINCE_CODE, MAX_PROVINCE_CODE), 2);
|
|
61
|
+
}
|
|
62
|
+
let regencyCode;
|
|
63
|
+
if (options.regencyCode !== void 0) {
|
|
64
|
+
if (!options.regencyCode.startsWith(provinceCode)) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
`Kode kabupaten/kota "${options.regencyCode}" tidak sesuai dengan kode provinsi "${provinceCode}"`
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
regencyCode = options.regencyCode;
|
|
70
|
+
} else {
|
|
71
|
+
regencyCode = provinceCode + padZero(randomInt(1, 99), 2);
|
|
72
|
+
}
|
|
73
|
+
let districtCode;
|
|
74
|
+
if (options.districtCode !== void 0) {
|
|
75
|
+
if (!options.districtCode.startsWith(regencyCode)) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
`Kode kecamatan "${options.districtCode}" tidak sesuai dengan kode kabupaten/kota "${regencyCode}"`
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
districtCode = options.districtCode;
|
|
81
|
+
} else {
|
|
82
|
+
districtCode = regencyCode + padZero(randomInt(1, 99), 2);
|
|
83
|
+
}
|
|
84
|
+
const birth = birthDate ?? randomBirthDate();
|
|
85
|
+
const day = birth.getDate();
|
|
86
|
+
const month = birth.getMonth() + 1;
|
|
87
|
+
const year = birth.getFullYear();
|
|
88
|
+
const selectedGender = gender ?? (Math.random() < 0.5 ? "M" : "F");
|
|
89
|
+
const encodedDay = selectedGender === "F" ? day + 40 : day;
|
|
90
|
+
const ddStr = padZero(encodedDay, 2);
|
|
91
|
+
const mmStr = padZero(month, 2);
|
|
92
|
+
const yyStr = padZero(year % 100, 2);
|
|
93
|
+
const seq = padZero(randomInt(1, 9999), 4);
|
|
94
|
+
return districtCode + ddStr + mmStr + yyStr + seq;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// src/utils.ts
|
|
98
|
+
function disambiguateYear(yy) {
|
|
99
|
+
const currentYY = (/* @__PURE__ */ new Date()).getFullYear() % 100;
|
|
100
|
+
return yy > currentYY ? 1900 + yy : 2e3 + yy;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// src/validate.ts
|
|
104
|
+
var MIN_PROVINCE_CODE2 = 11;
|
|
105
|
+
var MAX_PROVINCE_CODE2 = 97;
|
|
106
|
+
var NIK_LENGTH = 16;
|
|
107
|
+
var DIGITS_ONLY_PATTERN = /^\d{16}$/;
|
|
108
|
+
function isValidCalendarDate(year, month, day) {
|
|
109
|
+
const date = new Date(year, month - 1, day);
|
|
110
|
+
return date.getFullYear() === year && date.getMonth() === month - 1 && date.getDate() === day;
|
|
111
|
+
}
|
|
112
|
+
function validateNIK(nik) {
|
|
113
|
+
if (typeof nik !== "string") {
|
|
114
|
+
return { valid: false, error: "NIK harus berupa string" };
|
|
115
|
+
}
|
|
116
|
+
if (nik.length !== NIK_LENGTH) {
|
|
117
|
+
return { valid: false, error: "NIK harus 16 digit" };
|
|
118
|
+
}
|
|
119
|
+
if (!DIGITS_ONLY_PATTERN.test(nik)) {
|
|
120
|
+
return { valid: false, error: "NIK hanya boleh berisi angka" };
|
|
121
|
+
}
|
|
122
|
+
const provinceCode = Number.parseInt(nik.substring(0, 2), 10);
|
|
123
|
+
if (provinceCode < MIN_PROVINCE_CODE2 || provinceCode > MAX_PROVINCE_CODE2) {
|
|
124
|
+
return { valid: false, error: "Kode provinsi tidak valid" };
|
|
125
|
+
}
|
|
126
|
+
const dd = Number.parseInt(nik.substring(6, 8), 10);
|
|
127
|
+
const mm = Number.parseInt(nik.substring(8, 10), 10);
|
|
128
|
+
const yy = Number.parseInt(nik.substring(10, 12), 10);
|
|
129
|
+
const isValidMaleDD = dd >= 1 && dd <= 31;
|
|
130
|
+
const isValidFemaleDD = dd >= 41 && dd <= 71;
|
|
131
|
+
if (!isValidMaleDD && !isValidFemaleDD) {
|
|
132
|
+
return { valid: false, error: "Tanggal lahir tidak valid" };
|
|
133
|
+
}
|
|
134
|
+
if (mm < 1 || mm > 12) {
|
|
135
|
+
return { valid: false, error: "Tanggal lahir tidak valid" };
|
|
136
|
+
}
|
|
137
|
+
const actualDay = isValidFemaleDD ? dd - 40 : dd;
|
|
138
|
+
const fullYear = disambiguateYear(yy);
|
|
139
|
+
if (!isValidCalendarDate(fullYear, mm, actualDay)) {
|
|
140
|
+
return { valid: false, error: "Tanggal lahir tidak valid" };
|
|
141
|
+
}
|
|
142
|
+
const seq = nik.substring(12, 16);
|
|
143
|
+
if (seq === "0000") {
|
|
144
|
+
return { valid: false, error: "Nomor urut tidak valid" };
|
|
145
|
+
}
|
|
146
|
+
return { valid: true };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// src/parse.ts
|
|
150
|
+
function parseNIK(nik) {
|
|
151
|
+
const validation = validateNIK(nik);
|
|
152
|
+
if (!validation.valid) {
|
|
153
|
+
return { valid: false, error: validation.error };
|
|
154
|
+
}
|
|
155
|
+
const provinceCode = nik.substring(0, 2);
|
|
156
|
+
const regencyCode = nik.substring(0, 4);
|
|
157
|
+
const districtCode = nik.substring(0, 6);
|
|
158
|
+
const dd = Number.parseInt(nik.substring(6, 8), 10);
|
|
159
|
+
const mm = Number.parseInt(nik.substring(8, 10), 10);
|
|
160
|
+
const yy = Number.parseInt(nik.substring(10, 12), 10);
|
|
161
|
+
const isFemale = dd > 40;
|
|
162
|
+
const actualDay = isFemale ? dd - 40 : dd;
|
|
163
|
+
const gender = isFemale ? "F" : "M";
|
|
164
|
+
const fullYear = disambiguateYear(yy);
|
|
165
|
+
const birthDate = new Date(fullYear, mm - 1, actualDay);
|
|
166
|
+
const sequenceNumber = nik.substring(12, 16);
|
|
167
|
+
return {
|
|
168
|
+
valid: true,
|
|
169
|
+
nik,
|
|
170
|
+
provinceCode,
|
|
171
|
+
regencyCode,
|
|
172
|
+
districtCode,
|
|
173
|
+
birthDate,
|
|
174
|
+
gender,
|
|
175
|
+
sequenceNumber
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
179
|
+
0 && (module.exports = {
|
|
180
|
+
generateNIK,
|
|
181
|
+
parseNIK,
|
|
182
|
+
validateNIK
|
|
183
|
+
});
|
package/dist/index.d.cts
ADDED
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// src/generate.ts
|
|
2
|
+
var MIN_PROVINCE_CODE = 11;
|
|
3
|
+
var MAX_PROVINCE_CODE = 97;
|
|
4
|
+
var MIN_BIRTH_YEAR = 1950;
|
|
5
|
+
var MAX_BIRTH_YEAR = 2005;
|
|
6
|
+
function randomInt(min, max) {
|
|
7
|
+
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
8
|
+
}
|
|
9
|
+
function padZero(num, length) {
|
|
10
|
+
return String(num).padStart(length, "0");
|
|
11
|
+
}
|
|
12
|
+
function getDaysInMonth(year, month) {
|
|
13
|
+
return new Date(year, month, 0).getDate();
|
|
14
|
+
}
|
|
15
|
+
function randomBirthDate() {
|
|
16
|
+
const year = randomInt(MIN_BIRTH_YEAR, MAX_BIRTH_YEAR);
|
|
17
|
+
const month = randomInt(1, 12);
|
|
18
|
+
const maxDay = getDaysInMonth(year, month);
|
|
19
|
+
const day = randomInt(1, maxDay);
|
|
20
|
+
return new Date(year, month - 1, day);
|
|
21
|
+
}
|
|
22
|
+
function generateNIK(options = {}) {
|
|
23
|
+
const { gender, birthDate } = options;
|
|
24
|
+
let provinceCode;
|
|
25
|
+
if (options.provinceCode !== void 0) {
|
|
26
|
+
const pc = Number.parseInt(options.provinceCode, 10);
|
|
27
|
+
if (Number.isNaN(pc) || pc < MIN_PROVINCE_CODE || pc > MAX_PROVINCE_CODE) {
|
|
28
|
+
throw new Error("Kode provinsi tidak valid (harus 11-97)");
|
|
29
|
+
}
|
|
30
|
+
provinceCode = padZero(pc, 2);
|
|
31
|
+
} else {
|
|
32
|
+
provinceCode = padZero(randomInt(MIN_PROVINCE_CODE, MAX_PROVINCE_CODE), 2);
|
|
33
|
+
}
|
|
34
|
+
let regencyCode;
|
|
35
|
+
if (options.regencyCode !== void 0) {
|
|
36
|
+
if (!options.regencyCode.startsWith(provinceCode)) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
`Kode kabupaten/kota "${options.regencyCode}" tidak sesuai dengan kode provinsi "${provinceCode}"`
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
regencyCode = options.regencyCode;
|
|
42
|
+
} else {
|
|
43
|
+
regencyCode = provinceCode + padZero(randomInt(1, 99), 2);
|
|
44
|
+
}
|
|
45
|
+
let districtCode;
|
|
46
|
+
if (options.districtCode !== void 0) {
|
|
47
|
+
if (!options.districtCode.startsWith(regencyCode)) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
`Kode kecamatan "${options.districtCode}" tidak sesuai dengan kode kabupaten/kota "${regencyCode}"`
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
districtCode = options.districtCode;
|
|
53
|
+
} else {
|
|
54
|
+
districtCode = regencyCode + padZero(randomInt(1, 99), 2);
|
|
55
|
+
}
|
|
56
|
+
const birth = birthDate ?? randomBirthDate();
|
|
57
|
+
const day = birth.getDate();
|
|
58
|
+
const month = birth.getMonth() + 1;
|
|
59
|
+
const year = birth.getFullYear();
|
|
60
|
+
const selectedGender = gender ?? (Math.random() < 0.5 ? "M" : "F");
|
|
61
|
+
const encodedDay = selectedGender === "F" ? day + 40 : day;
|
|
62
|
+
const ddStr = padZero(encodedDay, 2);
|
|
63
|
+
const mmStr = padZero(month, 2);
|
|
64
|
+
const yyStr = padZero(year % 100, 2);
|
|
65
|
+
const seq = padZero(randomInt(1, 9999), 4);
|
|
66
|
+
return districtCode + ddStr + mmStr + yyStr + seq;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// src/utils.ts
|
|
70
|
+
function disambiguateYear(yy) {
|
|
71
|
+
const currentYY = (/* @__PURE__ */ new Date()).getFullYear() % 100;
|
|
72
|
+
return yy > currentYY ? 1900 + yy : 2e3 + yy;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// src/validate.ts
|
|
76
|
+
var MIN_PROVINCE_CODE2 = 11;
|
|
77
|
+
var MAX_PROVINCE_CODE2 = 97;
|
|
78
|
+
var NIK_LENGTH = 16;
|
|
79
|
+
var DIGITS_ONLY_PATTERN = /^\d{16}$/;
|
|
80
|
+
function isValidCalendarDate(year, month, day) {
|
|
81
|
+
const date = new Date(year, month - 1, day);
|
|
82
|
+
return date.getFullYear() === year && date.getMonth() === month - 1 && date.getDate() === day;
|
|
83
|
+
}
|
|
84
|
+
function validateNIK(nik) {
|
|
85
|
+
if (typeof nik !== "string") {
|
|
86
|
+
return { valid: false, error: "NIK harus berupa string" };
|
|
87
|
+
}
|
|
88
|
+
if (nik.length !== NIK_LENGTH) {
|
|
89
|
+
return { valid: false, error: "NIK harus 16 digit" };
|
|
90
|
+
}
|
|
91
|
+
if (!DIGITS_ONLY_PATTERN.test(nik)) {
|
|
92
|
+
return { valid: false, error: "NIK hanya boleh berisi angka" };
|
|
93
|
+
}
|
|
94
|
+
const provinceCode = Number.parseInt(nik.substring(0, 2), 10);
|
|
95
|
+
if (provinceCode < MIN_PROVINCE_CODE2 || provinceCode > MAX_PROVINCE_CODE2) {
|
|
96
|
+
return { valid: false, error: "Kode provinsi tidak valid" };
|
|
97
|
+
}
|
|
98
|
+
const dd = Number.parseInt(nik.substring(6, 8), 10);
|
|
99
|
+
const mm = Number.parseInt(nik.substring(8, 10), 10);
|
|
100
|
+
const yy = Number.parseInt(nik.substring(10, 12), 10);
|
|
101
|
+
const isValidMaleDD = dd >= 1 && dd <= 31;
|
|
102
|
+
const isValidFemaleDD = dd >= 41 && dd <= 71;
|
|
103
|
+
if (!isValidMaleDD && !isValidFemaleDD) {
|
|
104
|
+
return { valid: false, error: "Tanggal lahir tidak valid" };
|
|
105
|
+
}
|
|
106
|
+
if (mm < 1 || mm > 12) {
|
|
107
|
+
return { valid: false, error: "Tanggal lahir tidak valid" };
|
|
108
|
+
}
|
|
109
|
+
const actualDay = isValidFemaleDD ? dd - 40 : dd;
|
|
110
|
+
const fullYear = disambiguateYear(yy);
|
|
111
|
+
if (!isValidCalendarDate(fullYear, mm, actualDay)) {
|
|
112
|
+
return { valid: false, error: "Tanggal lahir tidak valid" };
|
|
113
|
+
}
|
|
114
|
+
const seq = nik.substring(12, 16);
|
|
115
|
+
if (seq === "0000") {
|
|
116
|
+
return { valid: false, error: "Nomor urut tidak valid" };
|
|
117
|
+
}
|
|
118
|
+
return { valid: true };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// src/parse.ts
|
|
122
|
+
function parseNIK(nik) {
|
|
123
|
+
const validation = validateNIK(nik);
|
|
124
|
+
if (!validation.valid) {
|
|
125
|
+
return { valid: false, error: validation.error };
|
|
126
|
+
}
|
|
127
|
+
const provinceCode = nik.substring(0, 2);
|
|
128
|
+
const regencyCode = nik.substring(0, 4);
|
|
129
|
+
const districtCode = nik.substring(0, 6);
|
|
130
|
+
const dd = Number.parseInt(nik.substring(6, 8), 10);
|
|
131
|
+
const mm = Number.parseInt(nik.substring(8, 10), 10);
|
|
132
|
+
const yy = Number.parseInt(nik.substring(10, 12), 10);
|
|
133
|
+
const isFemale = dd > 40;
|
|
134
|
+
const actualDay = isFemale ? dd - 40 : dd;
|
|
135
|
+
const gender = isFemale ? "F" : "M";
|
|
136
|
+
const fullYear = disambiguateYear(yy);
|
|
137
|
+
const birthDate = new Date(fullYear, mm - 1, actualDay);
|
|
138
|
+
const sequenceNumber = nik.substring(12, 16);
|
|
139
|
+
return {
|
|
140
|
+
valid: true,
|
|
141
|
+
nik,
|
|
142
|
+
provinceCode,
|
|
143
|
+
regencyCode,
|
|
144
|
+
districtCode,
|
|
145
|
+
birthDate,
|
|
146
|
+
gender,
|
|
147
|
+
sequenceNumber
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
export {
|
|
151
|
+
generateNIK,
|
|
152
|
+
parseNIK,
|
|
153
|
+
validateNIK
|
|
154
|
+
};
|
package/dist/parse.cjs
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/parse.ts
|
|
21
|
+
var parse_exports = {};
|
|
22
|
+
__export(parse_exports, {
|
|
23
|
+
parseNIK: () => parseNIK
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(parse_exports);
|
|
26
|
+
|
|
27
|
+
// src/utils.ts
|
|
28
|
+
function disambiguateYear(yy) {
|
|
29
|
+
const currentYY = (/* @__PURE__ */ new Date()).getFullYear() % 100;
|
|
30
|
+
return yy > currentYY ? 1900 + yy : 2e3 + yy;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// src/validate.ts
|
|
34
|
+
var MIN_PROVINCE_CODE = 11;
|
|
35
|
+
var MAX_PROVINCE_CODE = 97;
|
|
36
|
+
var NIK_LENGTH = 16;
|
|
37
|
+
var DIGITS_ONLY_PATTERN = /^\d{16}$/;
|
|
38
|
+
function isValidCalendarDate(year, month, day) {
|
|
39
|
+
const date = new Date(year, month - 1, day);
|
|
40
|
+
return date.getFullYear() === year && date.getMonth() === month - 1 && date.getDate() === day;
|
|
41
|
+
}
|
|
42
|
+
function validateNIK(nik) {
|
|
43
|
+
if (typeof nik !== "string") {
|
|
44
|
+
return { valid: false, error: "NIK harus berupa string" };
|
|
45
|
+
}
|
|
46
|
+
if (nik.length !== NIK_LENGTH) {
|
|
47
|
+
return { valid: false, error: "NIK harus 16 digit" };
|
|
48
|
+
}
|
|
49
|
+
if (!DIGITS_ONLY_PATTERN.test(nik)) {
|
|
50
|
+
return { valid: false, error: "NIK hanya boleh berisi angka" };
|
|
51
|
+
}
|
|
52
|
+
const provinceCode = Number.parseInt(nik.substring(0, 2), 10);
|
|
53
|
+
if (provinceCode < MIN_PROVINCE_CODE || provinceCode > MAX_PROVINCE_CODE) {
|
|
54
|
+
return { valid: false, error: "Kode provinsi tidak valid" };
|
|
55
|
+
}
|
|
56
|
+
const dd = Number.parseInt(nik.substring(6, 8), 10);
|
|
57
|
+
const mm = Number.parseInt(nik.substring(8, 10), 10);
|
|
58
|
+
const yy = Number.parseInt(nik.substring(10, 12), 10);
|
|
59
|
+
const isValidMaleDD = dd >= 1 && dd <= 31;
|
|
60
|
+
const isValidFemaleDD = dd >= 41 && dd <= 71;
|
|
61
|
+
if (!isValidMaleDD && !isValidFemaleDD) {
|
|
62
|
+
return { valid: false, error: "Tanggal lahir tidak valid" };
|
|
63
|
+
}
|
|
64
|
+
if (mm < 1 || mm > 12) {
|
|
65
|
+
return { valid: false, error: "Tanggal lahir tidak valid" };
|
|
66
|
+
}
|
|
67
|
+
const actualDay = isValidFemaleDD ? dd - 40 : dd;
|
|
68
|
+
const fullYear = disambiguateYear(yy);
|
|
69
|
+
if (!isValidCalendarDate(fullYear, mm, actualDay)) {
|
|
70
|
+
return { valid: false, error: "Tanggal lahir tidak valid" };
|
|
71
|
+
}
|
|
72
|
+
const seq = nik.substring(12, 16);
|
|
73
|
+
if (seq === "0000") {
|
|
74
|
+
return { valid: false, error: "Nomor urut tidak valid" };
|
|
75
|
+
}
|
|
76
|
+
return { valid: true };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// src/parse.ts
|
|
80
|
+
function parseNIK(nik) {
|
|
81
|
+
const validation = validateNIK(nik);
|
|
82
|
+
if (!validation.valid) {
|
|
83
|
+
return { valid: false, error: validation.error };
|
|
84
|
+
}
|
|
85
|
+
const provinceCode = nik.substring(0, 2);
|
|
86
|
+
const regencyCode = nik.substring(0, 4);
|
|
87
|
+
const districtCode = nik.substring(0, 6);
|
|
88
|
+
const dd = Number.parseInt(nik.substring(6, 8), 10);
|
|
89
|
+
const mm = Number.parseInt(nik.substring(8, 10), 10);
|
|
90
|
+
const yy = Number.parseInt(nik.substring(10, 12), 10);
|
|
91
|
+
const isFemale = dd > 40;
|
|
92
|
+
const actualDay = isFemale ? dd - 40 : dd;
|
|
93
|
+
const gender = isFemale ? "F" : "M";
|
|
94
|
+
const fullYear = disambiguateYear(yy);
|
|
95
|
+
const birthDate = new Date(fullYear, mm - 1, actualDay);
|
|
96
|
+
const sequenceNumber = nik.substring(12, 16);
|
|
97
|
+
return {
|
|
98
|
+
valid: true,
|
|
99
|
+
nik,
|
|
100
|
+
provinceCode,
|
|
101
|
+
regencyCode,
|
|
102
|
+
districtCode,
|
|
103
|
+
birthDate,
|
|
104
|
+
gender,
|
|
105
|
+
sequenceNumber
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
109
|
+
0 && (module.exports = {
|
|
110
|
+
parseNIK
|
|
111
|
+
});
|
package/dist/parse.d.cts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { NIKResult } from './types.cjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Modul parsing NIK (Nomor Induk Kependudukan).
|
|
5
|
+
*
|
|
6
|
+
* Menyediakan fungsi {@link parseNIK} yang memvalidasi dan mengekstrak
|
|
7
|
+
* semua komponen dari NIK: kode wilayah (provinsi, kabupaten/kota, kecamatan),
|
|
8
|
+
* tanggal lahir, jenis kelamin, dan nomor urut registrasi.
|
|
9
|
+
*
|
|
10
|
+
* Kode wilayah yang dikembalikan menggunakan format **Kemendagri** (bukan BPS).
|
|
11
|
+
* Untuk resolve ke nama wilayah, gunakan package `kode-wilayah-id` dengan
|
|
12
|
+
* fungsi `getProvinceByKemendagriCode()`, `getRegencyByKemendagriCode()`, dll.
|
|
13
|
+
*
|
|
14
|
+
* @module parse
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* import { parseNIK } from 'nik-id/parse';
|
|
19
|
+
*
|
|
20
|
+
* const result = parseNIK("3204076508850001");
|
|
21
|
+
* if (result.valid) {
|
|
22
|
+
* console.log(result.provinceCode); // "32"
|
|
23
|
+
* console.log(result.regencyCode); // "3204"
|
|
24
|
+
* console.log(result.districtCode); // "320407"
|
|
25
|
+
* console.log(result.gender); // "F"
|
|
26
|
+
* console.log(result.birthDate); // Date: 1985-08-25
|
|
27
|
+
* console.log(result.sequenceNumber); // "0001"
|
|
28
|
+
* }
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Parse NIK menjadi komponen-komponennya.
|
|
34
|
+
*
|
|
35
|
+
* Fungsi ini pertama memvalidasi NIK menggunakan {@link validateNIK},
|
|
36
|
+
* kemudian mengekstrak setiap komponen ke dalam object {@link NIKResult}.
|
|
37
|
+
*
|
|
38
|
+
* Komponen yang diekstrak:
|
|
39
|
+
* - **provinceCode** — kode provinsi Kemendagri (2 digit)
|
|
40
|
+
* - **regencyCode** — kode kabupaten/kota Kemendagri (4 digit)
|
|
41
|
+
* - **districtCode** — kode kecamatan Kemendagri (6 digit)
|
|
42
|
+
* - **birthDate** — tanggal lahir sebagai `Date` object
|
|
43
|
+
* - **gender** — jenis kelamin (`"M"` atau `"F"`)
|
|
44
|
+
* - **sequenceNumber** — nomor urut registrasi (4 digit)
|
|
45
|
+
*
|
|
46
|
+
* @param nik - String NIK 16 digit yang akan di-parse
|
|
47
|
+
* @returns {@link NIKResult} — discriminated union yang bisa di-narrow
|
|
48
|
+
* menggunakan `result.valid`
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```typescript
|
|
52
|
+
* const result = parseNIK("3204076508850001");
|
|
53
|
+
* if (result.valid) {
|
|
54
|
+
* // result bertipe NIKValid — semua field tersedia
|
|
55
|
+
* console.log(result.provinceCode); // "32"
|
|
56
|
+
* console.log(result.regencyCode); // "3204"
|
|
57
|
+
* console.log(result.districtCode); // "320407"
|
|
58
|
+
* console.log(result.birthDate); // Date: 1985-08-25
|
|
59
|
+
* console.log(result.gender); // "F"
|
|
60
|
+
* console.log(result.sequenceNumber); // "0001"
|
|
61
|
+
* } else {
|
|
62
|
+
* // result bertipe NIKInvalid — ada error
|
|
63
|
+
* console.log(result.error); // "NIK harus 16 digit"
|
|
64
|
+
* }
|
|
65
|
+
* ```
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```typescript
|
|
69
|
+
* // Integrasi dengan kode-wilayah-id
|
|
70
|
+
* import { getProvinceByKemendagriCode } from 'kode-wilayah-id';
|
|
71
|
+
*
|
|
72
|
+
* const result = parseNIK("3204076508850001");
|
|
73
|
+
* if (result.valid) {
|
|
74
|
+
* const prov = getProvinceByKemendagriCode(result.provinceCode);
|
|
75
|
+
* console.log(prov?.name); // "JAWA BARAT"
|
|
76
|
+
* }
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
declare function parseNIK(nik: string): NIKResult;
|
|
80
|
+
|
|
81
|
+
export { parseNIK };
|