@indodev/toolkit 0.5.0 → 0.7.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.
Files changed (65) hide show
  1. package/dist/email-validator/index.cjs +10 -0
  2. package/dist/email-validator/index.cjs.map +1 -1
  3. package/dist/email-validator/index.d.cts +1 -1
  4. package/dist/email-validator/index.d.ts +1 -1
  5. package/dist/email-validator/index.js +10 -1
  6. package/dist/email-validator/index.js.map +1 -1
  7. package/dist/{parse-BmmsNlJt.d.cts → errors--47zprcf.d.cts} +41 -7
  8. package/dist/{parse-BmmsNlJt.d.ts → errors--47zprcf.d.ts} +41 -7
  9. package/dist/{email-validator-R9L5unIw.d.cts → errors-DdutHLae.d.cts} +23 -1
  10. package/dist/{email-validator-R9L5unIw.d.ts → errors-DdutHLae.d.ts} +23 -1
  11. package/dist/index.cjs +317 -38
  12. package/dist/index.cjs.map +1 -1
  13. package/dist/index.d.cts +7 -7
  14. package/dist/index.d.ts +7 -7
  15. package/dist/index.js +310 -39
  16. package/dist/index.js.map +1 -1
  17. package/dist/{compare-Ku_8OhuU.d.cts → mask-CToJqmrd.d.cts} +79 -1
  18. package/dist/{compare-Ku_8OhuU.d.ts → mask-CToJqmrd.d.ts} +79 -1
  19. package/dist/nik/index.cjs +177 -24
  20. package/dist/nik/index.cjs.map +1 -1
  21. package/dist/nik/index.d.cts +24 -330
  22. package/dist/nik/index.d.ts +24 -330
  23. package/dist/nik/index.js +174 -25
  24. package/dist/nik/index.js.map +1 -1
  25. package/dist/npwp/index.cjs +10 -0
  26. package/dist/npwp/index.cjs.map +1 -1
  27. package/dist/npwp/index.d.cts +23 -1
  28. package/dist/npwp/index.d.ts +23 -1
  29. package/dist/npwp/index.js +10 -1
  30. package/dist/npwp/index.js.map +1 -1
  31. package/dist/parse-B3UzEHq3.d.cts +155 -0
  32. package/dist/parse-B3UzEHq3.d.ts +155 -0
  33. package/dist/parse-DlrgrRfY.d.cts +155 -0
  34. package/dist/parse-DlrgrRfY.d.ts +155 -0
  35. package/dist/phone/index.cjs +36 -12
  36. package/dist/phone/index.cjs.map +1 -1
  37. package/dist/phone/index.d.cts +1 -1
  38. package/dist/phone/index.d.ts +1 -1
  39. package/dist/phone/index.js +36 -13
  40. package/dist/phone/index.js.map +1 -1
  41. package/dist/plate/index.cjs +57 -0
  42. package/dist/plate/index.cjs.map +1 -1
  43. package/dist/plate/index.d.cts +1 -1
  44. package/dist/plate/index.d.ts +1 -1
  45. package/dist/plate/index.js +56 -1
  46. package/dist/plate/index.js.map +1 -1
  47. package/dist/text/index.cjs +31 -8
  48. package/dist/text/index.cjs.map +1 -1
  49. package/dist/text/index.d.cts +2 -63
  50. package/dist/text/index.d.ts +2 -63
  51. package/dist/text/index.js +31 -8
  52. package/dist/text/index.js.map +1 -1
  53. package/dist/utils-D4A4ro8M.d.cts +507 -0
  54. package/dist/utils-D4A4ro8M.d.ts +507 -0
  55. package/dist/vin/index.cjs +64 -0
  56. package/dist/vin/index.cjs.map +1 -1
  57. package/dist/vin/index.d.cts +1 -1
  58. package/dist/vin/index.d.ts +1 -1
  59. package/dist/vin/index.js +63 -1
  60. package/dist/vin/index.js.map +1 -1
  61. package/package.json +1 -1
  62. package/dist/types-i5e6R0AS.d.cts +0 -39
  63. package/dist/types-i5e6R0AS.d.ts +0 -39
  64. package/dist/utils-DDVlOusI.d.cts +0 -30
  65. package/dist/utils-DDVlOusI.d.ts +0 -30
@@ -0,0 +1,507 @@
1
+ /**
2
+ * Validates a NIK (Nomor Induk Kependudukan) format.
3
+ *
4
+ * A valid NIK must:
5
+ * - Be exactly 16 digits
6
+ * - Have a valid province code (positions 1-2)
7
+ * - Have a valid date (positions 7-12)
8
+ * - Not be in the future
9
+ * - Not be before 1900
10
+ *
11
+ * For female NIKs, the day is encoded as (actual day + 40).
12
+ * For example, a female born on the 15th would have day = 55.
13
+ *
14
+ * @param nik - The 16-digit NIK string to validate
15
+ * @returns `true` if the NIK is valid, `false` otherwise
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * validateNIK('3201234567890123'); // true - valid NIK
20
+ * validateNIK('1234'); // false - wrong length
21
+ * validateNIK('9912345678901234'); // false - invalid province
22
+ * ```
23
+ *
24
+ * @public
25
+ */
26
+ declare function validateNIK(nik: string): boolean;
27
+
28
+ /**
29
+ * Information extracted from a valid NIK.
30
+ *
31
+ * Contains parsed data including location codes, birth date, gender,
32
+ * and serial number from a 16-digit NIK string.
33
+ *
34
+ * @public
35
+ */
36
+ interface NIKInfo {
37
+ /**
38
+ * Province information extracted from positions 1-2 of the NIK.
39
+ *
40
+ * @example
41
+ * ```typescript
42
+ * { code: '32', name: 'Jawa Barat' }
43
+ * ```
44
+ */
45
+ province: {
46
+ /** Two-digit province code (e.g., '32') */
47
+ code: string;
48
+ /** Full province name (e.g., 'Jawa Barat') */
49
+ name: string;
50
+ };
51
+ /**
52
+ * Regency (Kabupaten/Kota) information extracted from positions 3-4 of the NIK.
53
+ *
54
+ * @example
55
+ * ```typescript
56
+ * { code: '01', name: 'Kab. Bogor' }
57
+ * ```
58
+ */
59
+ regency: {
60
+ /** Two-digit regency code (e.g., '01') */
61
+ code: string;
62
+ /** Full regency name (e.g., 'Kab. Bogor') */
63
+ name: string;
64
+ };
65
+ /**
66
+ * District (Kecamatan) information extracted from positions 5-6 of the NIK.
67
+ * May be `null` if district data is not available.
68
+ *
69
+ * @example
70
+ * ```typescript
71
+ * { code: '23', name: 'Ciawi' }
72
+ * ```
73
+ */
74
+ district: {
75
+ /** Two-digit district code (e.g., '23') */
76
+ code: string;
77
+ /** Full district name, or `null` if data unavailable */
78
+ name: string | null;
79
+ };
80
+ /**
81
+ * Birth date extracted from positions 7-12 of the NIK.
82
+ * For females, the day is encoded as (actual day + 40).
83
+ * Returns `null` if the date is invalid.
84
+ *
85
+ * @example
86
+ * ```typescript
87
+ * new Date(1989, 0, 31) // January 31, 1989
88
+ * ```
89
+ */
90
+ birthDate: Date | null;
91
+ /**
92
+ * Gender derived from the day encoding in the NIK.
93
+ * - 'male': day is 1-31
94
+ * - 'female': day is 41-71 (actual day + 40)
95
+ * - `null`: if unable to determine
96
+ */
97
+ gender: 'male' | 'female' | null;
98
+ /**
99
+ * Serial number from positions 13-16 of the NIK.
100
+ * Uniquely identifies individuals with the same location and birth date.
101
+ * Returns `null` if unable to extract.
102
+ *
103
+ * @example
104
+ * ```typescript
105
+ * '0123'
106
+ * ```
107
+ */
108
+ serialNumber: string | null;
109
+ /**
110
+ * Whether the NIK passed validation checks.
111
+ * If `false`, other fields may be `null` or contain partial data.
112
+ */
113
+ isValid: boolean;
114
+ }
115
+ /**
116
+ * Options for masking a NIK to protect privacy.
117
+ *
118
+ * Controls how many characters to show at the start and end,
119
+ * what character to use for masking, and optional separators.
120
+ *
121
+ * @example
122
+ * Default masking:
123
+ * ```typescript
124
+ * { visibleStart: 4, visibleEnd: 4, maskChar: '*' }
125
+ * // Result: '3201********0123'
126
+ *
127
+ * // With separator
128
+ * { visibleStart: 4, visibleEnd: 4, maskChar: '*', separator: '-' }
129
+ * // Result: '3201-****-****-0123'
130
+ * ```
131
+ *
132
+ * @public
133
+ */
134
+ interface MaskOptions {
135
+ /**
136
+ * Number of characters to show at the start.
137
+ *
138
+ * @defaultValue 4
139
+ */
140
+ visibleStart?: number;
141
+ /**
142
+ * Number of characters to show at the end.
143
+ *
144
+ * @defaultValue 4
145
+ */
146
+ visibleEnd?: number;
147
+ /**
148
+ * Character to use for masking hidden digits.
149
+ *
150
+ * @defaultValue '*'
151
+ */
152
+ maskChar?: string;
153
+ /**
154
+ * Optional separator to add between groups of digits.
155
+ * If provided, the NIK will be formatted with separators.
156
+ *
157
+ * @defaultValue undefined (no separator)
158
+ *
159
+ * @example
160
+ * ```typescript
161
+ * '-' // Results in format: '3201-****-****-0123'
162
+ * ' ' // Results in format: '3201 **** **** 0123'
163
+ * ```
164
+ */
165
+ separator?: string;
166
+ /**
167
+ * @deprecated Use `visibleStart` instead. Deprecated in v0.7.0.
168
+ */
169
+ start?: number;
170
+ /**
171
+ * @deprecated Use `visibleEnd` instead. Deprecated in v0.7.0.
172
+ */
173
+ end?: number;
174
+ /**
175
+ * @deprecated Use `maskChar` instead. Deprecated in v0.7.0.
176
+ */
177
+ char?: string;
178
+ }
179
+ /**
180
+ * Error thrown when an invalid NIK is provided to a function.
181
+ * Extends native Error with a `code` property for programmatic error handling.
182
+ *
183
+ * @example
184
+ * ```typescript
185
+ * try {
186
+ * requireNIK('invalid');
187
+ * } catch (error) {
188
+ * if (error instanceof InvalidNIKError) {
189
+ * console.log(error.code); // 'INVALID_NIK'
190
+ * }
191
+ * }
192
+ * ```
193
+ *
194
+ * @public
195
+ */
196
+ declare class InvalidNIKError extends Error {
197
+ /** Error code for programmatic identification */
198
+ readonly code: "INVALID_NIK";
199
+ constructor(message?: string);
200
+ }
201
+ /**
202
+ * Error codes for detailed NIK validation.
203
+ *
204
+ * @public
205
+ */
206
+ type NIKErrorCode = 'INVALID_FORMAT' | 'INVALID_PROVINCE' | 'INVALID_MONTH' | 'INVALID_DAY' | 'INVALID_DATE' | 'FUTURE_DATE';
207
+ /**
208
+ * A single validation error with code and message.
209
+ *
210
+ * @public
211
+ */
212
+ interface NIKValidationError {
213
+ /** Error code for programmatic handling */
214
+ code: NIKErrorCode;
215
+ /** Human-readable error message */
216
+ message: string;
217
+ }
218
+ /**
219
+ * Detailed validation result for NIK with structured error reporting.
220
+ *
221
+ * Use this for form validation where you need to show specific error
222
+ * messages per field.
223
+ *
224
+ * @example
225
+ * ```typescript
226
+ * const result = validateNIKDetailed('1234');
227
+ * if (!result.isValid) {
228
+ * result.errors.forEach(err => {
229
+ * console.log(err.code, err.message);
230
+ * });
231
+ * }
232
+ * ```
233
+ *
234
+ * @public
235
+ */
236
+ interface NIKValidationResult {
237
+ /** Whether the NIK is valid */
238
+ isValid: boolean;
239
+ /** List of validation errors (empty if valid) */
240
+ errors: NIKValidationError[];
241
+ /** Cleaned 16-digit NIK if valid, null otherwise */
242
+ nik: string | null;
243
+ }
244
+ /**
245
+ * Options for getAge function.
246
+ *
247
+ * @public
248
+ */
249
+ interface GetAgeOptions {
250
+ /** Date to calculate age from (default: current date) */
251
+ referenceDate?: Date;
252
+ /** Return formatted Indonesian string instead of object */
253
+ asString?: boolean;
254
+ }
255
+ /**
256
+ * Age result object with years, months, and days.
257
+ *
258
+ * @public
259
+ */
260
+ interface Age {
261
+ /** Full years */
262
+ years: number;
263
+ /** Remaining months after full years */
264
+ months: number;
265
+ /** Remaining days after full months */
266
+ days: number;
267
+ }
268
+
269
+ /**
270
+ * Parses a NIK and extracts all embedded information.
271
+ *
272
+ * Extracts province, regency, district codes, birth date, gender,
273
+ * and serial number from a 16-digit NIK string.
274
+ *
275
+ * @param nik - The 16-digit NIK string to parse
276
+ * @returns Parsed NIK information, or `null` if the NIK format is invalid
277
+ *
278
+ * @example
279
+ * Parse a valid male NIK:
280
+ * ```typescript
281
+ * const info = parseNIK('3201018901310123');
282
+ * console.log(info);
283
+ * // {
284
+ * // province: { code: '32', name: 'Jawa Barat' },
285
+ * // regency: { code: '01', name: 'Kab. Bogor' },
286
+ * // district: { code: '01', name: null },
287
+ * // birthDate: Date(1989, 0, 31), // Jan 31, 1989
288
+ * // gender: 'male',
289
+ * // serialNumber: '0123',
290
+ * // isValid: true
291
+ * // }
292
+ * ```
293
+ *
294
+ * @example
295
+ * Parse a female NIK (day + 40):
296
+ * ```typescript
297
+ * const info = parseNIK('3201019508550123');
298
+ * console.log(info.gender); // 'female'
299
+ * console.log(info.birthDate); // Date(1995, 7, 15) - Aug 15, 1995
300
+ * ```
301
+ *
302
+ * @example
303
+ * Invalid NIK returns null:
304
+ * ```typescript
305
+ * const info = parseNIK('invalid');
306
+ * console.log(info); // null
307
+ * ```
308
+ *
309
+ * @public
310
+ */
311
+ declare function parseNIK(nik: string): NIKInfo | null;
312
+
313
+ /**
314
+ * Formats a NIK with separators for better readability.
315
+ *
316
+ * Groups the NIK into logical segments: province, regency, district,
317
+ * year, month, day, and serial number.
318
+ *
319
+ * @param nik - The 16-digit NIK string to format
320
+ * @param separator - Character to use as separator
321
+ * @returns Formatted NIK string, or original string if invalid format
322
+ *
323
+ * @example
324
+ * Default separator (dash):
325
+ * ```typescript
326
+ * formatNIK('3201234567890123');
327
+ * // '32-01-23-45-67-89-0123'
328
+ * ```
329
+ *
330
+ * @example
331
+ * Custom separator:
332
+ * ```typescript
333
+ * formatNIK('3201234567890123', ' ');
334
+ * // '32 01 23 45 67 89 0123'
335
+ * ```
336
+ *
337
+ * @example
338
+ * Invalid NIK returns as-is:
339
+ * ```typescript
340
+ * formatNIK('1234');
341
+ * // '1234'
342
+ * ```
343
+ *
344
+ * @public
345
+ */
346
+ declare function formatNIK(nik: string, separator?: string): string;
347
+ /**
348
+ * Masks a NIK to protect privacy while keeping partial visibility.
349
+ *
350
+ * By default, shows the first 4 and last 4 digits, masking the middle 8.
351
+ * Optionally formats the masked NIK with separators.
352
+ *
353
+ * @param nik - The 16-digit NIK string to mask
354
+ * @param options - Masking configuration options
355
+ * @returns Masked NIK string, or original string if invalid format
356
+ *
357
+ * @example
358
+ * Default masking (first 4, last 4):
359
+ * ```typescript
360
+ * maskNIK('3201234567890123');
361
+ * // '3201********0123'
362
+ * ```
363
+ *
364
+ * @example
365
+ * Custom mask character:
366
+ * ```typescript
367
+ * maskNIK('3201234567890123', { maskChar: 'X' });
368
+ * // '3201XXXXXXXX0123'
369
+ * ```
370
+ *
371
+ * @example
372
+ * With separator:
373
+ * ```typescript
374
+ * maskNIK('3201234567890123', { separator: '-' });
375
+ * // '32-01-**-**-**-**-0123'
376
+ * ```
377
+ *
378
+ * @example
379
+ * Custom visibleStart and visibleEnd:
380
+ * ```typescript
381
+ * maskNIK('3201234567890123', { visibleStart: 6, visibleEnd: 4 });
382
+ * // '320123******0123'
383
+ * ```
384
+ *
385
+ * @public
386
+ */
387
+ declare function maskNIK(nik: string, options?: MaskOptions): string;
388
+
389
+ /**
390
+ * Calculates the age of a person based on their NIK.
391
+ *
392
+ * Returns detailed age breakdown with years, months, and days, or a
393
+ * formatted Indonesian string. This is consistent with `datetime.getAge()`.
394
+ *
395
+ * @param nik - The 16-digit NIK string
396
+ * @param options - Options object with `referenceDate` and `asString`
397
+ * @returns Age object `{ years, months, days }`, formatted string, or null if invalid
398
+ *
399
+ * @example
400
+ * ```typescript
401
+ * // Returns object by default
402
+ * getAge('3201018901310123');
403
+ * // { years: 35, months: 2, days: 6 } (as of 2026-04-06)
404
+ *
405
+ * // Returns formatted string
406
+ * getAge('3201018901310123', { asString: true });
407
+ * // '35 Tahun 2 Bulan 6 Hari'
408
+ *
409
+ * // Custom reference date
410
+ * getAge('3201018901310123', { referenceDate: new Date('2025-01-01') });
411
+ * // { years: 35, months: 11, days: 1 }
412
+ * ```
413
+ *
414
+ * @public
415
+ */
416
+ declare function getAge(nik: string, options?: GetAgeOptions): Age | string | null;
417
+ /**
418
+ * Compares two NIKs to check if they belong to the same person.
419
+ *
420
+ * Two NIKs are considered the same person if they have identical:
421
+ * - Province code (positions 1-2)
422
+ * - Regency code (positions 3-4)
423
+ * - District code (positions 5-6)
424
+ * - Birth date (year + month + day)
425
+ * - Gender (derived from day encoding)
426
+ * - Serial number (positions 13-16)
427
+ *
428
+ * @param nik1 - First NIK (accepts any format)
429
+ * @param nik2 - Second NIK (accepts any format)
430
+ * @returns True if both NIKs belong to the same person, false otherwise
431
+ *
432
+ * @example
433
+ * ```typescript
434
+ * // Same person, same format
435
+ * compareNIK('3201018901310123', '3201018901310123'); // true
436
+ *
437
+ * // Same person, different format
438
+ * compareNIK('3201018901310123', '3201-01-89-01-31-0123'); // true
439
+ *
440
+ * // Different serial number
441
+ * compareNIK('3201018901310123', '3201018901310124'); // false
442
+ *
443
+ * // Invalid NIK
444
+ * compareNIK('invalid', '3201018901310123'); // false
445
+ * ```
446
+ *
447
+ * @public
448
+ */
449
+ declare function compareNIK(nik1: string, nik2: string): boolean;
450
+ /**
451
+ * Checks if a person is an adult based on their NIK.
452
+ *
453
+ * By default, uses 17 years as the threshold (Indonesian KTP eligibility age).
454
+ * Indonesian law allows KTP at age 17, or upon marriage, or already married.
455
+ *
456
+ * @param nik - The 16-digit NIK string
457
+ * @param minAge - Minimum age threshold (default: 17)
458
+ * @returns True if the person is at least minAge years old, false otherwise
459
+ *
460
+ * @example
461
+ * ```typescript
462
+ * // Born 1995-01-01, reference 2026-04-06 (age 31)
463
+ * isAdult('3201950101950123'); // true (31 >= 17)
464
+ * isAdult('3201950101950123', 21); // true (31 >= 21)
465
+ *
466
+ * // Born 2010-01-01, reference 2026-04-06 (age 16)
467
+ * isAdult('3210010101950123'); // false (16 < 17)
468
+ * isAdult('3210010101950123', 16); // true (16 >= 16)
469
+ *
470
+ * // Invalid NIK
471
+ * isAdult('invalid'); // false
472
+ * ```
473
+ *
474
+ * @public
475
+ */
476
+ declare function isAdult(nik: string, minAge?: number): boolean;
477
+ /**
478
+ * Formats the birth date from a NIK into a human-readable string.
479
+ *
480
+ * @param nik - The 16-digit NIK string
481
+ * @param locale - The locale to use for formatting (default: 'id-ID')
482
+ * @returns Formatted birth date string, or null if invalid
483
+ *
484
+ * @example
485
+ * ```typescript
486
+ * formatBirthDate('3201018901310123'); // '31 Januari 1989'
487
+ * ```
488
+ */
489
+ declare function formatBirthDate(nik: string): string | null;
490
+ /**
491
+ * Checks if a NIK matches a specific gender.
492
+ *
493
+ * @param nik - The 16-digit NIK string
494
+ * @param gender - The gender to check ('male' | 'female')
495
+ * @returns True if the NIK matches the gender, false otherwise
496
+ */
497
+ declare function isValidForGender(nik: string, gender: 'male' | 'female'): boolean;
498
+ /**
499
+ * Checks if a NIK matches a specific birth date.
500
+ *
501
+ * @param nik - The 16-digit NIK string
502
+ * @param birthDate - The birth date to check
503
+ * @returns True if the NIK matches the birth date, false otherwise
504
+ */
505
+ declare function isValidForBirthDate(nik: string, birthDate: Date): boolean;
506
+
507
+ export { type Age as A, type GetAgeOptions as G, InvalidNIKError as I, type MaskOptions as M, type NIKInfo as N, formatBirthDate as a, isValidForBirthDate as b, type NIKValidationResult as c, compareNIK as d, isAdult as e, formatNIK as f, getAge as g, type NIKValidationError as h, isValidForGender as i, type NIKErrorCode as j, maskNIK as m, parseNIK as p, validateNIK as v };
@@ -71,7 +71,70 @@ function validateVIN(vin) {
71
71
  return actualCheckDigit === expectedCheckDigit;
72
72
  }
73
73
 
74
+ // src/vin/errors.ts
75
+ var InvalidVINError = class extends Error {
76
+ constructor(message = "Invalid VIN provided") {
77
+ super(message);
78
+ this.code = "INVALID_VIN";
79
+ this.name = "InvalidVINError";
80
+ }
81
+ };
82
+
83
+ // src/vin/parse.ts
84
+ function parseVIN(vin) {
85
+ if (!vin || typeof vin !== "string") {
86
+ return null;
87
+ }
88
+ const normalizedVIN = vin.toUpperCase();
89
+ if (normalizedVIN.length !== VIN_LENGTH) {
90
+ return null;
91
+ }
92
+ for (const char of EXCLUDED_VIN_CHARS) {
93
+ if (normalizedVIN.includes(char)) {
94
+ return null;
95
+ }
96
+ }
97
+ for (const char of normalizedVIN) {
98
+ if (VIN_CHAR_VALUES[char] === void 0) {
99
+ return null;
100
+ }
101
+ }
102
+ const isValid = validateCheckDigit(normalizedVIN);
103
+ const wmi = normalizedVIN.substring(0, 3);
104
+ const vds = normalizedVIN.substring(3, 9);
105
+ const checkDigit = normalizedVIN[VIN_CHECK_DIGIT_INDEX];
106
+ const modelYearCode = normalizedVIN[9];
107
+ const plantCode = normalizedVIN[10];
108
+ const serialNumber = normalizedVIN.substring(11, 17);
109
+ return {
110
+ wmi,
111
+ vds,
112
+ checkDigit,
113
+ modelYearCode,
114
+ plantCode,
115
+ serialNumber,
116
+ isValid
117
+ };
118
+ }
119
+ function validateCheckDigit(vin) {
120
+ let sum = 0;
121
+ for (let i = 0; i < VIN_LENGTH; i++) {
122
+ const char = vin[i];
123
+ const weight = VIN_WEIGHTS[i];
124
+ const val = VIN_CHAR_VALUES[char];
125
+ if (val === void 0) {
126
+ return false;
127
+ }
128
+ sum += val * weight;
129
+ }
130
+ const checkDigitValue = sum % VIN_MODULUS;
131
+ const expectedCheckDigit = checkDigitValue === 10 ? VIN_CHECK_DIGIT_X : checkDigitValue.toString();
132
+ const actualCheckDigit = vin[VIN_CHECK_DIGIT_INDEX];
133
+ return actualCheckDigit === expectedCheckDigit;
134
+ }
135
+
74
136
  exports.EXCLUDED_VIN_CHARS = EXCLUDED_VIN_CHARS;
137
+ exports.InvalidVINError = InvalidVINError;
75
138
  exports.VIN_CHAR_VALUES = VIN_CHAR_VALUES;
76
139
  exports.VIN_CHECK_DIGIT_INDEX = VIN_CHECK_DIGIT_INDEX;
77
140
  exports.VIN_CHECK_DIGIT_X = VIN_CHECK_DIGIT_X;
@@ -79,6 +142,7 @@ exports.VIN_LENGTH = VIN_LENGTH;
79
142
  exports.VIN_MODULUS = VIN_MODULUS;
80
143
  exports.VIN_VERSION = VIN_VERSION;
81
144
  exports.VIN_WEIGHTS = VIN_WEIGHTS;
145
+ exports.parseVIN = parseVIN;
82
146
  exports.validateVIN = validateVIN;
83
147
  //# sourceMappingURL=index.cjs.map
84
148
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/vin/constants.ts","../../src/vin/validate.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * The standard length of a VIN (ISO 3779).\n */\nexport const VIN_LENGTH = 17;\n\n/**\n * The 0-based index of the check digit in the VIN (Position 9).\n */\nexport const VIN_CHECK_DIGIT_INDEX = 8;\n\n/**\n * The modulus used in the VIN checksum calculation.\n */\nexport const VIN_MODULUS = 11;\n\n/**\n * The character representing a checksum value of 10.\n */\nexport const VIN_CHECK_DIGIT_X = 'X';\n\n/**\n * Weights used in the VIN checksum calculation.\n * Each position (1-17) is multiplied by its corresponding weight.\n * Position 9 is the check digit itself (weight 0).\n */\nexport const VIN_WEIGHTS = [8, 7, 6, 5, 4, 3, 2, 10, 0, 9, 8, 7, 6, 5, 4, 3, 2];\n\n/**\n * Values assigned to each valid character for VIN calculation.\n * Invalid characters: I, O, Q are not allowed in VIN.\n */\nexport const VIN_CHAR_VALUES: Record<string, number> = {\n '0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9,\n 'A': 1, 'B': 2, 'C': 3, 'D': 4, 'E': 5, 'F': 6, 'G': 7, 'H': 8,\n 'J': 1, 'K': 2, 'L': 3, 'M': 4, 'N': 5, 'P': 7, 'R': 9,\n 'S': 2, 'T': 3, 'U': 4, 'V': 5, 'W': 6, 'X': 7, 'Y': 8, 'Z': 9,\n};\n\n/**\n * Characters excluded from VIN (I, O, Q).\n */\nexport const EXCLUDED_VIN_CHARS = ['I', 'O', 'Q'];\n\n/**\n * Version of the VIN utility module.\n */\nexport const VIN_VERSION = '1.0.0';\n\n","import {\n VIN_WEIGHTS,\n VIN_CHAR_VALUES,\n EXCLUDED_VIN_CHARS,\n VIN_LENGTH,\n VIN_CHECK_DIGIT_INDEX,\n VIN_MODULUS,\n VIN_CHECK_DIGIT_X,\n} from './constants';\n\n/**\n * Validates a Vehicle Identification Number (VIN) based on ISO 3779.\n *\n * Checks for:\n * - Exactly 17 characters length.\n * - Exclusion of characters I, O, and Q.\n * - Checksum validation using the check digit at position 9.\n *\n * @param vin - The VIN string to validate\n * @returns boolean indicating if the VIN is valid\n *\n * @example\n * ```typescript\n * import { validateVIN } from '@indodev/toolkit/vin';\n *\n * validateVIN('1HBHA82L7ZB000001'); // true\n * validateVIN('1HBHA82I7ZB000001'); // false (contains 'I')\n * ```\n */\nexport function validateVIN(vin: string): boolean {\n if (!vin || vin.length !== VIN_LENGTH) {\n return false;\n }\n\n const normalizedVIN = vin.toUpperCase();\n\n // 1. Check for excluded characters (I, O, Q)\n for (const char of EXCLUDED_VIN_CHARS) {\n if (normalizedVIN.includes(char)) {\n return false;\n }\n }\n\n // 2. Checksum validation (Position 9)\n let sum = 0;\n for (let i = 0; i < VIN_LENGTH; i++) {\n const char = normalizedVIN[i];\n const weight = VIN_WEIGHTS[i];\n const val = VIN_CHAR_VALUES[char];\n\n if (val === undefined) {\n return false; // Invalid character found\n }\n\n sum += val * weight;\n }\n\n const checkDigitValue = sum % VIN_MODULUS;\n const expectedCheckDigit =\n checkDigitValue === 10 ? VIN_CHECK_DIGIT_X : checkDigitValue.toString();\n const actualCheckDigit = normalizedVIN[VIN_CHECK_DIGIT_INDEX];\n\n return actualCheckDigit === expectedCheckDigit;\n}\n"]}
1
+ {"version":3,"sources":["../../src/vin/constants.ts","../../src/vin/validate.ts","../../src/vin/errors.ts","../../src/vin/parse.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * The standard length of a VIN (ISO 3779).\n */\nexport const VIN_LENGTH = 17;\n\n/**\n * The 0-based index of the check digit in the VIN (Position 9).\n */\nexport const VIN_CHECK_DIGIT_INDEX = 8;\n\n/**\n * The modulus used in the VIN checksum calculation.\n */\nexport const VIN_MODULUS = 11;\n\n/**\n * The character representing a checksum value of 10.\n */\nexport const VIN_CHECK_DIGIT_X = 'X';\n\n/**\n * Weights used in the VIN checksum calculation.\n * Each position (1-17) is multiplied by its corresponding weight.\n * Position 9 is the check digit itself (weight 0).\n */\nexport const VIN_WEIGHTS = [8, 7, 6, 5, 4, 3, 2, 10, 0, 9, 8, 7, 6, 5, 4, 3, 2];\n\n/**\n * Values assigned to each valid character for VIN calculation.\n * Invalid characters: I, O, Q are not allowed in VIN.\n */\nexport const VIN_CHAR_VALUES: Record<string, number> = {\n '0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9,\n 'A': 1, 'B': 2, 'C': 3, 'D': 4, 'E': 5, 'F': 6, 'G': 7, 'H': 8,\n 'J': 1, 'K': 2, 'L': 3, 'M': 4, 'N': 5, 'P': 7, 'R': 9,\n 'S': 2, 'T': 3, 'U': 4, 'V': 5, 'W': 6, 'X': 7, 'Y': 8, 'Z': 9,\n};\n\n/**\n * Characters excluded from VIN (I, O, Q).\n */\nexport const EXCLUDED_VIN_CHARS = ['I', 'O', 'Q'];\n\n/**\n * Version of the VIN utility module.\n */\nexport const VIN_VERSION = '1.0.0';\n\n","import {\n VIN_WEIGHTS,\n VIN_CHAR_VALUES,\n EXCLUDED_VIN_CHARS,\n VIN_LENGTH,\n VIN_CHECK_DIGIT_INDEX,\n VIN_MODULUS,\n VIN_CHECK_DIGIT_X,\n} from './constants';\n\n/**\n * Validates a Vehicle Identification Number (VIN) based on ISO 3779.\n *\n * Checks for:\n * - Exactly 17 characters length.\n * - Exclusion of characters I, O, and Q.\n * - Checksum validation using the check digit at position 9.\n *\n * @param vin - The VIN string to validate\n * @returns boolean indicating if the VIN is valid\n *\n * @example\n * ```typescript\n * import { validateVIN } from '@indodev/toolkit/vin';\n *\n * validateVIN('1HBHA82L7ZB000001'); // true\n * validateVIN('1HBHA82I7ZB000001'); // false (contains 'I')\n * ```\n */\nexport function validateVIN(vin: string): boolean {\n if (!vin || vin.length !== VIN_LENGTH) {\n return false;\n }\n\n const normalizedVIN = vin.toUpperCase();\n\n // 1. Check for excluded characters (I, O, Q)\n for (const char of EXCLUDED_VIN_CHARS) {\n if (normalizedVIN.includes(char)) {\n return false;\n }\n }\n\n // 2. Checksum validation (Position 9)\n let sum = 0;\n for (let i = 0; i < VIN_LENGTH; i++) {\n const char = normalizedVIN[i];\n const weight = VIN_WEIGHTS[i];\n const val = VIN_CHAR_VALUES[char];\n\n if (val === undefined) {\n return false; // Invalid character found\n }\n\n sum += val * weight;\n }\n\n const checkDigitValue = sum % VIN_MODULUS;\n const expectedCheckDigit =\n checkDigitValue === 10 ? VIN_CHECK_DIGIT_X : checkDigitValue.toString();\n const actualCheckDigit = normalizedVIN[VIN_CHECK_DIGIT_INDEX];\n\n return actualCheckDigit === expectedCheckDigit;\n}\n","/**\n * Error thrown when an invalid VIN 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 * requireVIN('invalid');\n * } catch (error) {\n * if (error instanceof InvalidVINError) {\n * console.log(error.code); // 'INVALID_VIN'\n * }\n * }\n * ```\n *\n * @public\n */\nexport class InvalidVINError extends Error {\n readonly code = 'INVALID_VIN' as const;\n\n constructor(message: string = 'Invalid VIN provided') {\n super(message);\n this.name = 'InvalidVINError';\n }\n}\n","import {\n VIN_LENGTH,\n VIN_CHAR_VALUES,\n EXCLUDED_VIN_CHARS,\n VIN_WEIGHTS,\n VIN_CHECK_DIGIT_INDEX,\n VIN_MODULUS,\n VIN_CHECK_DIGIT_X,\n} from './constants';\nimport type { VINInfo } from './types';\n\n/**\n * Parses a Vehicle Identification Number (VIN) into its component parts.\n *\n * Extracts the WMI (World Manufacturer Identifier), VDS (Vehicle Descriptor Section),\n * check digit, model year code, plant code, and serial number from a VIN.\n *\n * @param vin - The VIN string to parse\n * @returns VINInfo object with extracted components, or null if invalid\n *\n * @example\n * Valid VIN:\n * ```typescript\n * parseVIN('1HBHA82L7ZB000001');\n * // {\n * // wmi: '1HB',\n * // vds: 'HA82L7',\n * // checkDigit: '7',\n * // modelYearCode: 'Z',\n * // plantCode: 'B',\n * // serialNumber: '0000001',\n * // isValid: true\n * // }\n * ```\n *\n * @example\n * Invalid VIN returns null:\n * ```typescript\n * parseVIN('invalid');\n * // null\n * ```\n *\n * @public\n */\nexport function parseVIN(vin: string): VINInfo | null {\n if (!vin || typeof vin !== 'string') {\n return null;\n }\n\n const normalizedVIN = vin.toUpperCase();\n\n if (normalizedVIN.length !== VIN_LENGTH) {\n return null;\n }\n\n for (const char of EXCLUDED_VIN_CHARS) {\n if (normalizedVIN.includes(char)) {\n return null;\n }\n }\n\n for (const char of normalizedVIN) {\n if (VIN_CHAR_VALUES[char] === undefined) {\n return null;\n }\n }\n\n const isValid = validateCheckDigit(normalizedVIN);\n\n const wmi = normalizedVIN.substring(0, 3);\n const vds = normalizedVIN.substring(3, 9);\n const checkDigit = normalizedVIN[VIN_CHECK_DIGIT_INDEX];\n const modelYearCode = normalizedVIN[9];\n const plantCode = normalizedVIN[10];\n const serialNumber = normalizedVIN.substring(11, 17);\n\n return {\n wmi,\n vds,\n checkDigit,\n modelYearCode,\n plantCode,\n serialNumber,\n isValid,\n };\n}\n\n/**\n * Validates the check digit of a VIN.\n *\n * @internal\n */\nfunction validateCheckDigit(vin: string): boolean {\n let sum = 0;\n for (let i = 0; i < VIN_LENGTH; i++) {\n const char = vin[i];\n const weight = VIN_WEIGHTS[i];\n const val = VIN_CHAR_VALUES[char];\n\n if (val === undefined) {\n return false;\n }\n\n sum += val * weight;\n }\n\n const checkDigitValue = sum % VIN_MODULUS;\n const expectedCheckDigit =\n checkDigitValue === 10 ? VIN_CHECK_DIGIT_X : checkDigitValue.toString();\n const actualCheckDigit = vin[VIN_CHECK_DIGIT_INDEX];\n\n return actualCheckDigit === expectedCheckDigit;\n}\n"]}
@@ -1,4 +1,4 @@
1
- export { V as VINOptions, a as VINValidationResult, v as validateVIN } from '../types-i5e6R0AS.cjs';
1
+ export { I as InvalidVINError, b as VINInfo, V as VINOptions, a as VINValidationResult, p as parseVIN, v as validateVIN } from '../parse-DlrgrRfY.cjs';
2
2
 
3
3
  /**
4
4
  * The standard length of a VIN (ISO 3779).
@@ -1,4 +1,4 @@
1
- export { V as VINOptions, a as VINValidationResult, v as validateVIN } from '../types-i5e6R0AS.js';
1
+ export { I as InvalidVINError, b as VINInfo, V as VINOptions, a as VINValidationResult, p as parseVIN, v as validateVIN } from '../parse-DlrgrRfY.js';
2
2
 
3
3
  /**
4
4
  * The standard length of a VIN (ISO 3779).