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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sumitro Aji Prabowo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
# nik-id
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/nik-id)
|
|
4
|
+
[](https://github.com/sumitroajiprabowo/nik-id/actions/workflows/ci.yml)
|
|
5
|
+
[](https://www.typescriptlang.org/)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
[](https://nodejs.org/)
|
|
8
|
+
[](https://www.npmjs.com/package/nik-id)
|
|
9
|
+
[](https://www.npmjs.com/package/nik-id)
|
|
10
|
+
|
|
11
|
+
Parser, validator, dan generator **NIK** (Nomor Induk Kependudukan) Indonesia.
|
|
12
|
+
|
|
13
|
+
TypeScript-first, zero dependencies, 100% test coverage, dual ESM+CJS, tree-shakeable.
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install nik-id
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Tidak perlu database, API call, atau file eksternal. Cukup install dan langsung pakai.
|
|
20
|
+
|
|
21
|
+
## Fitur
|
|
22
|
+
|
|
23
|
+
- **Validasi format** -- cek apakah NIK valid (panjang, digit, provinsi, tanggal lahir, nomor urut)
|
|
24
|
+
- **Parse komponen** -- ekstrak kode wilayah, tanggal lahir, gender, dan nomor urut dari NIK
|
|
25
|
+
- **Generate NIK** -- buat NIK yang valid secara format untuk testing, seeding, atau demo
|
|
26
|
+
- **Discriminated union** -- return type aman di-narrow pakai `result.valid` (TypeScript-friendly)
|
|
27
|
+
- **Error bahasa Indonesia** -- semua pesan error dalam bahasa Indonesia
|
|
28
|
+
- **Tree-shakeable** -- import hanya fungsi yang dibutuhkan via sub-path
|
|
29
|
+
- **TypeScript-first** -- strict types, auto-complete di IDE
|
|
30
|
+
- **Dual ESM + CJS** -- support semua environment (browser, Node.js, Bun, Deno)
|
|
31
|
+
- **Zero dependencies** -- tidak ada dependency runtime sama sekali
|
|
32
|
+
- **100% coverage** -- statements, branches, functions, lines
|
|
33
|
+
|
|
34
|
+
## Struktur NIK
|
|
35
|
+
|
|
36
|
+
NIK (Nomor Induk Kependudukan) adalah nomor identitas penduduk Indonesia yang terdiri dari **16 digit angka**:
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
3204 07 650885 0001
|
|
40
|
+
│ │ │ └── Nomor urut registrasi (4 digit, 0001-9999)
|
|
41
|
+
│ │ └── Tanggal lahir DDMMYY — perempuan: DD + 40 (jadi 41-71)
|
|
42
|
+
│ └── Kode kecamatan (2 digit, format Kemendagri)
|
|
43
|
+
└── Kode kabupaten/kota (4 digit, format Kemendagri)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
| Posisi | Digit | Keterangan | Contoh |
|
|
47
|
+
|--------|-------|------------|--------|
|
|
48
|
+
| 1-2 | 2 digit | Kode provinsi Kemendagri (11-97) | `32` = Jawa Barat |
|
|
49
|
+
| 3-4 | 2 digit | Kode kabupaten/kota | `04` = Kab. Bandung |
|
|
50
|
+
| 5-6 | 2 digit | Kode kecamatan | `07` = Nagreg |
|
|
51
|
+
| 7-8 | 2 digit | Tanggal lahir (DD), perempuan +40 | `65` = tanggal 25 (perempuan) |
|
|
52
|
+
| 9-10 | 2 digit | Bulan lahir (MM) | `08` = Agustus |
|
|
53
|
+
| 11-12 | 2 digit | Tahun lahir (YY) | `85` = 1985 |
|
|
54
|
+
| 13-16 | 4 digit | Nomor urut registrasi | `0001` |
|
|
55
|
+
|
|
56
|
+
**Catatan penting:**
|
|
57
|
+
- NIK menggunakan kode **Kemendagri** (Kementerian Dalam Negeri), bukan BPS
|
|
58
|
+
- Perempuan: tanggal lahir ditambah 40 (DD 01-31 → DD 41-71)
|
|
59
|
+
- Tahun 2 digit: disambiguasi berdasarkan tahun sekarang (YY > sekarang → 1900+YY, else 2000+YY)
|
|
60
|
+
|
|
61
|
+
## Quick Start
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
import { validateNIK, parseNIK, generateNIK } from 'nik-id';
|
|
65
|
+
|
|
66
|
+
// Validasi — cek apakah NIK valid
|
|
67
|
+
const valid = validateNIK("3204076508850001");
|
|
68
|
+
console.log(valid); // { valid: true }
|
|
69
|
+
|
|
70
|
+
// Parse — ekstrak semua komponen
|
|
71
|
+
const parsed = parseNIK("3204076508850001");
|
|
72
|
+
if (parsed.valid) {
|
|
73
|
+
console.log(parsed.provinceCode); // "32"
|
|
74
|
+
console.log(parsed.regencyCode); // "3204"
|
|
75
|
+
console.log(parsed.districtCode); // "320407"
|
|
76
|
+
console.log(parsed.gender); // "F"
|
|
77
|
+
console.log(parsed.birthDate); // Date: 1985-08-25
|
|
78
|
+
console.log(parsed.sequenceNumber); // "0001"
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Generate — buat NIK untuk testing
|
|
82
|
+
const nik = generateNIK({ gender: "F", birthDate: new Date("1985-08-25") });
|
|
83
|
+
console.log(nik); // "xxxxxx6508850001" (wilayah random)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## API Reference
|
|
87
|
+
|
|
88
|
+
### `validateNIK(nik: string): ValidationResult`
|
|
89
|
+
|
|
90
|
+
Validasi format NIK secara bertahap (fail-fast). Return discriminated union:
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
import { validateNIK } from 'nik-id';
|
|
94
|
+
// atau: import { validateNIK } from 'nik-id/validate';
|
|
95
|
+
|
|
96
|
+
// NIK valid
|
|
97
|
+
validateNIK("3204076508850001");
|
|
98
|
+
// → { valid: true }
|
|
99
|
+
|
|
100
|
+
// Panjang salah
|
|
101
|
+
validateNIK("123");
|
|
102
|
+
// → { valid: false, error: "NIK harus 16 digit" }
|
|
103
|
+
|
|
104
|
+
// Mengandung huruf
|
|
105
|
+
validateNIK("320407650885000A");
|
|
106
|
+
// → { valid: false, error: "NIK hanya boleh berisi angka" }
|
|
107
|
+
|
|
108
|
+
// Kode provinsi di luar range
|
|
109
|
+
validateNIK("0004076508850001");
|
|
110
|
+
// → { valid: false, error: "Kode provinsi tidak valid" }
|
|
111
|
+
|
|
112
|
+
// Tanggal tidak ada di kalender (31 Februari)
|
|
113
|
+
validateNIK("3204073102850001");
|
|
114
|
+
// → { valid: false, error: "Tanggal lahir tidak valid" }
|
|
115
|
+
|
|
116
|
+
// Nomor urut 0000
|
|
117
|
+
validateNIK("3204076508850000");
|
|
118
|
+
// → { valid: false, error: "Nomor urut tidak valid" }
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### `parseNIK(nik: string): NIKResult`
|
|
122
|
+
|
|
123
|
+
Parse NIK menjadi komponen-komponennya. Memvalidasi terlebih dahulu — kalau tidak valid, return error.
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
import { parseNIK } from 'nik-id';
|
|
127
|
+
// atau: import { parseNIK } from 'nik-id/parse';
|
|
128
|
+
|
|
129
|
+
const result = parseNIK("3204076508850001");
|
|
130
|
+
if (result.valid) {
|
|
131
|
+
console.log(result.nik); // "3204076508850001"
|
|
132
|
+
console.log(result.provinceCode); // "32" — Jawa Barat
|
|
133
|
+
console.log(result.regencyCode); // "3204" — Kab. Bandung
|
|
134
|
+
console.log(result.districtCode); // "320407" — Nagreg
|
|
135
|
+
console.log(result.gender); // "F" — Perempuan
|
|
136
|
+
console.log(result.birthDate); // Date: 1985-08-25
|
|
137
|
+
console.log(result.sequenceNumber); // "0001"
|
|
138
|
+
} else {
|
|
139
|
+
console.log(result.error);
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**Komponen yang diekstrak:**
|
|
144
|
+
|
|
145
|
+
| Field | Tipe | Keterangan | Contoh |
|
|
146
|
+
|-------|------|------------|--------|
|
|
147
|
+
| `nik` | `string` | NIK asli yang di-parse | `"3204076508850001"` |
|
|
148
|
+
| `provinceCode` | `string` | Kode provinsi Kemendagri (2 digit) | `"32"` |
|
|
149
|
+
| `regencyCode` | `string` | Kode kabupaten/kota Kemendagri (4 digit) | `"3204"` |
|
|
150
|
+
| `districtCode` | `string` | Kode kecamatan Kemendagri (6 digit) | `"320407"` |
|
|
151
|
+
| `birthDate` | `Date` | Tanggal lahir (object Date) | `Date(1985-08-25)` |
|
|
152
|
+
| `gender` | `"M" \| "F"` | Jenis kelamin | `"F"` |
|
|
153
|
+
| `sequenceNumber` | `string` | Nomor urut registrasi (4 digit) | `"0001"` |
|
|
154
|
+
|
|
155
|
+
### `generateNIK(options?: GenerateOptions): string`
|
|
156
|
+
|
|
157
|
+
Generate NIK yang valid secara format. Semua parameter opsional — yang tidak diisi akan di-random.
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
import { generateNIK } from 'nik-id';
|
|
161
|
+
// atau: import { generateNIK } from 'nik-id/generate';
|
|
162
|
+
|
|
163
|
+
// Full random
|
|
164
|
+
generateNIK();
|
|
165
|
+
|
|
166
|
+
// Gender spesifik
|
|
167
|
+
generateNIK({ gender: "F" });
|
|
168
|
+
generateNIK({ gender: "M" });
|
|
169
|
+
|
|
170
|
+
// Tanggal lahir spesifik
|
|
171
|
+
generateNIK({ birthDate: new Date("1985-08-25") });
|
|
172
|
+
|
|
173
|
+
// Gender + tanggal lahir
|
|
174
|
+
generateNIK({ gender: "F", birthDate: new Date("1985-08-25") });
|
|
175
|
+
|
|
176
|
+
// Wilayah spesifik (harus konsisten dari provinsi ke kecamatan)
|
|
177
|
+
generateNIK({
|
|
178
|
+
provinceCode: "32",
|
|
179
|
+
regencyCode: "3204",
|
|
180
|
+
districtCode: "320407",
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// Semua opsi sekaligus
|
|
184
|
+
generateNIK({
|
|
185
|
+
provinceCode: "32",
|
|
186
|
+
regencyCode: "3204",
|
|
187
|
+
districtCode: "320407",
|
|
188
|
+
gender: "F",
|
|
189
|
+
birthDate: new Date("1985-08-25"),
|
|
190
|
+
});
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
**Opsi yang tersedia:**
|
|
194
|
+
|
|
195
|
+
| Opsi | Tipe | Keterangan |
|
|
196
|
+
|------|------|------------|
|
|
197
|
+
| `provinceCode` | `string?` | Kode provinsi Kemendagri (2 digit, 11-97) |
|
|
198
|
+
| `regencyCode` | `string?` | Kode kabupaten/kota (4 digit, harus match provinsi) |
|
|
199
|
+
| `districtCode` | `string?` | Kode kecamatan (6 digit, harus match kabupaten) |
|
|
200
|
+
| `gender` | `"M" \| "F"?` | Jenis kelamin |
|
|
201
|
+
| `birthDate` | `Date?` | Tanggal lahir (kalau tidak diisi, random 1950-2005) |
|
|
202
|
+
|
|
203
|
+
**Error yang di-throw:**
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
// Kode provinsi di luar range
|
|
207
|
+
generateNIK({ provinceCode: "99" });
|
|
208
|
+
// → Error: "Kode provinsi tidak valid (harus 11-97)"
|
|
209
|
+
|
|
210
|
+
// Kode kabupaten tidak cocok dengan provinsi
|
|
211
|
+
generateNIK({ provinceCode: "32", regencyCode: "3301" });
|
|
212
|
+
// → Error: 'Kode kabupaten/kota "3301" tidak sesuai dengan kode provinsi "32"'
|
|
213
|
+
|
|
214
|
+
// Kode kecamatan tidak cocok dengan kabupaten
|
|
215
|
+
generateNIK({ provinceCode: "32", regencyCode: "3204", districtCode: "320501" });
|
|
216
|
+
// → Error: 'Kode kecamatan "320501" tidak sesuai dengan kode kabupaten/kota "3204"'
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
> **Catatan:** NIK yang dihasilkan valid secara **format** tapi **bukan** NIK asli milik orang sungguhan. Cocok untuk testing, seeding database, atau demo.
|
|
220
|
+
|
|
221
|
+
## Validasi yang Dilakukan
|
|
222
|
+
|
|
223
|
+
`validateNIK` dan `parseNIK` melakukan pengecekan bertahap (fail-fast — berhenti di error pertama):
|
|
224
|
+
|
|
225
|
+
| # | Pengecekan | Detail |
|
|
226
|
+
|---|-----------|--------|
|
|
227
|
+
| 1 | **Tipe data** | Input harus bertipe `string` |
|
|
228
|
+
| 2 | **Panjang** | Harus tepat 16 karakter |
|
|
229
|
+
| 3 | **Format** | Harus semua digit angka (0-9) |
|
|
230
|
+
| 4 | **Kode provinsi** | 2 digit pertama harus dalam range 11-97 |
|
|
231
|
+
| 5 | **Tanggal lahir** | DD (01-31 atau 41-71), MM (01-12), valid di kalender |
|
|
232
|
+
| 6 | **Nomor urut** | Digit 13-16 tidak boleh `0000` |
|
|
233
|
+
|
|
234
|
+
> **Catatan:** Validasi bersifat **format-only**. Package ini tidak mengecek apakah kode wilayah benar-benar terdaftar di database Kemendagri. Untuk lookup nama wilayah, gunakan package [`kode-wilayah-id`](https://www.npmjs.com/package/kode-wilayah-id).
|
|
235
|
+
|
|
236
|
+
### Error Messages
|
|
237
|
+
|
|
238
|
+
Semua pesan error dalam Bahasa Indonesia:
|
|
239
|
+
|
|
240
|
+
| Error | Penyebab | Contoh Input |
|
|
241
|
+
|-------|----------|-------------|
|
|
242
|
+
| `NIK harus berupa string` | Input bukan bertipe string | `12345`, `null`, `undefined` |
|
|
243
|
+
| `NIK harus 16 digit` | Panjang bukan 16 karakter | `"123"`, `""` |
|
|
244
|
+
| `NIK hanya boleh berisi angka` | Mengandung karakter non-digit | `"320407650885000A"` |
|
|
245
|
+
| `Kode provinsi tidak valid` | 2 digit pertama di luar range 11-97 | `"0004076508850001"` |
|
|
246
|
+
| `Tanggal lahir tidak valid` | Tanggal/bulan invalid atau tidak ada di kalender | `"3204073102850001"` |
|
|
247
|
+
| `Nomor urut tidak valid` | 4 digit terakhir adalah `0000` | `"3204076508850000"` |
|
|
248
|
+
|
|
249
|
+
## Tree-shaking / Sub-path Imports
|
|
250
|
+
|
|
251
|
+
Import hanya fungsi yang dibutuhkan — bundler hanya include kode yang di-import:
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
// Hanya validasi
|
|
255
|
+
import { validateNIK } from 'nik-id/validate';
|
|
256
|
+
|
|
257
|
+
// Hanya parsing
|
|
258
|
+
import { parseNIK } from 'nik-id/parse';
|
|
259
|
+
|
|
260
|
+
// Hanya generator
|
|
261
|
+
import { generateNIK } from 'nik-id/generate';
|
|
262
|
+
|
|
263
|
+
// Types only (zero runtime)
|
|
264
|
+
import type { NIKResult, ValidationResult, GenerateOptions } from 'nik-id/types';
|
|
265
|
+
|
|
266
|
+
// Atau import semua sekaligus
|
|
267
|
+
import { validateNIK, parseNIK, generateNIK } from 'nik-id';
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### Bundle Size
|
|
271
|
+
|
|
272
|
+
Package ini sangat ringan karena zero dependencies dan hanya berisi logika validasi:
|
|
273
|
+
|
|
274
|
+
| Import | Ukuran (minified) |
|
|
275
|
+
|--------|-------------------|
|
|
276
|
+
| `nik-id/validate` | ~1.8 KB |
|
|
277
|
+
| `nik-id/parse` | ~2.8 KB |
|
|
278
|
+
| `nik-id/generate` | ~2.3 KB |
|
|
279
|
+
| `nik-id` (full) | ~5.2 KB |
|
|
280
|
+
|
|
281
|
+
## Integrasi dengan kode-wilayah-id
|
|
282
|
+
|
|
283
|
+
Package ini **tidak** menyertakan data wilayah — hanya mengembalikan kode Kemendagri. Untuk resolve ke nama wilayah, gunakan package [`kode-wilayah-id`](https://www.npmjs.com/package/kode-wilayah-id):
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
npm install kode-wilayah-id
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
```typescript
|
|
290
|
+
import { parseNIK } from 'nik-id';
|
|
291
|
+
import {
|
|
292
|
+
getProvinceByKemendagriCode,
|
|
293
|
+
getRegencyByKemendagriCode,
|
|
294
|
+
getDistrictByKemendagriCode,
|
|
295
|
+
} from 'kode-wilayah-id';
|
|
296
|
+
|
|
297
|
+
const result = parseNIK("3204076508850001");
|
|
298
|
+
if (result.valid) {
|
|
299
|
+
const province = getProvinceByKemendagriCode(result.provinceCode);
|
|
300
|
+
const regency = getRegencyByKemendagriCode(result.regencyCode);
|
|
301
|
+
const district = getDistrictByKemendagriCode(result.districtCode);
|
|
302
|
+
|
|
303
|
+
console.log(province?.name); // "JAWA BARAT"
|
|
304
|
+
console.log(regency?.name); // "KAB. BANDUNG"
|
|
305
|
+
console.log(district?.name); // "NAGREG"
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
## Discriminated Union Pattern
|
|
310
|
+
|
|
311
|
+
Return type `parseNIK` dan `validateNIK` menggunakan discriminated union — TypeScript bisa narrow type otomatis berdasarkan `result.valid`:
|
|
312
|
+
|
|
313
|
+
```typescript
|
|
314
|
+
import { parseNIK } from 'nik-id';
|
|
315
|
+
import type { NIKResult, NIKValid, NIKInvalid } from 'nik-id/types';
|
|
316
|
+
|
|
317
|
+
const result: NIKResult = parseNIK(input);
|
|
318
|
+
|
|
319
|
+
if (result.valid) {
|
|
320
|
+
// TypeScript tahu ini NIKValid — semua field tersedia
|
|
321
|
+
console.log(result.provinceCode); // ✅ string
|
|
322
|
+
console.log(result.gender); // ✅ "M" | "F"
|
|
323
|
+
console.log(result.birthDate); // ✅ Date
|
|
324
|
+
} else {
|
|
325
|
+
// TypeScript tahu ini NIKInvalid — hanya error
|
|
326
|
+
console.log(result.error); // ✅ string
|
|
327
|
+
// console.log(result.gender); // ❌ compile error!
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
Pattern yang sama berlaku untuk `validateNIK`:
|
|
332
|
+
|
|
333
|
+
```typescript
|
|
334
|
+
import { validateNIK } from 'nik-id';
|
|
335
|
+
|
|
336
|
+
const result = validateNIK(input);
|
|
337
|
+
if (result.valid) {
|
|
338
|
+
// ValidationValid — tidak ada field lain
|
|
339
|
+
console.log("NIK valid!");
|
|
340
|
+
} else {
|
|
341
|
+
// ValidationInvalid — ada field error
|
|
342
|
+
console.log(result.error);
|
|
343
|
+
}
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
## Contoh Penggunaan
|
|
347
|
+
|
|
348
|
+
Lihat folder [`examples/`](examples/) untuk contoh lengkap di berbagai framework dan runtime:
|
|
349
|
+
|
|
350
|
+
| Framework | File | Fitur |
|
|
351
|
+
|-----------|------|-------|
|
|
352
|
+
| **Node.js** | [`node.ts`](examples/node.ts) | Basic usage — validasi, parse, generate |
|
|
353
|
+
| **React** | [`react.tsx`](examples/react.tsx) | Form validasi KTP dengan feedback real-time |
|
|
354
|
+
| **Next.js** | [`nextjs.tsx`](examples/nextjs.tsx) | API route + server component |
|
|
355
|
+
| **Express** | [`express.ts`](examples/express.ts) | REST API endpoint validasi NIK |
|
|
356
|
+
| **Hono** | [`hono.ts`](examples/hono.ts) | Lightweight REST API validasi NIK |
|
|
357
|
+
| **Bun** | [`bun.ts`](examples/bun.ts) | Native Bun HTTP server |
|
|
358
|
+
| **Deno** | [`deno.ts`](examples/deno.ts) | Native Deno server |
|
|
359
|
+
|
|
360
|
+
```bash
|
|
361
|
+
# Jalankan contoh Node.js
|
|
362
|
+
npx tsx examples/node.ts
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
## Types
|
|
366
|
+
|
|
367
|
+
Semua type tersedia di `nik-id/types`:
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
import type {
|
|
371
|
+
NIKValid,
|
|
372
|
+
NIKInvalid,
|
|
373
|
+
NIKResult,
|
|
374
|
+
ValidationValid,
|
|
375
|
+
ValidationInvalid,
|
|
376
|
+
ValidationResult,
|
|
377
|
+
GenerateOptions,
|
|
378
|
+
} from 'nik-id/types';
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### NIKResult (parseNIK)
|
|
382
|
+
|
|
383
|
+
```typescript
|
|
384
|
+
// Hasil valid
|
|
385
|
+
interface NIKValid {
|
|
386
|
+
valid: true;
|
|
387
|
+
nik: string; // "3204076508850001"
|
|
388
|
+
provinceCode: string; // "32"
|
|
389
|
+
regencyCode: string; // "3204"
|
|
390
|
+
districtCode: string; // "320407"
|
|
391
|
+
birthDate: Date; // Date(1985-08-25)
|
|
392
|
+
gender: "M" | "F"; // "F"
|
|
393
|
+
sequenceNumber: string; // "0001"
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Hasil invalid
|
|
397
|
+
interface NIKInvalid {
|
|
398
|
+
valid: false;
|
|
399
|
+
error: string; // "NIK harus 16 digit"
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
type NIKResult = NIKValid | NIKInvalid;
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
### ValidationResult (validateNIK)
|
|
406
|
+
|
|
407
|
+
```typescript
|
|
408
|
+
interface ValidationValid {
|
|
409
|
+
valid: true;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
interface ValidationInvalid {
|
|
413
|
+
valid: false;
|
|
414
|
+
error: string;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
type ValidationResult = ValidationValid | ValidationInvalid;
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
### GenerateOptions (generateNIK)
|
|
421
|
+
|
|
422
|
+
```typescript
|
|
423
|
+
interface GenerateOptions {
|
|
424
|
+
provinceCode?: string; // "32" (2 digit, 11-97)
|
|
425
|
+
regencyCode?: string; // "3204" (4 digit, harus match provinsi)
|
|
426
|
+
districtCode?: string; // "320407" (6 digit, harus match kabupaten)
|
|
427
|
+
gender?: "M" | "F"; // Jenis kelamin
|
|
428
|
+
birthDate?: Date; // Tanggal lahir (default: random 1950-2005)
|
|
429
|
+
}
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
## Encoding Tanggal Lahir
|
|
433
|
+
|
|
434
|
+
NIK meng-encode tanggal lahir dalam 6 digit (posisi 7-12) dengan format DDMMYY:
|
|
435
|
+
|
|
436
|
+
### Gender dan DD
|
|
437
|
+
|
|
438
|
+
| Gender | Range DD | Contoh | Arti |
|
|
439
|
+
|--------|----------|--------|------|
|
|
440
|
+
| Laki-laki | 01-31 | DD=`15` | Lahir tanggal 15 |
|
|
441
|
+
| Perempuan | 41-71 | DD=`65` | Lahir tanggal 25 (65-40=25) |
|
|
442
|
+
| Invalid | 00, 32-40, 72-99 | -- | Tidak valid |
|
|
443
|
+
|
|
444
|
+
### Disambiguasi Tahun (YY)
|
|
445
|
+
|
|
446
|
+
Karena NIK hanya menyimpan 2 digit tahun, perlu disambiguasi:
|
|
447
|
+
|
|
448
|
+
| YY | Tahun sekarang (2 digit) | Hasil | Logika |
|
|
449
|
+
|----|--------------------------|-------|--------|
|
|
450
|
+
| 85 | 26 | 1985 | 85 > 26 → 1900 + 85 |
|
|
451
|
+
| 02 | 26 | 2002 | 02 ≤ 26 → 2000 + 02 |
|
|
452
|
+
| 26 | 26 | 2026 | 26 ≤ 26 → 2000 + 26 |
|
|
453
|
+
| 27 | 26 | 1927 | 27 > 26 → 1900 + 27 |
|
|
454
|
+
| 00 | 26 | 2000 | 00 ≤ 26 → 2000 + 00 |
|
|
455
|
+
| 99 | 26 | 1999 | 99 > 26 → 1900 + 99 |
|
|
456
|
+
|
|
457
|
+
## Kode Wilayah Kemendagri
|
|
458
|
+
|
|
459
|
+
NIK menggunakan kode wilayah format Kemendagri (bukan BPS):
|
|
460
|
+
|
|
461
|
+
| Level | Panjang | Contoh | Keterangan |
|
|
462
|
+
|-------|---------|--------|------------|
|
|
463
|
+
| Provinsi | 2 digit | `"32"` | Jawa Barat |
|
|
464
|
+
| Kabupaten/Kota | 4 digit | `"3204"` | Kab. Bandung |
|
|
465
|
+
| Kecamatan | 6 digit | `"320407"` | Nagreg |
|
|
466
|
+
|
|
467
|
+
Range kode provinsi yang valid: **11** (Aceh) sampai **97** (Papua Barat Daya).
|
|
468
|
+
|
|
469
|
+
> Package ini hanya memvalidasi **range** kode provinsi (11-97), tidak mengecek apakah kode spesifik benar-benar ada. Untuk lookup nama wilayah, gunakan [`kode-wilayah-id`](https://www.npmjs.com/package/kode-wilayah-id).
|
|
470
|
+
|
|
471
|
+
## Contributing
|
|
472
|
+
|
|
473
|
+
Kontribusi sangat diterima!
|
|
474
|
+
|
|
475
|
+
```bash
|
|
476
|
+
git clone https://github.com/sumitroajiprabowo/nik-id.git
|
|
477
|
+
cd nik-id
|
|
478
|
+
npm install
|
|
479
|
+
npm run lint # Lint check (Biome)
|
|
480
|
+
npm run format:check # Format check
|
|
481
|
+
npm run typecheck # TypeScript check
|
|
482
|
+
npm run test:coverage # Test dengan coverage (harus 100%)
|
|
483
|
+
npm run build # Build ESM + CJS
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
## Changelog
|
|
487
|
+
|
|
488
|
+
Lihat [CHANGELOG.md](CHANGELOG.md) untuk riwayat perubahan.
|
|
489
|
+
|
|
490
|
+
## Lisensi
|
|
491
|
+
|
|
492
|
+
[MIT](LICENSE) (c) [Sumitro Aji Prabowo](https://github.com/sumitroajiprabowo)
|
|
@@ -0,0 +1,95 @@
|
|
|
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/generate.ts
|
|
21
|
+
var generate_exports = {};
|
|
22
|
+
__export(generate_exports, {
|
|
23
|
+
generateNIK: () => generateNIK
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(generate_exports);
|
|
26
|
+
var MIN_PROVINCE_CODE = 11;
|
|
27
|
+
var MAX_PROVINCE_CODE = 97;
|
|
28
|
+
var MIN_BIRTH_YEAR = 1950;
|
|
29
|
+
var MAX_BIRTH_YEAR = 2005;
|
|
30
|
+
function randomInt(min, max) {
|
|
31
|
+
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
32
|
+
}
|
|
33
|
+
function padZero(num, length) {
|
|
34
|
+
return String(num).padStart(length, "0");
|
|
35
|
+
}
|
|
36
|
+
function getDaysInMonth(year, month) {
|
|
37
|
+
return new Date(year, month, 0).getDate();
|
|
38
|
+
}
|
|
39
|
+
function randomBirthDate() {
|
|
40
|
+
const year = randomInt(MIN_BIRTH_YEAR, MAX_BIRTH_YEAR);
|
|
41
|
+
const month = randomInt(1, 12);
|
|
42
|
+
const maxDay = getDaysInMonth(year, month);
|
|
43
|
+
const day = randomInt(1, maxDay);
|
|
44
|
+
return new Date(year, month - 1, day);
|
|
45
|
+
}
|
|
46
|
+
function generateNIK(options = {}) {
|
|
47
|
+
const { gender, birthDate } = options;
|
|
48
|
+
let provinceCode;
|
|
49
|
+
if (options.provinceCode !== void 0) {
|
|
50
|
+
const pc = Number.parseInt(options.provinceCode, 10);
|
|
51
|
+
if (Number.isNaN(pc) || pc < MIN_PROVINCE_CODE || pc > MAX_PROVINCE_CODE) {
|
|
52
|
+
throw new Error("Kode provinsi tidak valid (harus 11-97)");
|
|
53
|
+
}
|
|
54
|
+
provinceCode = padZero(pc, 2);
|
|
55
|
+
} else {
|
|
56
|
+
provinceCode = padZero(randomInt(MIN_PROVINCE_CODE, MAX_PROVINCE_CODE), 2);
|
|
57
|
+
}
|
|
58
|
+
let regencyCode;
|
|
59
|
+
if (options.regencyCode !== void 0) {
|
|
60
|
+
if (!options.regencyCode.startsWith(provinceCode)) {
|
|
61
|
+
throw new Error(
|
|
62
|
+
`Kode kabupaten/kota "${options.regencyCode}" tidak sesuai dengan kode provinsi "${provinceCode}"`
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
regencyCode = options.regencyCode;
|
|
66
|
+
} else {
|
|
67
|
+
regencyCode = provinceCode + padZero(randomInt(1, 99), 2);
|
|
68
|
+
}
|
|
69
|
+
let districtCode;
|
|
70
|
+
if (options.districtCode !== void 0) {
|
|
71
|
+
if (!options.districtCode.startsWith(regencyCode)) {
|
|
72
|
+
throw new Error(
|
|
73
|
+
`Kode kecamatan "${options.districtCode}" tidak sesuai dengan kode kabupaten/kota "${regencyCode}"`
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
districtCode = options.districtCode;
|
|
77
|
+
} else {
|
|
78
|
+
districtCode = regencyCode + padZero(randomInt(1, 99), 2);
|
|
79
|
+
}
|
|
80
|
+
const birth = birthDate ?? randomBirthDate();
|
|
81
|
+
const day = birth.getDate();
|
|
82
|
+
const month = birth.getMonth() + 1;
|
|
83
|
+
const year = birth.getFullYear();
|
|
84
|
+
const selectedGender = gender ?? (Math.random() < 0.5 ? "M" : "F");
|
|
85
|
+
const encodedDay = selectedGender === "F" ? day + 40 : day;
|
|
86
|
+
const ddStr = padZero(encodedDay, 2);
|
|
87
|
+
const mmStr = padZero(month, 2);
|
|
88
|
+
const yyStr = padZero(year % 100, 2);
|
|
89
|
+
const seq = padZero(randomInt(1, 9999), 4);
|
|
90
|
+
return districtCode + ddStr + mmStr + yyStr + seq;
|
|
91
|
+
}
|
|
92
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
93
|
+
0 && (module.exports = {
|
|
94
|
+
generateNIK
|
|
95
|
+
});
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { GenerateOptions } from './types.cjs';
|
|
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 };
|