@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2022 J. Ryan Rembert
3
+ Copyright (c) 2022-2026 J. Ryan Rembert
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1 +1,18 @@
1
- # luhnjs
1
+ # luhnjs
2
+
3
+ [![Node.js CI](https://github.com/jrrembert/luhnjs/actions/workflows/node.js.yml/badge.svg)](https://github.com/jrrembert/luhnjs/actions/workflows/node.js.yml)
4
+ [![codecov](https://codecov.io/gh/jrrembert/luhnjs/graph/badge.svg)](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
@@ -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 {};
@@ -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.1-rc2",
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.44.0",
21
+ "@typescript-eslint/parser": "^5.62.0",
21
22
  "eslint": "^8.28.0",
22
23
  "jest": "^29.3.1",
23
- "ts-jest": "^29.0.3",
24
- "ts-node": "^10.9.1",
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
  }