aamva-decoder 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/README.md +254 -0
- package/dist/barcode.d.ts +30 -0
- package/dist/barcode.js +106 -0
- package/dist/dates.d.ts +6 -0
- package/dist/dates.js +55 -0
- package/dist/eyeColor.d.ts +7 -0
- package/dist/eyeColor.js +32 -0
- package/dist/hairColor.d.ts +6 -0
- package/dist/hairColor.js +23 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +32 -0
- package/dist/issuingAuthority.d.ts +8 -0
- package/dist/issuingAuthority.js +85 -0
- package/dist/license.d.ts +89 -0
- package/dist/license.js +263 -0
- package/dist/raceEthnicity.d.ts +6 -0
- package/dist/raceEthnicity.js +20 -0
- package/dist/weightRange.d.ts +8 -0
- package/dist/weightRange.js +23 -0
- package/package.json +27 -0
package/README.md
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
# aamva-decoder
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/aamva-decoder)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://www.typescriptlang.org/)
|
|
6
|
+
[](https://nodejs.org/)
|
|
7
|
+
|
|
8
|
+
Parse PDF-417 barcode data from US and Canadian driver's licenses. Supports AAMVA versions 1 through 11 (2025 standard).
|
|
9
|
+
|
|
10
|
+
## Usage
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import { parseLicense } from "aamva-decoder";
|
|
14
|
+
|
|
15
|
+
const barcodeData: string = /* scan PDF-417 barcode */;
|
|
16
|
+
const license = parseLicense(barcodeData);
|
|
17
|
+
|
|
18
|
+
console.log(license.firstName); // "JOHN"
|
|
19
|
+
console.log(license.lastName); // "PUBLIC"
|
|
20
|
+
console.log(license.dateOfBirth); // Date object
|
|
21
|
+
console.log(license.state); // "CA"
|
|
22
|
+
console.log(license.aamvaVersion); // 11
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
If you already have a parsed `BarcodeFile` (from `parseBarcodeString`), you can skip re-parsing:
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { parseBarcodeString, parseLicenseFromBarcode } from "aamva-decoder";
|
|
29
|
+
|
|
30
|
+
const barcode = parseBarcodeString(barcodeData);
|
|
31
|
+
const license = parseLicenseFromBarcode(barcode);
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Raw Element Access
|
|
35
|
+
|
|
36
|
+
Every `License` object includes a `raw` field containing all element key-value pairs from the barcode. This is useful for accessing jurisdiction-specific or future element IDs that aren't mapped to named fields:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
const license = parseLicense(barcodeData);
|
|
40
|
+
console.log(license.raw["ZVA"]); // jurisdiction-specific element
|
|
41
|
+
console.log(license.raw["DCA"]); // raw vehicle class code
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Installation
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npm install aamva-decoder
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## AAMVA Standard
|
|
51
|
+
|
|
52
|
+
### Supported Fields
|
|
53
|
+
|
|
54
|
+
| Name | Description | Element ID | Type | `License` Attribute |
|
|
55
|
+
|:-----|:------------|:----------:|:-----|:--------------------|
|
|
56
|
+
| First Name | Customer first name | DAC | `string` | `firstName` |
|
|
57
|
+
| Last Name | Customer last name | DCS | `string` | `lastName` |
|
|
58
|
+
| Middle Name | Customer middle name | DAD | `string` | `middleName` |
|
|
59
|
+
| Name Suffix | Name suffix (JR, SR, etc.) | DCU | `string` | `nameSuffix` |
|
|
60
|
+
| Name Prefix | Name prefix (DR, etc.) | DAF | `string` | `namePrefix` |
|
|
61
|
+
| First Name (v2-3) | First name element used in v2-3 | DCT | `string` | `firstNameV2` |
|
|
62
|
+
| Date of Birth | Customer date of birth | DBB | `Date` | `dateOfBirth` |
|
|
63
|
+
| Expiration Date | Document expiration date | DBA | `Date` | `expirationDate` |
|
|
64
|
+
| Issue Date | Document issue date | DBD | `Date` | `issueDate` |
|
|
65
|
+
| Sex | Customer sex | DBC | `string` | `sex` |
|
|
66
|
+
| Eye Color | Customer eye color | DAY | `string` | `eyeColor` |
|
|
67
|
+
| Hair Color | Customer hair color | DAZ | `string` | `hairColor` |
|
|
68
|
+
| Height | Customer height | DAU | `string` | `height` |
|
|
69
|
+
| Height (cm) | Height in centimeters | DAV | `string` | `heightCm` |
|
|
70
|
+
| Weight | Customer weight (lbs) | DAW | `string` | `weight` |
|
|
71
|
+
| Weight (kg) | Weight in kilograms | DAX | `string` | `weightKg` |
|
|
72
|
+
| Weight Range | Weight range code (0-9) | DCE | `string` | `weightRange` |
|
|
73
|
+
| Street Address | Customer street address | DAG | `string` | `streetAddress` |
|
|
74
|
+
| Street Address 2 | Street address line 2 | DAH | `string` | `streetAddress2` |
|
|
75
|
+
| City | Customer city | DAI | `string` | `city` |
|
|
76
|
+
| State | Customer state | DAJ | `string` | `state` |
|
|
77
|
+
| Postal Code | Customer postal code | DAK | `string` | `postalCode` |
|
|
78
|
+
| Country | Issuing country | DCG | `string` | `country` |
|
|
79
|
+
| Document Number | Unique customer ID number | DAQ/DBJ | `string` | `documentNumber` |
|
|
80
|
+
| Document Discriminator | Unique document ID number | DCF | `string` | `documentDiscriminator` |
|
|
81
|
+
| Vehicle Class | Jurisdiction-specific vehicle class | DCA/DAR | `string` | `vehicleClass` |
|
|
82
|
+
| Restriction Codes | Jurisdiction-specific restriction codes | DCB/DAS | `string` | `restrictionCodes` |
|
|
83
|
+
| Endorsement Codes | Jurisdiction-specific endorsement codes | DCD/DAT | `string` | `endorsementCodes` |
|
|
84
|
+
| Federal Commercial Vehicle Codes | Federal commercial vehicle codes | DCH | `string` | `federalCommercialVehicleCodes` |
|
|
85
|
+
| Standard Vehicle Classification | Standard vehicle classification | DCM | `string` | `standardVehicleClassification` |
|
|
86
|
+
| Standard Endorsement Code | Standard endorsement code | DCN | `string` | `standardEndorsementCode` |
|
|
87
|
+
| Standard Restriction Code | Standard restriction code | DCO | `string` | `standardRestrictionCode` |
|
|
88
|
+
| Vehicle Class Description | Text description of vehicle class | DCP | `string` | `vehicleClassDescription` |
|
|
89
|
+
| Endorsement Code Description | Text description of endorsements | DCQ | `string` | `endorsementCodeDescription` |
|
|
90
|
+
| Restriction Code Description | Text description of restrictions | DCR | `string` | `restrictionCodeDescription` |
|
|
91
|
+
| Compliance Type | REAL ID compliance ("F"=compliant, "N"=non-compliant) | DDA | `string` | `complianceType` |
|
|
92
|
+
| Card Revision Date | Most recent card revision date | DDB | `Date` | `cardRevisionDate` |
|
|
93
|
+
| Limited Duration Document | Is this a limited duration document? | DDD | `boolean` | `limitedDurationDocument` |
|
|
94
|
+
| Organ Donor | Is the cardholder an organ donor? | DDK/DBH | `boolean` | `organDonor` |
|
|
95
|
+
| Organ Donor (legacy) | Organ donor indicator (older format, pre-DDK) | DBH | `string` | `organDonorLegacy` |
|
|
96
|
+
| Veteran | Is the cardholder a veteran? | DDL | `boolean` | `veteran` |
|
|
97
|
+
| First Name Truncation | Was first name truncated? ("T"/"N") | DDF | `string` | `firstNameTruncation` |
|
|
98
|
+
| Middle Name Truncation | Was middle name truncated? ("T"/"N") | DDG | `string` | `middleNameTruncation` |
|
|
99
|
+
| Last Name Truncation | Was last name truncated? ("T"/"N") | DDE | `string` | `lastNameTruncation` |
|
|
100
|
+
| Place of Birth | Country/municipality/state of birth | DCI | `string` | `placeOfBirth` |
|
|
101
|
+
| Audit Information | Identifies when, where, and by whom the card was made | DCJ | `string` | `auditInformation` |
|
|
102
|
+
| Inventory Control Number | Identifies raw materials used in card production | DCK | `string` | `inventoryControlNumber` |
|
|
103
|
+
| Last Name Alias | Other last name by which cardholder is known | DBN/DBO | `string` | `lastNameAlias` |
|
|
104
|
+
| First Name Alias | Other first name by which cardholder is known | DBG/DBP | `string` | `firstNameAlias` |
|
|
105
|
+
| Suffix Alias | Other suffix by which cardholder is known | DBS | `string` | `suffixAlias` |
|
|
106
|
+
| Race/Ethnicity | Customer race/ethnicity (v3-v10, removed in v11) | DCL | `string` | `raceEthnicity` |
|
|
107
|
+
| HAZMAT Endorsement Exp. | HAZMAT endorsement expiration date (v3-v10, removed in v11) | DDC | `Date` | `hazmatEndorsementExpiration` |
|
|
108
|
+
| CDL Indicator | Is this a commercial driver's license? (v11+) | DDM | `boolean` | `cdlIndicator` |
|
|
109
|
+
| Non-Domiciled Indicator | Is this a non-domiciled license? (v11+) | DDN | `boolean` | `nonDomiciledIndicator` |
|
|
110
|
+
| Enhanced Document Indicator | Is this an enhanced document? (v11+) | DDO | `boolean` | `enhancedDocumentIndicator` |
|
|
111
|
+
| Permit Indicator | Is this a permit? (v11+) | DDP | `boolean` | `permitIndicator` |
|
|
112
|
+
| Issue Timestamp | Date and time of card issuance | DBE | `string` | `issueTimestamp` |
|
|
113
|
+
| Number of Duplicates | Number of duplicate cards issued | DBF | `string` | `numberOfDuplicates` |
|
|
114
|
+
| Non-Resident Indicator | Non-resident indicator | DBI | `string` | `nonResidentIndicator` |
|
|
115
|
+
| Unique Customer ID | Unique customer identifier (v1, maps to DAQ) | DBJ | `string` | `uniqueCustomerId` |
|
|
116
|
+
| Social Security Number | Social security number (deprecated) | DBK | `string` | `socialSecurityNumber` |
|
|
117
|
+
| AKA Date of Birth | AKA date of birth | DBL | `Date` | `akaDateOfBirth` |
|
|
118
|
+
| AKA Social Security Number | AKA social security number (deprecated) | DBM | `string` | `akaSocialSecurityNumber` |
|
|
119
|
+
| Under 18 Until | Date cardholder turns 18 | DDH | `Date` | `under18Until` |
|
|
120
|
+
| Under 19 Until | Date cardholder turns 19 | DDI | `Date` | `under19Until` |
|
|
121
|
+
| Under 21 Until | Date cardholder turns 21 | DDJ | `Date` | `under21Until` |
|
|
122
|
+
|
|
123
|
+
#### V1 Legacy Fields
|
|
124
|
+
|
|
125
|
+
These fields use v1-specific element IDs. Their values are also normalized into the modern field names above as fallbacks.
|
|
126
|
+
|
|
127
|
+
| Name | Description | Element ID | `License` Attribute |
|
|
128
|
+
|:-----|:------------|:----------:|:--------------------|
|
|
129
|
+
| Last Name (v1) | Last name (v1 element ID) | DAB | `lastNameV1` |
|
|
130
|
+
| Name Suffix (v1) | Name suffix (v1 element ID) | DAE | `nameSuffixV1` |
|
|
131
|
+
| Classification Code (v1) | Vehicle classification (v1 element ID) | DAR | `classificationCodeV1` |
|
|
132
|
+
| Restriction Code (v1) | Restriction code (v1 element ID) | DAS | `restrictionCodeV1` |
|
|
133
|
+
| Endorsement Code (v1) | Endorsement code (v1 element ID) | DAT | `endorsementCodeV1` |
|
|
134
|
+
| Residence Street Address | Residence street address (v1) | DAL | `residenceStreetAddress` |
|
|
135
|
+
| Residence Street Address 2 | Residence street address line 2 (v1) | DAM | `residenceStreetAddress2` |
|
|
136
|
+
| Residence City | Residence city (v1) | DAN | `residenceCity` |
|
|
137
|
+
| Residence Jurisdiction Code | Residence state/province (v1) | DAO | `residenceJurisdictionCode` |
|
|
138
|
+
| Residence Postal Code | Residence postal code (v1) | DAP | `residencePostalCode` |
|
|
139
|
+
| AKA Last Name (v1) | AKA last name (v1 element ID) | DBO | `akaLastNameV1` |
|
|
140
|
+
| AKA First Name (v1) | AKA first name (v1 element ID) | DBP | `akaFirstNameV1` |
|
|
141
|
+
| AKA Middle Name | AKA middle name | DBQ | `akaMiddleName` |
|
|
142
|
+
| AKA Suffix (v1) | AKA suffix (v1 element ID) | DBR | `akaSuffixV1` |
|
|
143
|
+
|
|
144
|
+
#### V1 Fallback Behavior
|
|
145
|
+
|
|
146
|
+
When parsing v1 barcodes, the library normalizes v1 element IDs into their modern equivalents:
|
|
147
|
+
|
|
148
|
+
- `firstName` = DAC, then DCT, then parsed from DAA
|
|
149
|
+
- `lastName` = DCS, then DAB, then parsed from DAA
|
|
150
|
+
- `documentNumber` = DAQ, then DBJ
|
|
151
|
+
- `vehicleClass` = DCA, then DAR (trimmed)
|
|
152
|
+
- `restrictionCodes` = DCB, then DAS (trimmed)
|
|
153
|
+
- `endorsementCodes` = DCD, then DAT (trimmed)
|
|
154
|
+
- `lastNameAlias` = DBN, then DBO
|
|
155
|
+
- `firstNameAlias` = DBG, then DBP
|
|
156
|
+
- `organDonor` = DDK, then DBH
|
|
157
|
+
|
|
158
|
+
Space-padded v1 fields are automatically trimmed; all-whitespace values become `null`.
|
|
159
|
+
|
|
160
|
+
All fields return `null` when not present in the barcode data.
|
|
161
|
+
|
|
162
|
+
### Additional Parsed Metadata
|
|
163
|
+
|
|
164
|
+
These fields are derived from the barcode header, not from element IDs:
|
|
165
|
+
|
|
166
|
+
| Name | Description | Type | `License` Attribute |
|
|
167
|
+
|:-----|:------------|:-----|:--------------------|
|
|
168
|
+
| Issuer ID | 6-digit IIN of the issuing jurisdiction | `number` | `issuerId` |
|
|
169
|
+
| AAMVA Version | AAMVA version number (1-11) | `number` | `aamvaVersion` |
|
|
170
|
+
| Jurisdiction | Full name of the issuing jurisdiction | `string` | `jurisdiction` |
|
|
171
|
+
| Raw Elements | All element key-value pairs from the barcode | `Record<string, string>` | `raw` |
|
|
172
|
+
|
|
173
|
+
### AAMVA Element IDs by Version
|
|
174
|
+
|
|
175
|
+
**Bold** = mandatory in that version. `--` = not included. New in v11: DDM, DDN, DDO, DDP. Removed in v11: DCL, DDC, DBN, DBG, DBS.
|
|
176
|
+
|
|
177
|
+
| Field | v1 | v2 | v3 | v4 | v5 | v6 | v7 | v8 | v9 | v10 | v11 |
|
|
178
|
+
|:------|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:---:|:---:|
|
|
179
|
+
| First Name | DAC | **DCT** | **DCT** | **DAC** | **DAC** | **DAC** | **DAC** | **DAC** | **DAC** | **DAC** | **DAC** |
|
|
180
|
+
| Last Name | DAB | **DCS** | **DCS** | **DCS** | **DCS** | **DCS** | **DCS** | **DCS** | **DCS** | **DCS** | **DCS** |
|
|
181
|
+
| Middle Name | DAD | **DAD** | **DAD** | **DAD** | **DAD** | **DAD** | **DAD** | **DAD** | **DAD** | **DAD** | **DAD** |
|
|
182
|
+
| Name Suffix | DBN | **DCU** | DCU | DCU | DCU | DCU | DCU | DCU | DCU | DCU | DCU |
|
|
183
|
+
| Date of Birth | **DBB** | **DBB** | **DBB** | **DBB** | **DBB** | **DBB** | **DBB** | **DBB** | **DBB** | **DBB** | **DBB** |
|
|
184
|
+
| Expiration Date | **DBA** | **DBA** | **DBA** | **DBA** | **DBA** | **DBA** | **DBA** | **DBA** | **DBA** | **DBA** | **DBA** |
|
|
185
|
+
| Issue Date | **DBD** | **DBD** | **DBD** | **DBD** | **DBD** | **DBD** | **DBD** | **DBD** | **DBD** | **DBD** | **DBD** |
|
|
186
|
+
| Sex | **DBC** | **DBC** | **DBC** | **DBC** | **DBC** | **DBC** | **DBC** | **DBC** | **DBC** | **DBC** | **DBC** |
|
|
187
|
+
| Eye Color | DAY | **DAY** | **DAY** | **DAY** | **DAY** | **DAY** | **DAY** | **DAY** | **DAY** | **DAY** | **DAY** |
|
|
188
|
+
| Height | DAU | **DAU** | **DAU** | **DAU** | **DAU** | **DAU** | **DAU** | **DAU** | **DAU** | **DAU** | **DAU** |
|
|
189
|
+
| Street Address | **DAG** | **DAG** | **DAG** | **DAG** | **DAG** | **DAG** | **DAG** | **DAG** | **DAG** | **DAG** | **DAG** |
|
|
190
|
+
| Street Address 2 | DAH | DAH | DAH | DAH | DAH | DAH | DAH | DAH | DAH | DAH | DAH |
|
|
191
|
+
| City | **DAI** | **DAI** | **DAI** | **DAI** | **DAI** | **DAI** | **DAI** | **DAI** | **DAI** | **DAI** | **DAI** |
|
|
192
|
+
| State | **DAJ** | **DAJ** | **DAJ** | **DAJ** | **DAJ** | **DAJ** | **DAJ** | **DAJ** | **DAJ** | **DAJ** | **DAJ** |
|
|
193
|
+
| Postal Code | **DAK** | **DAK** | **DAK** | **DAK** | **DAK** | **DAK** | **DAK** | **DAK** | **DAK** | **DAK** | **DAK** |
|
|
194
|
+
| Country | `--` | **DCG** | **DCG** | **DCG** | **DCG** | **DCG** | **DCG** | **DCG** | **DCG** | **DCG** | **DCG** |
|
|
195
|
+
| Document Number | **DAQ** | **DAQ** | **DAQ** | **DAQ** | **DAQ** | **DAQ** | **DAQ** | **DAQ** | **DAQ** | **DAQ** | **DAQ** |
|
|
196
|
+
| Document Discriminator | `--` | **DCF** | **DCF** | **DCF** | **DCF** | **DCF** | **DCF** | **DCF** | **DCF** | **DCF** | **DCF** |
|
|
197
|
+
| Compliance Type | `--` | `--` | `--` | DDA | DDA | DDA | DDA | DDA | DDA | DDA | DDA |
|
|
198
|
+
| Card Revision Date | `--` | `--` | `--` | DDB | DDB | DDB | DDB | DDB | DDB | DDB | DDB |
|
|
199
|
+
| Limited Duration | `--` | `--` | `--` | DDD | DDD | DDD | DDD | DDD | DDD | DDD | DDD |
|
|
200
|
+
| Organ Donor | `--` | `--` | `--` | `--` | `--` | `--` | `--` | DDK | DDK | DDK | DDK |
|
|
201
|
+
| Veteran | `--` | `--` | `--` | `--` | `--` | `--` | `--` | DDL | DDL | DDL | DDL |
|
|
202
|
+
| First Name Truncation | `--` | **DDF** | `--` | **DDF** | **DDF** | **DDF** | **DDF** | **DDF** | **DDF** | **DDF** | **DDF** |
|
|
203
|
+
| Middle Name Truncation | `--` | **DDG** | `--` | **DDG** | **DDG** | **DDG** | **DDG** | **DDG** | **DDG** | **DDG** | **DDG** |
|
|
204
|
+
| Last Name Truncation | `--` | **DDE** | `--` | **DDE** | **DDE** | **DDE** | **DDE** | **DDE** | **DDE** | **DDE** | **DDE** |
|
|
205
|
+
| Place of Birth | `--` | `--` | DCI | DCI | DCI | DCI | DCI | DCI | DCI | DCI | DCI |
|
|
206
|
+
| Audit Information | `--` | `--` | DCJ | DCJ | DCJ | DCJ | DCJ | DCJ | DCJ | DCJ | DCJ |
|
|
207
|
+
| Inventory Control | `--` | `--` | DCK | DCK | DCK | DCK | DCK | DCK | DCK | DCK | DCK |
|
|
208
|
+
| Hair Color | DAZ | DAZ | DAZ | DAZ | DAZ | DAZ | DAZ | DAZ | DAZ | DAZ | DAZ |
|
|
209
|
+
| Weight | DAW | DAW | DAW | DAW | DAW | DAW | DAW | DAW | DAW | DAW | DAW |
|
|
210
|
+
| Last Name Alias | DBO | DBN | DBN | DBN | DBN | DBN | DBN | DBN | DBN | DBN | `--` |
|
|
211
|
+
| First Name Alias | DBP | DBG | DBG | DBG | DBG | DBG | DBG | DBG | DBG | DBG | `--` |
|
|
212
|
+
| Suffix Alias | DBR | `--` | DBS | DBS | DBS | DBS | DBS | DBS | DBS | DBS | `--` |
|
|
213
|
+
| Race/Ethnicity | `--` | `--` | DCL | DCL | DCL | DCL | DCL | DCL | DCL | DCL | `--` |
|
|
214
|
+
| HAZMAT Endorsement Exp. | `--` | `--` | `--` | DDC | DDC | DDC | DDC | DDC | DDC | DDC | `--` |
|
|
215
|
+
| CDL Indicator | `--` | `--` | `--` | `--` | `--` | `--` | `--` | `--` | `--` | `--` | DDM |
|
|
216
|
+
| Non-Domiciled Indicator | `--` | `--` | `--` | `--` | `--` | `--` | `--` | `--` | `--` | `--` | DDN |
|
|
217
|
+
| Enhanced Document | `--` | `--` | `--` | `--` | `--` | `--` | `--` | `--` | `--` | `--` | DDO |
|
|
218
|
+
| Permit Indicator | `--` | `--` | `--` | `--` | `--` | `--` | `--` | `--` | `--` | `--` | DDP |
|
|
219
|
+
|
|
220
|
+
## Low-Level API
|
|
221
|
+
|
|
222
|
+
For advanced use cases, you can parse the raw barcode structure:
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
import { parseBarcodeString } from "aamva-decoder";
|
|
226
|
+
|
|
227
|
+
const barcode = parseBarcodeString(barcodeData);
|
|
228
|
+
console.log(barcode.header.aamvaVersion); // 11
|
|
229
|
+
console.log(barcode.header.issuerId); // 636014
|
|
230
|
+
|
|
231
|
+
for (const subfile of barcode.subfiles) {
|
|
232
|
+
console.log(subfile.subfileType); // "DL", "ZC", etc.
|
|
233
|
+
console.log(subfile.elements); // Record<string, string>
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Utility Exports
|
|
238
|
+
|
|
239
|
+
### `parseWeightRange(code: string): WeightRange`
|
|
240
|
+
|
|
241
|
+
Parses a weight range code (0-9) from the DCE element into a descriptive object:
|
|
242
|
+
|
|
243
|
+
```ts
|
|
244
|
+
import { parseWeightRange } from "aamva-decoder";
|
|
245
|
+
|
|
246
|
+
const range = parseWeightRange("5");
|
|
247
|
+
// { code: "5", description: "191-220 lbs (87-100 kg)", lbs: "191-220", kg: "87-100" }
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
The `WEIGHT_RANGES` constant array is also exported for enumeration.
|
|
251
|
+
|
|
252
|
+
## License
|
|
253
|
+
|
|
254
|
+
MIT
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export declare const COMPLIANCE_INDICATOR = "@";
|
|
2
|
+
export declare const DATA_ELEMENT_SEPARATOR = "\n";
|
|
3
|
+
export declare const RECORD_SEPARATOR = "\u001E";
|
|
4
|
+
export declare const SEGMENT_TERMINATOR = "\r";
|
|
5
|
+
export declare const FILE_TYPE = "ANSI ";
|
|
6
|
+
export interface FileHeader {
|
|
7
|
+
issuerId: number;
|
|
8
|
+
aamvaVersion: number;
|
|
9
|
+
numberOfEntries: number;
|
|
10
|
+
jurisdictionVersion: number;
|
|
11
|
+
}
|
|
12
|
+
export interface SubfileDesignator {
|
|
13
|
+
subfileType: string;
|
|
14
|
+
offset: number;
|
|
15
|
+
length: number;
|
|
16
|
+
}
|
|
17
|
+
export interface Subfile {
|
|
18
|
+
subfileType: string;
|
|
19
|
+
elements: Record<string, string>;
|
|
20
|
+
}
|
|
21
|
+
export interface BarcodeFile {
|
|
22
|
+
header: FileHeader;
|
|
23
|
+
subfiles: Subfile[];
|
|
24
|
+
}
|
|
25
|
+
export declare function trimBefore(char: string, str: string): string;
|
|
26
|
+
export declare function headerLength(aamvaVersion: number): 19 | 21;
|
|
27
|
+
export declare function parseFileHeader(barcodeString: string): FileHeader;
|
|
28
|
+
export declare function parseSubfileDesignator(barcodeString: string, aamvaVersion: number, designatorIndex: number): SubfileDesignator;
|
|
29
|
+
export declare function parseSubfile(barcodeString: string, designator: SubfileDesignator): Subfile;
|
|
30
|
+
export declare function parseBarcodeString(barcodeString: string): BarcodeFile;
|
package/dist/barcode.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FILE_TYPE = exports.SEGMENT_TERMINATOR = exports.RECORD_SEPARATOR = exports.DATA_ELEMENT_SEPARATOR = exports.COMPLIANCE_INDICATOR = void 0;
|
|
4
|
+
exports.trimBefore = trimBefore;
|
|
5
|
+
exports.headerLength = headerLength;
|
|
6
|
+
exports.parseFileHeader = parseFileHeader;
|
|
7
|
+
exports.parseSubfileDesignator = parseSubfileDesignator;
|
|
8
|
+
exports.parseSubfile = parseSubfile;
|
|
9
|
+
exports.parseBarcodeString = parseBarcodeString;
|
|
10
|
+
exports.COMPLIANCE_INDICATOR = "@";
|
|
11
|
+
exports.DATA_ELEMENT_SEPARATOR = "\n";
|
|
12
|
+
exports.RECORD_SEPARATOR = "\x1e";
|
|
13
|
+
exports.SEGMENT_TERMINATOR = "\r";
|
|
14
|
+
exports.FILE_TYPE = "ANSI ";
|
|
15
|
+
function trimBefore(char, str) {
|
|
16
|
+
const index = str.indexOf(char);
|
|
17
|
+
return index === -1 ? str : str.slice(index);
|
|
18
|
+
}
|
|
19
|
+
function headerLength(aamvaVersion) {
|
|
20
|
+
if (aamvaVersion < 1 || aamvaVersion > 99) {
|
|
21
|
+
throw new Error("aamva_version is out of range (1-99).");
|
|
22
|
+
}
|
|
23
|
+
return aamvaVersion < 2 ? 19 : 21;
|
|
24
|
+
}
|
|
25
|
+
function parseFileHeader(barcodeString) {
|
|
26
|
+
const MIN_LENGTH = 17;
|
|
27
|
+
if (barcodeString.length < MIN_LENGTH) {
|
|
28
|
+
throw new Error("Header length is too short.");
|
|
29
|
+
}
|
|
30
|
+
else if (barcodeString[0] !== exports.COMPLIANCE_INDICATOR) {
|
|
31
|
+
throw new Error("Header element 'COMPLIANCE_INDICATOR' is invalid.");
|
|
32
|
+
}
|
|
33
|
+
else if (barcodeString[1] !== exports.DATA_ELEMENT_SEPARATOR) {
|
|
34
|
+
throw new Error("Header element 'DATA_ELEMENT_SEPARATOR' is invalid.");
|
|
35
|
+
}
|
|
36
|
+
else if (barcodeString[2] !== exports.RECORD_SEPARATOR) {
|
|
37
|
+
throw new Error("Header element 'RECORD_SEPARATOR' is invalid.");
|
|
38
|
+
}
|
|
39
|
+
else if (barcodeString[3] !== exports.SEGMENT_TERMINATOR) {
|
|
40
|
+
throw new Error("Header element 'SEGMENT_TERMINATOR' is invalid.");
|
|
41
|
+
}
|
|
42
|
+
else if (barcodeString.slice(4, 9) !== exports.FILE_TYPE) {
|
|
43
|
+
throw new Error("Header element 'FILE_TYPE' is invalid.");
|
|
44
|
+
}
|
|
45
|
+
const aamvaVersion = parseInt(barcodeString.slice(15, 17), 10);
|
|
46
|
+
if (barcodeString.length < headerLength(aamvaVersion)) {
|
|
47
|
+
throw new Error("Header length is too short.");
|
|
48
|
+
}
|
|
49
|
+
const issuerId = parseInt(barcodeString.slice(9, 15), 10);
|
|
50
|
+
const numberOfEntries = parseInt(aamvaVersion < 2 ? barcodeString.slice(17, 19) : barcodeString.slice(19, 21), 10);
|
|
51
|
+
const jurisdictionVersion = aamvaVersion < 2 ? 0 : parseInt(barcodeString.slice(17, 19), 10);
|
|
52
|
+
return {
|
|
53
|
+
issuerId,
|
|
54
|
+
aamvaVersion,
|
|
55
|
+
numberOfEntries,
|
|
56
|
+
jurisdictionVersion,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function parseSubfileDesignator(barcodeString, aamvaVersion, designatorIndex) {
|
|
60
|
+
const DESIGNATOR_LENGTH = 10;
|
|
61
|
+
const cursor = designatorIndex * DESIGNATOR_LENGTH + headerLength(aamvaVersion);
|
|
62
|
+
if (barcodeString.length < cursor + DESIGNATOR_LENGTH) {
|
|
63
|
+
throw new Error("Subfile designator is too short.");
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
subfileType: barcodeString.slice(cursor, cursor + 2),
|
|
67
|
+
offset: parseInt(barcodeString.slice(cursor + 2, cursor + 6), 10),
|
|
68
|
+
length: parseInt(barcodeString.slice(cursor + 6, cursor + 10), 10),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function parseSubfile(barcodeString, designator) {
|
|
72
|
+
const { subfileType, offset, length } = designator;
|
|
73
|
+
const endOffset = offset + length;
|
|
74
|
+
if (barcodeString.length < endOffset) {
|
|
75
|
+
throw new Error("Subfile length is too short.");
|
|
76
|
+
}
|
|
77
|
+
else if (barcodeString.slice(offset, offset + 2) !== subfileType) {
|
|
78
|
+
throw new Error("Subfile is missing subfile type.");
|
|
79
|
+
}
|
|
80
|
+
else if (barcodeString[endOffset - 1] !== exports.SEGMENT_TERMINATOR) {
|
|
81
|
+
throw new Error("Subfile is missing segment terminator.");
|
|
82
|
+
}
|
|
83
|
+
const items = barcodeString
|
|
84
|
+
.slice(offset + 2, endOffset - 1)
|
|
85
|
+
.split(exports.DATA_ELEMENT_SEPARATOR)
|
|
86
|
+
.filter(Boolean);
|
|
87
|
+
const elements = {};
|
|
88
|
+
for (const item of items) {
|
|
89
|
+
elements[item.slice(0, 3)] = item.slice(3);
|
|
90
|
+
}
|
|
91
|
+
return { subfileType, elements };
|
|
92
|
+
}
|
|
93
|
+
function parseBarcodeString(barcodeString) {
|
|
94
|
+
barcodeString = trimBefore(exports.COMPLIANCE_INDICATOR, barcodeString);
|
|
95
|
+
const header = parseFileHeader(barcodeString);
|
|
96
|
+
if (header.numberOfEntries < 1) {
|
|
97
|
+
throw new Error("Number of entries cannot be less than 1.");
|
|
98
|
+
}
|
|
99
|
+
const subfiles = [];
|
|
100
|
+
for (let i = 0; i < header.numberOfEntries; i++) {
|
|
101
|
+
const designator = parseSubfileDesignator(barcodeString, header.aamvaVersion, i);
|
|
102
|
+
const subfile = parseSubfile(barcodeString, designator);
|
|
103
|
+
subfiles.push(subfile);
|
|
104
|
+
}
|
|
105
|
+
return { header, subfiles };
|
|
106
|
+
}
|
package/dist/dates.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare const ISO_FORMAT: "ISO";
|
|
2
|
+
export declare const IMPERIAL_FORMAT: "IMPERIAL";
|
|
3
|
+
export type DateFormat = typeof ISO_FORMAT | typeof IMPERIAL_FORMAT;
|
|
4
|
+
export declare function countryDateFormat(country: string): DateFormat;
|
|
5
|
+
export declare function getDateFormat(aamvaVersion: number, country: string): DateFormat;
|
|
6
|
+
export declare function parseDate(dateString: string, format: DateFormat): Date;
|
package/dist/dates.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.IMPERIAL_FORMAT = exports.ISO_FORMAT = void 0;
|
|
4
|
+
exports.countryDateFormat = countryDateFormat;
|
|
5
|
+
exports.getDateFormat = getDateFormat;
|
|
6
|
+
exports.parseDate = parseDate;
|
|
7
|
+
exports.ISO_FORMAT = "ISO";
|
|
8
|
+
exports.IMPERIAL_FORMAT = "IMPERIAL";
|
|
9
|
+
function countryDateFormat(country) {
|
|
10
|
+
country = country.toUpperCase();
|
|
11
|
+
if (country === "CANADA")
|
|
12
|
+
return exports.ISO_FORMAT;
|
|
13
|
+
if (country === "MEXICO")
|
|
14
|
+
return exports.ISO_FORMAT;
|
|
15
|
+
if (country === "USA")
|
|
16
|
+
return exports.IMPERIAL_FORMAT;
|
|
17
|
+
throw new Error("Provided country is not supported.");
|
|
18
|
+
}
|
|
19
|
+
function getDateFormat(aamvaVersion, country) {
|
|
20
|
+
return aamvaVersion < 3 ? exports.IMPERIAL_FORMAT : countryDateFormat(country);
|
|
21
|
+
}
|
|
22
|
+
function parseDate(dateString, format) {
|
|
23
|
+
let year;
|
|
24
|
+
let month;
|
|
25
|
+
let day;
|
|
26
|
+
if (format === exports.ISO_FORMAT) {
|
|
27
|
+
// YYYYMMDD
|
|
28
|
+
if (dateString.length !== 8) {
|
|
29
|
+
throw new Error("Invalid date format for provided date string.");
|
|
30
|
+
}
|
|
31
|
+
year = parseInt(dateString.slice(0, 4), 10);
|
|
32
|
+
month = parseInt(dateString.slice(4, 6), 10);
|
|
33
|
+
day = parseInt(dateString.slice(6, 8), 10);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
// MMDDYYYY
|
|
37
|
+
if (dateString.length !== 8) {
|
|
38
|
+
throw new Error("Invalid date format for provided date string.");
|
|
39
|
+
}
|
|
40
|
+
month = parseInt(dateString.slice(0, 2), 10);
|
|
41
|
+
day = parseInt(dateString.slice(2, 4), 10);
|
|
42
|
+
year = parseInt(dateString.slice(4, 8), 10);
|
|
43
|
+
}
|
|
44
|
+
if (isNaN(year) || isNaN(month) || isNaN(day)) {
|
|
45
|
+
throw new Error("Invalid date format for provided date string.");
|
|
46
|
+
}
|
|
47
|
+
const date = new Date(year, month - 1, day);
|
|
48
|
+
// Validate the date components round-trip correctly
|
|
49
|
+
if (date.getFullYear() !== year ||
|
|
50
|
+
date.getMonth() !== month - 1 ||
|
|
51
|
+
date.getDate() !== day) {
|
|
52
|
+
throw new Error("Invalid date format for provided date string.");
|
|
53
|
+
}
|
|
54
|
+
return date;
|
|
55
|
+
}
|
package/dist/eyeColor.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EYE_COLORS = void 0;
|
|
4
|
+
exports.parseEyeColor = parseEyeColor;
|
|
5
|
+
exports.EYE_COLORS = [
|
|
6
|
+
{ code: "BLK", color: "Black", description: "Black or very dark brown" },
|
|
7
|
+
{ code: "BLU", color: "Blue", description: "Blue" },
|
|
8
|
+
{ code: "BRO", color: "Brown", description: "Brown, including amber" },
|
|
9
|
+
{
|
|
10
|
+
code: "DIC",
|
|
11
|
+
color: "Dichromatic",
|
|
12
|
+
description: "Dichromatic or multicolor, of one or both eyes",
|
|
13
|
+
},
|
|
14
|
+
{ code: "GRY", color: "Gray", description: "Gray" },
|
|
15
|
+
{ code: "GRN", color: "Green", description: "Green" },
|
|
16
|
+
{
|
|
17
|
+
code: "HAZ",
|
|
18
|
+
color: "Hazel",
|
|
19
|
+
description: "Hazel, a mixture of colors, most commonly green and brown",
|
|
20
|
+
},
|
|
21
|
+
{ code: "MAR", color: "Maroon", description: "Maroon" },
|
|
22
|
+
{ code: "PNK", color: "Pink", description: "Pink or albino" },
|
|
23
|
+
{ code: "UNK", color: "Unknown", description: "Unknown" },
|
|
24
|
+
];
|
|
25
|
+
function parseEyeColor(code) {
|
|
26
|
+
code = code === "BRN" ? "BRO" : code;
|
|
27
|
+
const found = exports.EYE_COLORS.find((c) => c.code === code);
|
|
28
|
+
if (!found) {
|
|
29
|
+
throw new Error(`Color code '${code}' not found.`);
|
|
30
|
+
}
|
|
31
|
+
return found;
|
|
32
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HAIR_COLORS = void 0;
|
|
4
|
+
exports.parseHairColor = parseHairColor;
|
|
5
|
+
exports.HAIR_COLORS = [
|
|
6
|
+
{ code: "BAL", color: "Bald" },
|
|
7
|
+
{ code: "BLK", color: "Black" },
|
|
8
|
+
{ code: "BLN", color: "Blond" },
|
|
9
|
+
{ code: "BRO", color: "Brown" },
|
|
10
|
+
{ code: "GRY", color: "Gray" },
|
|
11
|
+
{ code: "RED", color: "Red/Auburn" },
|
|
12
|
+
{ code: "SDY", color: "Sandy" },
|
|
13
|
+
{ code: "WHI", color: "White" },
|
|
14
|
+
{ code: "UNK", color: "Unknown" },
|
|
15
|
+
];
|
|
16
|
+
function parseHairColor(code) {
|
|
17
|
+
code = code === "BRN" ? "BRO" : code;
|
|
18
|
+
const found = exports.HAIR_COLORS.find((c) => c.code === code);
|
|
19
|
+
if (!found) {
|
|
20
|
+
throw new Error(`Color code '${code}' not found.`);
|
|
21
|
+
}
|
|
22
|
+
return found;
|
|
23
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { parseBarcodeString, parseFileHeader, parseSubfileDesignator, parseSubfile, trimBefore, headerLength, type BarcodeFile, type FileHeader, type Subfile, type SubfileDesignator, } from "./barcode";
|
|
2
|
+
export { parseLicense, parseLicenseFromBarcode, type License } from "./license";
|
|
3
|
+
export { parseWeightRange, type WeightRange, WEIGHT_RANGES, } from "./weightRange";
|
|
4
|
+
export { getDateFormat, countryDateFormat, parseDate, type DateFormat, } from "./dates";
|
|
5
|
+
export { parseEyeColor, type EyeColor, EYE_COLORS } from "./eyeColor";
|
|
6
|
+
export { parseHairColor, type HairColor, HAIR_COLORS } from "./hairColor";
|
|
7
|
+
export { parseRaceEthnicity, type RaceEthnicity, RACE_ETHNICITIES, } from "./raceEthnicity";
|
|
8
|
+
export { getAuthorityById, type IssuingAuthority, ISSUING_AUTHORITIES, } from "./issuingAuthority";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ISSUING_AUTHORITIES = exports.getAuthorityById = exports.RACE_ETHNICITIES = exports.parseRaceEthnicity = exports.HAIR_COLORS = exports.parseHairColor = exports.EYE_COLORS = exports.parseEyeColor = exports.parseDate = exports.countryDateFormat = exports.getDateFormat = exports.WEIGHT_RANGES = exports.parseWeightRange = exports.parseLicenseFromBarcode = exports.parseLicense = exports.headerLength = exports.trimBefore = exports.parseSubfile = exports.parseSubfileDesignator = exports.parseFileHeader = exports.parseBarcodeString = void 0;
|
|
4
|
+
var barcode_1 = require("./barcode");
|
|
5
|
+
Object.defineProperty(exports, "parseBarcodeString", { enumerable: true, get: function () { return barcode_1.parseBarcodeString; } });
|
|
6
|
+
Object.defineProperty(exports, "parseFileHeader", { enumerable: true, get: function () { return barcode_1.parseFileHeader; } });
|
|
7
|
+
Object.defineProperty(exports, "parseSubfileDesignator", { enumerable: true, get: function () { return barcode_1.parseSubfileDesignator; } });
|
|
8
|
+
Object.defineProperty(exports, "parseSubfile", { enumerable: true, get: function () { return barcode_1.parseSubfile; } });
|
|
9
|
+
Object.defineProperty(exports, "trimBefore", { enumerable: true, get: function () { return barcode_1.trimBefore; } });
|
|
10
|
+
Object.defineProperty(exports, "headerLength", { enumerable: true, get: function () { return barcode_1.headerLength; } });
|
|
11
|
+
var license_1 = require("./license");
|
|
12
|
+
Object.defineProperty(exports, "parseLicense", { enumerable: true, get: function () { return license_1.parseLicense; } });
|
|
13
|
+
Object.defineProperty(exports, "parseLicenseFromBarcode", { enumerable: true, get: function () { return license_1.parseLicenseFromBarcode; } });
|
|
14
|
+
var weightRange_1 = require("./weightRange");
|
|
15
|
+
Object.defineProperty(exports, "parseWeightRange", { enumerable: true, get: function () { return weightRange_1.parseWeightRange; } });
|
|
16
|
+
Object.defineProperty(exports, "WEIGHT_RANGES", { enumerable: true, get: function () { return weightRange_1.WEIGHT_RANGES; } });
|
|
17
|
+
var dates_1 = require("./dates");
|
|
18
|
+
Object.defineProperty(exports, "getDateFormat", { enumerable: true, get: function () { return dates_1.getDateFormat; } });
|
|
19
|
+
Object.defineProperty(exports, "countryDateFormat", { enumerable: true, get: function () { return dates_1.countryDateFormat; } });
|
|
20
|
+
Object.defineProperty(exports, "parseDate", { enumerable: true, get: function () { return dates_1.parseDate; } });
|
|
21
|
+
var eyeColor_1 = require("./eyeColor");
|
|
22
|
+
Object.defineProperty(exports, "parseEyeColor", { enumerable: true, get: function () { return eyeColor_1.parseEyeColor; } });
|
|
23
|
+
Object.defineProperty(exports, "EYE_COLORS", { enumerable: true, get: function () { return eyeColor_1.EYE_COLORS; } });
|
|
24
|
+
var hairColor_1 = require("./hairColor");
|
|
25
|
+
Object.defineProperty(exports, "parseHairColor", { enumerable: true, get: function () { return hairColor_1.parseHairColor; } });
|
|
26
|
+
Object.defineProperty(exports, "HAIR_COLORS", { enumerable: true, get: function () { return hairColor_1.HAIR_COLORS; } });
|
|
27
|
+
var raceEthnicity_1 = require("./raceEthnicity");
|
|
28
|
+
Object.defineProperty(exports, "parseRaceEthnicity", { enumerable: true, get: function () { return raceEthnicity_1.parseRaceEthnicity; } });
|
|
29
|
+
Object.defineProperty(exports, "RACE_ETHNICITIES", { enumerable: true, get: function () { return raceEthnicity_1.RACE_ETHNICITIES; } });
|
|
30
|
+
var issuingAuthority_1 = require("./issuingAuthority");
|
|
31
|
+
Object.defineProperty(exports, "getAuthorityById", { enumerable: true, get: function () { return issuingAuthority_1.getAuthorityById; } });
|
|
32
|
+
Object.defineProperty(exports, "ISSUING_AUTHORITIES", { enumerable: true, get: function () { return issuingAuthority_1.ISSUING_AUTHORITIES; } });
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface IssuingAuthority {
|
|
2
|
+
issuerId: number;
|
|
3
|
+
jurisdiction: string;
|
|
4
|
+
abbr: string | null;
|
|
5
|
+
country: string;
|
|
6
|
+
}
|
|
7
|
+
export declare const ISSUING_AUTHORITIES: readonly IssuingAuthority[];
|
|
8
|
+
export declare function getAuthorityById(idNumber: number): IssuingAuthority;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ISSUING_AUTHORITIES = void 0;
|
|
4
|
+
exports.getAuthorityById = getAuthorityById;
|
|
5
|
+
exports.ISSUING_AUTHORITIES = [
|
|
6
|
+
{ issuerId: 604426, jurisdiction: "Prince Edward Island", abbr: "PE", country: "Canada" },
|
|
7
|
+
{ issuerId: 604427, jurisdiction: "American Samoa", abbr: "AS", country: "USA" },
|
|
8
|
+
{ issuerId: 604428, jurisdiction: "Quebec", abbr: "QC", country: "Canada" },
|
|
9
|
+
{ issuerId: 604429, jurisdiction: "Yukon", abbr: "YT", country: "Canada" },
|
|
10
|
+
{ issuerId: 604430, jurisdiction: "Norther Marianna Islands", abbr: "MP", country: "USA" },
|
|
11
|
+
{ issuerId: 604431, jurisdiction: "Puerto Rico", abbr: "PR", country: "USA" },
|
|
12
|
+
{ issuerId: 604432, jurisdiction: "Alberta", abbr: "AB", country: "Canada" },
|
|
13
|
+
{ issuerId: 604433, jurisdiction: "Nunavut", abbr: "NU", country: "Canada" },
|
|
14
|
+
{ issuerId: 604434, jurisdiction: "Northwest Territories", abbr: "NT", country: "Canada" },
|
|
15
|
+
{ issuerId: 636000, jurisdiction: "Virginia", abbr: "VA", country: "USA" },
|
|
16
|
+
{ issuerId: 636001, jurisdiction: "New York", abbr: "NY", country: "USA" },
|
|
17
|
+
{ issuerId: 636002, jurisdiction: "Massachusetts", abbr: "MA", country: "USA" },
|
|
18
|
+
{ issuerId: 636003, jurisdiction: "Maryland", abbr: "MD", country: "USA" },
|
|
19
|
+
{ issuerId: 636004, jurisdiction: "North Carolina", abbr: "NC", country: "USA" },
|
|
20
|
+
{ issuerId: 636005, jurisdiction: "South Carolina", abbr: "SC", country: "USA" },
|
|
21
|
+
{ issuerId: 636006, jurisdiction: "Connecticut", abbr: "CT", country: "USA" },
|
|
22
|
+
{ issuerId: 636007, jurisdiction: "Louisiana", abbr: "LA", country: "USA" },
|
|
23
|
+
{ issuerId: 636008, jurisdiction: "Montana", abbr: "MT", country: "USA" },
|
|
24
|
+
{ issuerId: 636009, jurisdiction: "New Mexico", abbr: "NM", country: "USA" },
|
|
25
|
+
{ issuerId: 636010, jurisdiction: "Florida", abbr: "FL", country: "USA" },
|
|
26
|
+
{ issuerId: 636011, jurisdiction: "Delaware", abbr: "DE", country: "USA" },
|
|
27
|
+
{ issuerId: 636012, jurisdiction: "Ontario", abbr: "ON", country: "Canada" },
|
|
28
|
+
{ issuerId: 636013, jurisdiction: "Nova Scotia", abbr: "NS", country: "Canada" },
|
|
29
|
+
{ issuerId: 636014, jurisdiction: "California", abbr: "CA", country: "USA" },
|
|
30
|
+
{ issuerId: 636015, jurisdiction: "Texas", abbr: "TX", country: "USA" },
|
|
31
|
+
{ issuerId: 636016, jurisdiction: "Newfoundland", abbr: "NF", country: "Canada" },
|
|
32
|
+
{ issuerId: 636017, jurisdiction: "New Brunswick", abbr: "NB", country: "Canada" },
|
|
33
|
+
{ issuerId: 636018, jurisdiction: "Iowa", abbr: "IA", country: "USA" },
|
|
34
|
+
{ issuerId: 636019, jurisdiction: "Guam", abbr: "GU", country: "USA" },
|
|
35
|
+
{ issuerId: 636020, jurisdiction: "Colorado", abbr: "GM", country: "USA" },
|
|
36
|
+
{ issuerId: 636021, jurisdiction: "Arkansas", abbr: "AR", country: "USA" },
|
|
37
|
+
{ issuerId: 636022, jurisdiction: "Kansas", abbr: "KS", country: "USA" },
|
|
38
|
+
{ issuerId: 636023, jurisdiction: "Ohio", abbr: "OH", country: "USA" },
|
|
39
|
+
{ issuerId: 636024, jurisdiction: "Vermont", abbr: "VT", country: "USA" },
|
|
40
|
+
{ issuerId: 636025, jurisdiction: "Pennsylvania", abbr: "PA", country: "USA" },
|
|
41
|
+
{ issuerId: 636026, jurisdiction: "Arizona", abbr: "AZ", country: "USA" },
|
|
42
|
+
{ issuerId: 636027, jurisdiction: "State Dept. (Diplomatic)", abbr: null, country: "USA" },
|
|
43
|
+
{ issuerId: 636028, jurisdiction: "British Columbia", abbr: "BC", country: "Canada" },
|
|
44
|
+
{ issuerId: 636029, jurisdiction: "Oregon", abbr: "OR", country: "USA" },
|
|
45
|
+
{ issuerId: 636030, jurisdiction: "Missouri", abbr: "MO", country: "USA" },
|
|
46
|
+
{ issuerId: 636031, jurisdiction: "Wisconsin", abbr: "WI", country: "USA" },
|
|
47
|
+
{ issuerId: 636032, jurisdiction: "Michigan", abbr: "MI", country: "USA" },
|
|
48
|
+
{ issuerId: 636033, jurisdiction: "Alabama", abbr: "AL", country: "USA" },
|
|
49
|
+
{ issuerId: 636034, jurisdiction: "North Dakota", abbr: "ND", country: "USA" },
|
|
50
|
+
{ issuerId: 636035, jurisdiction: "Illinois", abbr: "IL", country: "USA" },
|
|
51
|
+
{ issuerId: 636036, jurisdiction: "New Jersey", abbr: "NJ", country: "USA" },
|
|
52
|
+
{ issuerId: 636037, jurisdiction: "Indiana", abbr: "IN", country: "USA" },
|
|
53
|
+
{ issuerId: 636038, jurisdiction: "Minnesota", abbr: "MN", country: "USA" },
|
|
54
|
+
{ issuerId: 636039, jurisdiction: "New Hampshire", abbr: "NH", country: "USA" },
|
|
55
|
+
{ issuerId: 636040, jurisdiction: "Utah", abbr: "UT", country: "USA" },
|
|
56
|
+
{ issuerId: 636041, jurisdiction: "Maine", abbr: "ME", country: "USA" },
|
|
57
|
+
{ issuerId: 636042, jurisdiction: "South Dakota", abbr: "SD", country: "USA" },
|
|
58
|
+
{ issuerId: 636043, jurisdiction: "District of Columbia", abbr: "DC", country: "USA" },
|
|
59
|
+
{ issuerId: 636044, jurisdiction: "Saskatchewan", abbr: "SK", country: "Canada" },
|
|
60
|
+
{ issuerId: 636045, jurisdiction: "Washington", abbr: "WA", country: "USA" },
|
|
61
|
+
{ issuerId: 636046, jurisdiction: "Kentucky", abbr: "KY", country: "USA" },
|
|
62
|
+
{ issuerId: 636047, jurisdiction: "Hawaii", abbr: "HI", country: "USA" },
|
|
63
|
+
{ issuerId: 636048, jurisdiction: "Manitoba", abbr: "MB", country: "Canada" },
|
|
64
|
+
{ issuerId: 636049, jurisdiction: "Nevada", abbr: "NV", country: "USA" },
|
|
65
|
+
{ issuerId: 636050, jurisdiction: "Idaho", abbr: "ID", country: "USA" },
|
|
66
|
+
{ issuerId: 636051, jurisdiction: "Mississippi", abbr: "MS", country: "USA" },
|
|
67
|
+
{ issuerId: 636052, jurisdiction: "Rhode Island", abbr: "RI", country: "USA" },
|
|
68
|
+
{ issuerId: 636053, jurisdiction: "Tennessee", abbr: "TN", country: "USA" },
|
|
69
|
+
{ issuerId: 636054, jurisdiction: "Nebraska", abbr: "NE", country: "USA" },
|
|
70
|
+
{ issuerId: 636055, jurisdiction: "Georgia", abbr: "GA", country: "USA" },
|
|
71
|
+
{ issuerId: 636056, jurisdiction: "Coahuila", abbr: "CU", country: "Mexico" },
|
|
72
|
+
{ issuerId: 636057, jurisdiction: "Hidalgo", abbr: "HL", country: "Mexico" },
|
|
73
|
+
{ issuerId: 636058, jurisdiction: "Oklahoma", abbr: "OK", country: "USA" },
|
|
74
|
+
{ issuerId: 636059, jurisdiction: "Alaska", abbr: "AK", country: "USA" },
|
|
75
|
+
{ issuerId: 636060, jurisdiction: "Wyoming", abbr: "WY", country: "USA" },
|
|
76
|
+
{ issuerId: 636061, jurisdiction: "West Virginia", abbr: "WV", country: "USA" },
|
|
77
|
+
{ issuerId: 636062, jurisdiction: "Virgin Islands", abbr: "VI", country: "USA" },
|
|
78
|
+
];
|
|
79
|
+
function getAuthorityById(idNumber) {
|
|
80
|
+
const found = exports.ISSUING_AUTHORITIES.find((a) => a.issuerId === idNumber);
|
|
81
|
+
if (!found) {
|
|
82
|
+
throw new Error(`Issuer ID number '${idNumber}' not found in authority list.`);
|
|
83
|
+
}
|
|
84
|
+
return found;
|
|
85
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { type BarcodeFile } from "./barcode";
|
|
2
|
+
export interface License {
|
|
3
|
+
firstName: string | null;
|
|
4
|
+
lastName: string | null;
|
|
5
|
+
middleName: string | null;
|
|
6
|
+
nameSuffix: string | null;
|
|
7
|
+
dateOfBirth: Date | null;
|
|
8
|
+
expirationDate: Date | null;
|
|
9
|
+
issueDate: Date | null;
|
|
10
|
+
streetAddress: string | null;
|
|
11
|
+
city: string | null;
|
|
12
|
+
state: string | null;
|
|
13
|
+
postalCode: string | null;
|
|
14
|
+
country: string | null;
|
|
15
|
+
documentNumber: string | null;
|
|
16
|
+
documentDiscriminator: string | null;
|
|
17
|
+
sex: "Male" | "Female" | "Not specified" | null;
|
|
18
|
+
height: string | null;
|
|
19
|
+
weight: string | null;
|
|
20
|
+
eyeColor: string | null;
|
|
21
|
+
hairColor: string | null;
|
|
22
|
+
streetAddress2: string | null;
|
|
23
|
+
complianceType: string | null;
|
|
24
|
+
cardRevisionDate: Date | null;
|
|
25
|
+
limitedDurationDocument: boolean | null;
|
|
26
|
+
organDonor: boolean | null;
|
|
27
|
+
veteran: boolean | null;
|
|
28
|
+
firstNameTruncation: string | null;
|
|
29
|
+
middleNameTruncation: string | null;
|
|
30
|
+
lastNameTruncation: string | null;
|
|
31
|
+
placeOfBirth: string | null;
|
|
32
|
+
auditInformation: string | null;
|
|
33
|
+
inventoryControlNumber: string | null;
|
|
34
|
+
lastNameAlias: string | null;
|
|
35
|
+
firstNameAlias: string | null;
|
|
36
|
+
suffixAlias: string | null;
|
|
37
|
+
raceEthnicity: string | null;
|
|
38
|
+
hazmatEndorsementExpiration: Date | null;
|
|
39
|
+
cdlIndicator: boolean | null;
|
|
40
|
+
nonDomiciledIndicator: boolean | null;
|
|
41
|
+
enhancedDocumentIndicator: boolean | null;
|
|
42
|
+
permitIndicator: boolean | null;
|
|
43
|
+
vehicleClass: string | null;
|
|
44
|
+
restrictionCodes: string | null;
|
|
45
|
+
endorsementCodes: string | null;
|
|
46
|
+
heightCm: string | null;
|
|
47
|
+
weightKg: string | null;
|
|
48
|
+
weightRange: string | null;
|
|
49
|
+
federalCommercialVehicleCodes: string | null;
|
|
50
|
+
standardVehicleClassification: string | null;
|
|
51
|
+
standardEndorsementCode: string | null;
|
|
52
|
+
standardRestrictionCode: string | null;
|
|
53
|
+
vehicleClassDescription: string | null;
|
|
54
|
+
endorsementCodeDescription: string | null;
|
|
55
|
+
restrictionCodeDescription: string | null;
|
|
56
|
+
namePrefix: string | null;
|
|
57
|
+
firstNameV2: string | null;
|
|
58
|
+
lastNameV1: string | null;
|
|
59
|
+
nameSuffixV1: string | null;
|
|
60
|
+
classificationCodeV1: string | null;
|
|
61
|
+
restrictionCodeV1: string | null;
|
|
62
|
+
endorsementCodeV1: string | null;
|
|
63
|
+
residenceStreetAddress: string | null;
|
|
64
|
+
residenceStreetAddress2: string | null;
|
|
65
|
+
residenceCity: string | null;
|
|
66
|
+
residenceJurisdictionCode: string | null;
|
|
67
|
+
residencePostalCode: string | null;
|
|
68
|
+
issueTimestamp: string | null;
|
|
69
|
+
numberOfDuplicates: string | null;
|
|
70
|
+
organDonorLegacy: string | null;
|
|
71
|
+
nonResidentIndicator: string | null;
|
|
72
|
+
uniqueCustomerId: string | null;
|
|
73
|
+
socialSecurityNumber: string | null;
|
|
74
|
+
akaDateOfBirth: Date | null;
|
|
75
|
+
akaSocialSecurityNumber: string | null;
|
|
76
|
+
akaLastNameV1: string | null;
|
|
77
|
+
akaFirstNameV1: string | null;
|
|
78
|
+
akaMiddleName: string | null;
|
|
79
|
+
akaSuffixV1: string | null;
|
|
80
|
+
under18Until: Date | null;
|
|
81
|
+
under19Until: Date | null;
|
|
82
|
+
under21Until: Date | null;
|
|
83
|
+
raw: Record<string, string>;
|
|
84
|
+
issuerId: number;
|
|
85
|
+
aamvaVersion: number;
|
|
86
|
+
jurisdiction: string | null;
|
|
87
|
+
}
|
|
88
|
+
export declare function parseLicenseFromBarcode(barcode: BarcodeFile): License;
|
|
89
|
+
export declare function parseLicense(barcodeString: string): License;
|
package/dist/license.js
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseLicenseFromBarcode = parseLicenseFromBarcode;
|
|
4
|
+
exports.parseLicense = parseLicense;
|
|
5
|
+
const barcode_1 = require("./barcode");
|
|
6
|
+
const dates_1 = require("./dates");
|
|
7
|
+
const eyeColor_1 = require("./eyeColor");
|
|
8
|
+
const hairColor_1 = require("./hairColor");
|
|
9
|
+
const issuingAuthority_1 = require("./issuingAuthority");
|
|
10
|
+
const raceEthnicity_1 = require("./raceEthnicity");
|
|
11
|
+
const weightRange_1 = require("./weightRange");
|
|
12
|
+
function get(elements, key) {
|
|
13
|
+
const val = elements[key];
|
|
14
|
+
return val !== undefined ? val : null;
|
|
15
|
+
}
|
|
16
|
+
/** Returns trimmed value or null for space-padded v1 fields. */
|
|
17
|
+
function getOrNull(elements, key) {
|
|
18
|
+
const val = elements[key];
|
|
19
|
+
if (val === undefined)
|
|
20
|
+
return null;
|
|
21
|
+
const trimmed = val.trim();
|
|
22
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
23
|
+
}
|
|
24
|
+
function parseSex(value, aamvaVersion) {
|
|
25
|
+
if (value === null)
|
|
26
|
+
return null;
|
|
27
|
+
if (aamvaVersion < 2) {
|
|
28
|
+
// v1 used "M"/"F"
|
|
29
|
+
if (value === "M")
|
|
30
|
+
return "Male";
|
|
31
|
+
if (value === "F")
|
|
32
|
+
return "Female";
|
|
33
|
+
return "Not specified";
|
|
34
|
+
}
|
|
35
|
+
if (value === "1")
|
|
36
|
+
return "Male";
|
|
37
|
+
if (value === "2")
|
|
38
|
+
return "Female";
|
|
39
|
+
if (value === "9")
|
|
40
|
+
return "Not specified";
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
function tryParseDate(dateStr, aamvaVersion, country) {
|
|
44
|
+
if (!dateStr)
|
|
45
|
+
return null;
|
|
46
|
+
try {
|
|
47
|
+
const format = (0, dates_1.getDateFormat)(aamvaVersion, country);
|
|
48
|
+
return (0, dates_1.parseDate)(dateStr, format);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function tryParseEyeColor(code) {
|
|
55
|
+
if (!code)
|
|
56
|
+
return null;
|
|
57
|
+
try {
|
|
58
|
+
return (0, eyeColor_1.parseEyeColor)(code.trim()).color;
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function parseIndicator(value) {
|
|
65
|
+
if (value === null)
|
|
66
|
+
return null;
|
|
67
|
+
return value === "1";
|
|
68
|
+
}
|
|
69
|
+
function tryParseRaceEthnicity(code) {
|
|
70
|
+
if (!code)
|
|
71
|
+
return null;
|
|
72
|
+
try {
|
|
73
|
+
return (0, raceEthnicity_1.parseRaceEthnicity)(code.trim()).description;
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function tryParseHairColor(code) {
|
|
80
|
+
if (!code)
|
|
81
|
+
return null;
|
|
82
|
+
try {
|
|
83
|
+
return (0, hairColor_1.parseHairColor)(code.trim()).color;
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function tryParseWeightRange(code) {
|
|
90
|
+
if (!code)
|
|
91
|
+
return null;
|
|
92
|
+
try {
|
|
93
|
+
return (0, weightRange_1.parseWeightRange)(code.trim()).description;
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function parseLicenseFromBarcode(barcode) {
|
|
100
|
+
const { header } = barcode;
|
|
101
|
+
// Find the DL or ID subfile
|
|
102
|
+
const dlSubfile = barcode.subfiles.find((s) => s.subfileType === "DL") ??
|
|
103
|
+
barcode.subfiles.find((s) => s.subfileType === "ID");
|
|
104
|
+
const elements = dlSubfile?.elements ?? {};
|
|
105
|
+
// Determine country for date formatting
|
|
106
|
+
let country = get(elements, "DCG") ?? "USA";
|
|
107
|
+
try {
|
|
108
|
+
const authority = (0, issuingAuthority_1.getAuthorityById)(header.issuerId);
|
|
109
|
+
country = authority.country;
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
// Use DCG or default
|
|
113
|
+
}
|
|
114
|
+
// Name parsing with v1/v2 fallbacks
|
|
115
|
+
let firstName;
|
|
116
|
+
let lastName;
|
|
117
|
+
let middleName;
|
|
118
|
+
if (header.aamvaVersion >= 3) {
|
|
119
|
+
firstName = get(elements, "DAC");
|
|
120
|
+
lastName = get(elements, "DCS");
|
|
121
|
+
middleName = get(elements, "DAD");
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
// v1-2: parse from DAA (LAST,FIRST,MIDDLE), fallback to individual fields
|
|
125
|
+
const fullName = get(elements, "DAA");
|
|
126
|
+
if (fullName) {
|
|
127
|
+
const parts = fullName.split(",");
|
|
128
|
+
lastName = parts[0]?.trim() || null;
|
|
129
|
+
firstName = parts[1]?.trim() || null;
|
|
130
|
+
middleName = parts[2]?.trim() || null;
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
firstName = get(elements, "DAC");
|
|
134
|
+
lastName = get(elements, "DCS");
|
|
135
|
+
middleName = get(elements, "DAD");
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// V1/v2 fallback: DAC ?? DCT ?? parsed-from-DAA
|
|
139
|
+
firstName = firstName ?? getOrNull(elements, "DCT");
|
|
140
|
+
// V1 fallback: DCS ?? DAB ?? parsed-from-DAA
|
|
141
|
+
lastName = lastName ?? getOrNull(elements, "DAB");
|
|
142
|
+
// V1 fallback: DAQ ?? DBJ
|
|
143
|
+
const documentNumber = get(elements, "DAQ") ?? getOrNull(elements, "DBJ");
|
|
144
|
+
// Driving privilege with v1 fallbacks
|
|
145
|
+
const vehicleClass = getOrNull(elements, "DCA") ?? getOrNull(elements, "DAR");
|
|
146
|
+
const restrictionCodes = getOrNull(elements, "DCB") ?? getOrNull(elements, "DAS");
|
|
147
|
+
const endorsementCodes = getOrNull(elements, "DCD") ?? getOrNull(elements, "DAT");
|
|
148
|
+
// Alias fallbacks: v2+ IDs ?? v1 IDs
|
|
149
|
+
const lastNameAlias = get(elements, "DBN") ?? getOrNull(elements, "DBO");
|
|
150
|
+
const firstNameAlias = get(elements, "DBG") ?? getOrNull(elements, "DBP");
|
|
151
|
+
// Organ donor fallback: DDK ?? DBH
|
|
152
|
+
const organDonor = get(elements, "DDK") !== undefined
|
|
153
|
+
? parseIndicator(get(elements, "DDK"))
|
|
154
|
+
: parseIndicator(getOrNull(elements, "DBH"));
|
|
155
|
+
// Jurisdiction
|
|
156
|
+
let jurisdiction = null;
|
|
157
|
+
try {
|
|
158
|
+
jurisdiction = (0, issuingAuthority_1.getAuthorityById)(header.issuerId).jurisdiction;
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
// Unknown issuer
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
firstName,
|
|
165
|
+
lastName,
|
|
166
|
+
middleName,
|
|
167
|
+
nameSuffix: get(elements, "DCU"),
|
|
168
|
+
dateOfBirth: tryParseDate(get(elements, "DBB"), header.aamvaVersion, country),
|
|
169
|
+
expirationDate: tryParseDate(get(elements, "DBA"), header.aamvaVersion, country),
|
|
170
|
+
issueDate: tryParseDate(get(elements, "DBD"), header.aamvaVersion, country),
|
|
171
|
+
streetAddress: get(elements, "DAG"),
|
|
172
|
+
city: get(elements, "DAI"),
|
|
173
|
+
state: get(elements, "DAJ"),
|
|
174
|
+
postalCode: get(elements, "DAK")?.trim() ?? null,
|
|
175
|
+
country: get(elements, "DCG"),
|
|
176
|
+
documentNumber,
|
|
177
|
+
documentDiscriminator: get(elements, "DCF"),
|
|
178
|
+
sex: parseSex(get(elements, "DBC"), header.aamvaVersion),
|
|
179
|
+
height: get(elements, "DAU"),
|
|
180
|
+
weight: get(elements, "DAW"),
|
|
181
|
+
eyeColor: tryParseEyeColor(get(elements, "DAY")),
|
|
182
|
+
hairColor: tryParseHairColor(get(elements, "DAZ")),
|
|
183
|
+
streetAddress2: get(elements, "DAH"),
|
|
184
|
+
complianceType: get(elements, "DDA"),
|
|
185
|
+
cardRevisionDate: tryParseDate(get(elements, "DDB"), header.aamvaVersion, country),
|
|
186
|
+
limitedDurationDocument: parseIndicator(get(elements, "DDD")),
|
|
187
|
+
organDonor,
|
|
188
|
+
veteran: parseIndicator(get(elements, "DDL")),
|
|
189
|
+
firstNameTruncation: get(elements, "DDF"),
|
|
190
|
+
middleNameTruncation: get(elements, "DDG"),
|
|
191
|
+
lastNameTruncation: get(elements, "DDE"),
|
|
192
|
+
placeOfBirth: get(elements, "DCI"),
|
|
193
|
+
auditInformation: get(elements, "DCJ"),
|
|
194
|
+
inventoryControlNumber: get(elements, "DCK"),
|
|
195
|
+
lastNameAlias,
|
|
196
|
+
firstNameAlias,
|
|
197
|
+
suffixAlias: get(elements, "DBS"),
|
|
198
|
+
raceEthnicity: tryParseRaceEthnicity(get(elements, "DCL")),
|
|
199
|
+
hazmatEndorsementExpiration: tryParseDate(get(elements, "DDC"), header.aamvaVersion, country),
|
|
200
|
+
cdlIndicator: parseIndicator(get(elements, "DDM")),
|
|
201
|
+
nonDomiciledIndicator: parseIndicator(get(elements, "DDN")),
|
|
202
|
+
enhancedDocumentIndicator: parseIndicator(get(elements, "DDO")),
|
|
203
|
+
permitIndicator: parseIndicator(get(elements, "DDP")),
|
|
204
|
+
// Driving privilege (DCA/DCB/DCD with v1 fallbacks)
|
|
205
|
+
vehicleClass,
|
|
206
|
+
restrictionCodes,
|
|
207
|
+
endorsementCodes,
|
|
208
|
+
// Physical description alternatives
|
|
209
|
+
heightCm: getOrNull(elements, "DAV"),
|
|
210
|
+
weightKg: getOrNull(elements, "DAX"),
|
|
211
|
+
weightRange: tryParseWeightRange(get(elements, "DCE")),
|
|
212
|
+
// Standard classification descriptions
|
|
213
|
+
federalCommercialVehicleCodes: get(elements, "DCH"),
|
|
214
|
+
standardVehicleClassification: get(elements, "DCM"),
|
|
215
|
+
standardEndorsementCode: get(elements, "DCN"),
|
|
216
|
+
standardRestrictionCode: get(elements, "DCO"),
|
|
217
|
+
vehicleClassDescription: get(elements, "DCP"),
|
|
218
|
+
endorsementCodeDescription: get(elements, "DCQ"),
|
|
219
|
+
restrictionCodeDescription: get(elements, "DCR"),
|
|
220
|
+
// Name fields (v1/v2-3 legacy)
|
|
221
|
+
namePrefix: getOrNull(elements, "DAF"),
|
|
222
|
+
firstNameV2: get(elements, "DCT"),
|
|
223
|
+
// V1 legacy name fields
|
|
224
|
+
lastNameV1: getOrNull(elements, "DAB"),
|
|
225
|
+
nameSuffixV1: getOrNull(elements, "DAE"),
|
|
226
|
+
// V1 driving privilege (raw v1 values)
|
|
227
|
+
classificationCodeV1: getOrNull(elements, "DAR"),
|
|
228
|
+
restrictionCodeV1: getOrNull(elements, "DAS"),
|
|
229
|
+
endorsementCodeV1: getOrNull(elements, "DAT"),
|
|
230
|
+
// Residence address (v1)
|
|
231
|
+
residenceStreetAddress: get(elements, "DAL"),
|
|
232
|
+
residenceStreetAddress2: get(elements, "DAM"),
|
|
233
|
+
residenceCity: get(elements, "DAN"),
|
|
234
|
+
residenceJurisdictionCode: get(elements, "DAO"),
|
|
235
|
+
residencePostalCode: get(elements, "DAP"),
|
|
236
|
+
// Document/administrative
|
|
237
|
+
issueTimestamp: get(elements, "DBE"),
|
|
238
|
+
numberOfDuplicates: get(elements, "DBF"),
|
|
239
|
+
organDonorLegacy: getOrNull(elements, "DBH"),
|
|
240
|
+
nonResidentIndicator: get(elements, "DBI"),
|
|
241
|
+
uniqueCustomerId: getOrNull(elements, "DBJ"),
|
|
242
|
+
socialSecurityNumber: get(elements, "DBK"),
|
|
243
|
+
akaDateOfBirth: tryParseDate(get(elements, "DBL"), header.aamvaVersion, country),
|
|
244
|
+
akaSocialSecurityNumber: get(elements, "DBM"),
|
|
245
|
+
// AKA (v1 element IDs)
|
|
246
|
+
akaLastNameV1: getOrNull(elements, "DBO"),
|
|
247
|
+
akaFirstNameV1: getOrNull(elements, "DBP"),
|
|
248
|
+
akaMiddleName: getOrNull(elements, "DBQ"),
|
|
249
|
+
akaSuffixV1: getOrNull(elements, "DBR"),
|
|
250
|
+
// Age milestone dates
|
|
251
|
+
under18Until: tryParseDate(get(elements, "DDH"), header.aamvaVersion, country),
|
|
252
|
+
under19Until: tryParseDate(get(elements, "DDI"), header.aamvaVersion, country),
|
|
253
|
+
under21Until: tryParseDate(get(elements, "DDJ"), header.aamvaVersion, country),
|
|
254
|
+
// Raw element access
|
|
255
|
+
raw: { ...elements },
|
|
256
|
+
issuerId: header.issuerId,
|
|
257
|
+
aamvaVersion: header.aamvaVersion,
|
|
258
|
+
jurisdiction,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
function parseLicense(barcodeString) {
|
|
262
|
+
return parseLicenseFromBarcode((0, barcode_1.parseBarcodeString)(barcodeString));
|
|
263
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RACE_ETHNICITIES = void 0;
|
|
4
|
+
exports.parseRaceEthnicity = parseRaceEthnicity;
|
|
5
|
+
exports.RACE_ETHNICITIES = [
|
|
6
|
+
{ code: "AI", description: "Alaskan or American Indian" },
|
|
7
|
+
{ code: "AP", description: "Asian or Pacific Islander" },
|
|
8
|
+
{ code: "BK", description: "Black" },
|
|
9
|
+
{ code: "H", description: "Hispanic Origin" },
|
|
10
|
+
{ code: "O", description: "Non-hispanic" },
|
|
11
|
+
{ code: "U", description: "Unknown" },
|
|
12
|
+
{ code: "W", description: "White" },
|
|
13
|
+
];
|
|
14
|
+
function parseRaceEthnicity(code) {
|
|
15
|
+
const found = exports.RACE_ETHNICITIES.find((r) => r.code === code);
|
|
16
|
+
if (!found) {
|
|
17
|
+
throw new Error(`Race/Ethnicity code '${code}' not found.`);
|
|
18
|
+
}
|
|
19
|
+
return found;
|
|
20
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WEIGHT_RANGES = void 0;
|
|
4
|
+
exports.parseWeightRange = parseWeightRange;
|
|
5
|
+
exports.WEIGHT_RANGES = [
|
|
6
|
+
{ code: "0", description: "Up to 70 lbs (31 kg)", lbs: "0-70", kg: "0-31" },
|
|
7
|
+
{ code: "1", description: "71-100 lbs (32-45 kg)", lbs: "71-100", kg: "32-45" },
|
|
8
|
+
{ code: "2", description: "101-130 lbs (46-59 kg)", lbs: "101-130", kg: "46-59" },
|
|
9
|
+
{ code: "3", description: "131-160 lbs (60-70 kg)", lbs: "131-160", kg: "60-70" },
|
|
10
|
+
{ code: "4", description: "161-190 lbs (71-86 kg)", lbs: "161-190", kg: "71-86" },
|
|
11
|
+
{ code: "5", description: "191-220 lbs (87-100 kg)", lbs: "191-220", kg: "87-100" },
|
|
12
|
+
{ code: "6", description: "221-250 lbs (101-113 kg)", lbs: "221-250", kg: "101-113" },
|
|
13
|
+
{ code: "7", description: "251-280 lbs (114-127 kg)", lbs: "251-280", kg: "114-127" },
|
|
14
|
+
{ code: "8", description: "281-320 lbs (128-145 kg)", lbs: "281-320", kg: "128-145" },
|
|
15
|
+
{ code: "9", description: "Over 320 lbs (145+ kg)", lbs: "321+", kg: "146+" },
|
|
16
|
+
];
|
|
17
|
+
function parseWeightRange(code) {
|
|
18
|
+
const found = exports.WEIGHT_RANGES.find((w) => w.code === code);
|
|
19
|
+
if (!found) {
|
|
20
|
+
throw new Error(`Weight range code '${code}' not found.`);
|
|
21
|
+
}
|
|
22
|
+
return found;
|
|
23
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "aamva-decoder",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Parse PDF417 barcode data from North American driver's licenses (AAMVA standard)",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"test": "vitest run",
|
|
13
|
+
"prepublishOnly": "npm run build && npm test"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"aamva",
|
|
17
|
+
"barcode",
|
|
18
|
+
"driver-license",
|
|
19
|
+
"pdf417",
|
|
20
|
+
"react-native"
|
|
21
|
+
],
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"typescript": "^5.4.0",
|
|
25
|
+
"vitest": "^1.6.0"
|
|
26
|
+
}
|
|
27
|
+
}
|