Migrating from 2.x
@evanion/luhn 3.0.0 and its patch releases up to 3.0.2, the version on npm. Values on this page were produced by running the source at f9b1d93, a later commit than the 3.0.0 tag, and are not re-executed.
@evanion/luhn 3.x introduces breaking changes to the default dictionary and the configuration API. Most notably, the default dictionary has shrunk from 62 characters to 36, which means codes produced by 2.x will not validate under 3.x by default.
Handling Legacy Codes
If your system has already issued codes using version 2.x, you must decide how to handle them before updating. You have three primary options:
- Reissue: If codes are internal or short-lived, simply reissue them using 3.x.
- Legacy Support: Maintain a legacy instance to validate 2.x codes.
- Transition Period: Validate against both 2.x and 3.x instances, and reissue the code to the 3.x format when it is first read.
The 2.x default lowercased input but used a 62-character dictionary that included both cases. You can reproduce 2.x check characters using ALTERNATING_CASE_DICTIONARY combined with lowercased input:
const = ({ : });
function (: string): string {
return .(.()).;
}
function (: string): boolean {
const = .(0, -1).();
return .( + .(-1)).;
}
('order-2026-0042'); // -> 'E'
('order-2026-0042-E'); // -> true
('order-2026-0043-E'); // -> false
.('order-2026-0042-E').; // -> falseAPI Mapping: 2.x 3.x
The 2.x API relied on mutable static properties on a class. 3.x replaces this with frozen instances created via createLuhn.
| 2.x Call / Property | 3.x Equivalent | Notes |
|---|---|---|
Luhn.generate(input) | Luhn.generate(input) | Call is the same; output value is different. |
Luhn.validate(input) | Luhn.validate(input) | Call is the same; now accepts 3.x codes. |
Luhn.generate(input, true) | createLuhn({ caseInsensitive: false }).generate(input) | Disables input folding. |
Luhn.validate(input, true) | createLuhn({ caseInsensitive: false }).validate(input) | Disables input folding. |
class X extends Luhn { static dictionary = d } | createLuhn({ dictionary: d, caseInsensitive: true }) | Same values as the legacy subclass. |
class X extends Luhn { static sensitive = true } | createLuhn({ dictionary: ALTERNATING_CASE_DICTIONARY }) | Reproduces case-sensitive 2.x behavior. |
Luhn.dictionary = d | The default instance is now frozen. | |
Luhn.sensitive | !Luhn.caseInsensitive | The flag’s meaning is now inverted. |
ValidationError | LuhnError | Base class for EmptyInputError and InvalidDictionaryError. |
validate('') true | false | Empty strings are no longer considered valid. |
generate('') {'0'} | EmptyInputError | Generating a checksum for nothing is now an error. |
Why the Architecture Changed
From Mutable Statics to Frozen Instances
Version 2.x was configured through mutable statics, which meant dictionaries were never checked before use. This led to runtime crashes (e.g., TypeError: dictionary.indexOf is not a function) if a dictionary was misconfigured.
createLuhn now validates the dictionary immediately upon instance creation. Once built, the instance is frozen, ensuring that the checksum logic remains consistent.
let : unknown;
try {
( as { : string }). = '0123456789';
} catch () {
= ;
}
instanceof ; // -> true
.; // -> '0123456789abcdefghijklmnopqrstuvwxyz'The Alphabet Shrinkage
The 2.x default alphabet held case pairs (both A and a). However, the library was documented as case-insensitive. This was a contradiction: if you fold A to a, the index originally held by A becomes unreachable.
The 3.x default consists of 36 lowercase alphanumerics. This is a sound basis for a case-insensitive default. If you need the full 62-character set, use ALTERNATING_CASE_DICTIONARY.
Transitioning from Subclasses to Instances
In 2.x, you created a subclass for every extra dictionary. In 3.x, you simply create a new instance. Since createLuhn builds a lookup table on every call, you should cache these instances if they recur. A luhn dictionary chosen at runtime shows the recommended caching pattern.