@jrrembert/luhnjs 0.0.1-rc2 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +18 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +10 -0
- package/dist/src/luhn.d.ts +52 -0
- package/dist/src/luhn.js +198 -0
- package/package.json +7 -5
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1 +1,18 @@
|
|
|
1
|
-
# luhnjs
|
|
1
|
+
# luhnjs
|
|
2
|
+
|
|
3
|
+
[](https://github.com/jrrembert/luhnjs/actions/workflows/node.js.yml)
|
|
4
|
+
[](https://codecov.io/gh/jrrembert/luhnjs)
|
|
5
|
+
|
|
6
|
+
A TypeScript implementation of the Luhn algorithm for generating and validating checksums.
|
|
7
|
+
|
|
8
|
+
## Documentation
|
|
9
|
+
|
|
10
|
+
- [Continuous Integration](docs/CI.md) - CI/CD workflows and troubleshooting
|
|
11
|
+
- [Release and Build Process](docs/RELEASE.md) - Guide for building and publishing releases
|
|
12
|
+
- [Specification](docs/SPEC.md) - Cross-language port alignment and API specification
|
|
13
|
+
|
|
14
|
+
## License
|
|
15
|
+
|
|
16
|
+
[MIT](LICENSE)
|
|
17
|
+
|
|
18
|
+
Copyright © 2022-2026 J. Ryan Rembert
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { generate, validate, random, generateModN, validateModN, checksumModN } from './src/luhn';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.checksumModN = exports.validateModN = exports.generateModN = exports.random = exports.validate = exports.generate = void 0;
|
|
4
|
+
var luhn_1 = require("./src/luhn");
|
|
5
|
+
Object.defineProperty(exports, "generate", { enumerable: true, get: function () { return luhn_1.generate; } });
|
|
6
|
+
Object.defineProperty(exports, "validate", { enumerable: true, get: function () { return luhn_1.validate; } });
|
|
7
|
+
Object.defineProperty(exports, "random", { enumerable: true, get: function () { return luhn_1.random; } });
|
|
8
|
+
Object.defineProperty(exports, "generateModN", { enumerable: true, get: function () { return luhn_1.generateModN; } });
|
|
9
|
+
Object.defineProperty(exports, "validateModN", { enumerable: true, get: function () { return luhn_1.validateModN; } });
|
|
10
|
+
Object.defineProperty(exports, "checksumModN", { enumerable: true, get: function () { return luhn_1.checksumModN; } });
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
declare class GenerateOptions {
|
|
2
|
+
checkSumOnly: boolean;
|
|
3
|
+
constructor();
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Calculate and append Luhn algorithm checksum to a given value
|
|
7
|
+
*
|
|
8
|
+
* @param value string of digits to generate checksum for
|
|
9
|
+
* @param options.checkSumOnly if true, returns only the checksum digit; if false, returns value with checksum appended
|
|
10
|
+
* @returns string containing either the checksum digit alone or the input value with checksum appended
|
|
11
|
+
*/
|
|
12
|
+
export declare function generate(value: string, options?: GenerateOptions): string;
|
|
13
|
+
/**
|
|
14
|
+
* Determine if the Luhn checksum for a given number is correct
|
|
15
|
+
*
|
|
16
|
+
* @param value string containing digits with a Luhn checksum as the last digit
|
|
17
|
+
* @returns boolean indicating whether the checksum is valid
|
|
18
|
+
*/
|
|
19
|
+
export declare function validate(value: string): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Generate a random number with valid Luhn checksum
|
|
22
|
+
*
|
|
23
|
+
* @param length string containing the desired length of the generated number
|
|
24
|
+
* @returns string containing random digits with valid Luhn checksum
|
|
25
|
+
*/
|
|
26
|
+
export declare function random(length: string): string;
|
|
27
|
+
/**
|
|
28
|
+
* Calculate and append a Luhn mod-N checksum to a numeric string
|
|
29
|
+
*
|
|
30
|
+
* @param value - Numeric string to generate checksum for
|
|
31
|
+
* @param n - Modulus (base) between 1 and 36
|
|
32
|
+
* @param options.checkSumOnly - If true, returns only the checksum character
|
|
33
|
+
* @returns String containing either the checksum character alone or input with checksum appended
|
|
34
|
+
*/
|
|
35
|
+
export declare function generateModN(value: string, n: number, options?: GenerateOptions): string;
|
|
36
|
+
/**
|
|
37
|
+
* Determine if the Luhn mod-N checksum for a given value is correct
|
|
38
|
+
*
|
|
39
|
+
* @param value - Numeric string where the last character is the check character
|
|
40
|
+
* @param n - Modulus (base) between 1 and 36
|
|
41
|
+
* @returns boolean indicating whether the checksum is valid
|
|
42
|
+
*/
|
|
43
|
+
export declare function validateModN(value: string, n: number): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Calculate Luhn mod-N check digit for an alphanumeric string
|
|
46
|
+
*
|
|
47
|
+
* @param value - Alphanumeric string to calculate check digit for
|
|
48
|
+
* @param n - Modulus (base) to use for the algorithm
|
|
49
|
+
* @returns Check digit as a number
|
|
50
|
+
*/
|
|
51
|
+
export declare function checksumModN(value: string, n: number): number;
|
|
52
|
+
export {};
|
package/dist/src/luhn.js
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.checksumModN = exports.validateModN = exports.generateModN = exports.random = exports.validate = exports.generate = void 0;
|
|
4
|
+
const CODE_POINTS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
|
5
|
+
class GenerateOptions {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.checkSumOnly = false;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Validates that input is a non-empty string that can be converted to a number
|
|
12
|
+
*
|
|
13
|
+
* @param value - String to validate
|
|
14
|
+
* @throws {Error} If value is not a string, is empty, or cannot be converted to a number
|
|
15
|
+
*/
|
|
16
|
+
function handleErrors(value) {
|
|
17
|
+
if (typeof value !== 'string') {
|
|
18
|
+
throw new Error(`value must be a string - received ${value}`);
|
|
19
|
+
}
|
|
20
|
+
if (!value.length) {
|
|
21
|
+
throw new Error('string cannot be empty');
|
|
22
|
+
}
|
|
23
|
+
if (value.includes(' ')) {
|
|
24
|
+
throw new Error('string cannot contain spaces');
|
|
25
|
+
}
|
|
26
|
+
if (value.includes('-')) {
|
|
27
|
+
throw new Error('negative numbers are not allowed');
|
|
28
|
+
}
|
|
29
|
+
if (value.includes('.')) {
|
|
30
|
+
throw new Error('floating point numbers are not allowed');
|
|
31
|
+
}
|
|
32
|
+
if (isNaN(+value)) {
|
|
33
|
+
throw new Error('string must be convertible to a number');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Generates a Luhn algorithm checksum digit for a string of numbers
|
|
38
|
+
*
|
|
39
|
+
* @param value - String of digits to generate checksum for
|
|
40
|
+
* @returns Single digit checksum value (0-9)
|
|
41
|
+
*/
|
|
42
|
+
function generateCheckSum(value) {
|
|
43
|
+
// convert to array
|
|
44
|
+
const toArray = Array.from(value);
|
|
45
|
+
// if double is `true`, multiply digit by 2
|
|
46
|
+
let double = true;
|
|
47
|
+
// starting from right, iterate through each value multiplying every 2nd digit by 2
|
|
48
|
+
const sum = toArray.reduceRight((prev, current) => {
|
|
49
|
+
if (double) {
|
|
50
|
+
double = false;
|
|
51
|
+
const temp = parseInt(current) * 2;
|
|
52
|
+
// if value is greater than or equal to 10, sum each digit of value ie. if value is 15, use 1 + 5 to get value
|
|
53
|
+
if (temp >= 10) {
|
|
54
|
+
return prev + Array.from(temp.toString()).reduce((prev, current) => {
|
|
55
|
+
return prev + parseInt(current);
|
|
56
|
+
}, 0);
|
|
57
|
+
}
|
|
58
|
+
return prev + parseInt(current) * 2;
|
|
59
|
+
}
|
|
60
|
+
double = true;
|
|
61
|
+
return prev + parseInt(current);
|
|
62
|
+
}, 0);
|
|
63
|
+
return (10 - (sum % 10)) % 10;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Calculate and append Luhn algorithm checksum to a given value
|
|
67
|
+
*
|
|
68
|
+
* @param value string of digits to generate checksum for
|
|
69
|
+
* @param options.checkSumOnly if true, returns only the checksum digit; if false, returns value with checksum appended
|
|
70
|
+
* @returns string containing either the checksum digit alone or the input value with checksum appended
|
|
71
|
+
*/
|
|
72
|
+
function generate(value, options) {
|
|
73
|
+
handleErrors(value);
|
|
74
|
+
const checkSum = generateCheckSum(value).toString();
|
|
75
|
+
return (options === null || options === void 0 ? void 0 : options.checkSumOnly) ? checkSum : value.concat(checkSum);
|
|
76
|
+
}
|
|
77
|
+
exports.generate = generate;
|
|
78
|
+
/**
|
|
79
|
+
* Determine if the Luhn checksum for a given number is correct
|
|
80
|
+
*
|
|
81
|
+
* @param value string containing digits with a Luhn checksum as the last digit
|
|
82
|
+
* @returns boolean indicating whether the checksum is valid
|
|
83
|
+
*/
|
|
84
|
+
function validate(value) {
|
|
85
|
+
handleErrors(value);
|
|
86
|
+
if (value.length === 1) {
|
|
87
|
+
throw new Error('string must be longer than 1 character');
|
|
88
|
+
}
|
|
89
|
+
const valueWithoutCheckSum = value.substring(0, value.length - 1);
|
|
90
|
+
return value === generate(valueWithoutCheckSum);
|
|
91
|
+
}
|
|
92
|
+
exports.validate = validate;
|
|
93
|
+
/**
|
|
94
|
+
* Generate a random number with valid Luhn checksum
|
|
95
|
+
*
|
|
96
|
+
* @param length string containing the desired length of the generated number
|
|
97
|
+
* @returns string containing random digits with valid Luhn checksum
|
|
98
|
+
*/
|
|
99
|
+
function random(length) {
|
|
100
|
+
handleErrors(length);
|
|
101
|
+
const lengthAsInteger = parseInt(length);
|
|
102
|
+
if (lengthAsInteger > 100) {
|
|
103
|
+
throw new Error('string must be less than 100 characters');
|
|
104
|
+
}
|
|
105
|
+
if (lengthAsInteger < 2) {
|
|
106
|
+
throw new Error('string must be greater than 1');
|
|
107
|
+
}
|
|
108
|
+
const random = Array.from({ length: lengthAsInteger - 1 }, (_, index) => {
|
|
109
|
+
// Ensure the first digit is not zero
|
|
110
|
+
if (index === 0) {
|
|
111
|
+
return Math.floor(Math.random() * 9) + 1; // 1 to 9
|
|
112
|
+
}
|
|
113
|
+
return Math.floor(Math.random() * 10);
|
|
114
|
+
}).join('');
|
|
115
|
+
return generate(random);
|
|
116
|
+
}
|
|
117
|
+
exports.random = random;
|
|
118
|
+
/**
|
|
119
|
+
* Calculate and append a Luhn mod-N checksum to a numeric string
|
|
120
|
+
*
|
|
121
|
+
* @param value - Numeric string to generate checksum for
|
|
122
|
+
* @param n - Modulus (base) between 1 and 36
|
|
123
|
+
* @param options.checkSumOnly - If true, returns only the checksum character
|
|
124
|
+
* @returns String containing either the checksum character alone or input with checksum appended
|
|
125
|
+
*/
|
|
126
|
+
function generateModN(value, n, options) {
|
|
127
|
+
handleErrors(value);
|
|
128
|
+
if (n < 1 || n > 36) {
|
|
129
|
+
throw new Error('n must be between 1 and 36');
|
|
130
|
+
}
|
|
131
|
+
const checkSum = checksumModN(value, n);
|
|
132
|
+
const checkChar = CODE_POINTS[checkSum];
|
|
133
|
+
return (options === null || options === void 0 ? void 0 : options.checkSumOnly) ? checkChar : value.concat(checkChar);
|
|
134
|
+
}
|
|
135
|
+
exports.generateModN = generateModN;
|
|
136
|
+
/**
|
|
137
|
+
* Determine if the Luhn mod-N checksum for a given value is correct
|
|
138
|
+
*
|
|
139
|
+
* @param value - Numeric string where the last character is the check character
|
|
140
|
+
* @param n - Modulus (base) between 1 and 36
|
|
141
|
+
* @returns boolean indicating whether the checksum is valid
|
|
142
|
+
*/
|
|
143
|
+
function validateModN(value, n) {
|
|
144
|
+
if (typeof value !== 'string') {
|
|
145
|
+
throw new Error(`value must be a string - received ${value}`);
|
|
146
|
+
}
|
|
147
|
+
if (!value.length) {
|
|
148
|
+
throw new Error('string cannot be empty');
|
|
149
|
+
}
|
|
150
|
+
if (value.length === 1) {
|
|
151
|
+
throw new Error('string must be longer than 1 character');
|
|
152
|
+
}
|
|
153
|
+
if (n < 1 || n > 36) {
|
|
154
|
+
throw new Error('n must be between 1 and 36');
|
|
155
|
+
}
|
|
156
|
+
const valueWithoutCheckSum = value.substring(0, value.length - 1);
|
|
157
|
+
return value === generateModN(valueWithoutCheckSum, n);
|
|
158
|
+
}
|
|
159
|
+
exports.validateModN = validateModN;
|
|
160
|
+
/**
|
|
161
|
+
* Convert a character to its code point index (0-9, A-Z, case-insensitive)
|
|
162
|
+
*/
|
|
163
|
+
function charToInt(char) {
|
|
164
|
+
const index = CODE_POINTS.indexOf(char.toUpperCase());
|
|
165
|
+
if (index === -1) {
|
|
166
|
+
throw new Error(`Invalid character: ${char}`);
|
|
167
|
+
}
|
|
168
|
+
return index;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Calculate Luhn mod-N check digit for an alphanumeric string
|
|
172
|
+
*
|
|
173
|
+
* @param value - Alphanumeric string to calculate check digit for
|
|
174
|
+
* @param n - Modulus (base) to use for the algorithm
|
|
175
|
+
* @returns Check digit as a number
|
|
176
|
+
*/
|
|
177
|
+
function checksumModN(value, n) {
|
|
178
|
+
if (typeof value !== 'string') {
|
|
179
|
+
throw new Error(`value must be a string - received ${value}`);
|
|
180
|
+
}
|
|
181
|
+
if (!value.length) {
|
|
182
|
+
throw new Error('string cannot be empty');
|
|
183
|
+
}
|
|
184
|
+
if (n < 1 || n > 36) {
|
|
185
|
+
throw new Error('n must be between 1 and 36');
|
|
186
|
+
}
|
|
187
|
+
const chars = Array.from(value);
|
|
188
|
+
let factor = 2;
|
|
189
|
+
let sum = 0;
|
|
190
|
+
for (let i = chars.length - 1; i >= 0; i--) {
|
|
191
|
+
let addend = charToInt(chars[i]) * factor;
|
|
192
|
+
addend = Math.floor(addend / n) + (addend % n);
|
|
193
|
+
sum += addend;
|
|
194
|
+
factor = factor === 2 ? 1 : 2;
|
|
195
|
+
}
|
|
196
|
+
return (n - (sum % n)) % n;
|
|
197
|
+
}
|
|
198
|
+
exports.checksumModN = checksumModN;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jrrembert/luhnjs",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"files": [
|
|
@@ -12,16 +12,18 @@
|
|
|
12
12
|
"scripts": {
|
|
13
13
|
"build": "tsc && node dist/index.js",
|
|
14
14
|
"lint": "eslint .",
|
|
15
|
-
"test": "jest"
|
|
15
|
+
"test": "jest",
|
|
16
|
+
"prepublishOnly": "yarn lint && yarn test && yarn build"
|
|
16
17
|
},
|
|
17
18
|
"devDependencies": {
|
|
18
19
|
"@types/jest": "^29.2.3",
|
|
19
20
|
"@typescript-eslint/eslint-plugin": "^5.44.0",
|
|
20
|
-
"@typescript-eslint/parser": "^5.
|
|
21
|
+
"@typescript-eslint/parser": "^5.62.0",
|
|
21
22
|
"eslint": "^8.28.0",
|
|
22
23
|
"jest": "^29.3.1",
|
|
23
|
-
"ts-jest": "^29.
|
|
24
|
-
"ts-node": "^10.9.
|
|
24
|
+
"ts-jest": "^29.4.6",
|
|
25
|
+
"ts-node": "^10.9.2",
|
|
26
|
+
"semantic-release": "^24.0.0",
|
|
25
27
|
"typescript": "^4.9.3"
|
|
26
28
|
}
|
|
27
29
|
}
|