@ibiliaze/stringman 3.8.0 → 3.10.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/dist/index.d.ts +4 -3
- package/dist/ticket.d.ts +18 -5
- package/dist/ticket.js +83 -44
- package/package.json +2 -2
- package/src/ticket.ts +81 -40
package/dist/index.d.ts
CHANGED
|
@@ -155,9 +155,10 @@ export declare const ticket: {
|
|
|
155
155
|
row: number;
|
|
156
156
|
col: number;
|
|
157
157
|
rndLen?: number;
|
|
158
|
+
dashed?: boolean;
|
|
158
159
|
}) => string;
|
|
159
160
|
validateTicketCode: (code: string) => boolean;
|
|
160
|
-
rndBlock:
|
|
161
|
-
fixtureBlock:
|
|
162
|
-
seatBlock:
|
|
161
|
+
rndBlock: any;
|
|
162
|
+
fixtureBlock: any;
|
|
163
|
+
seatBlock: any;
|
|
163
164
|
};
|
package/dist/ticket.d.ts
CHANGED
|
@@ -1,17 +1,30 @@
|
|
|
1
|
-
export declare const
|
|
2
|
-
export declare const
|
|
3
|
-
export declare const
|
|
4
|
-
export declare const
|
|
1
|
+
export declare const luhn10: (digits: string) => string;
|
|
2
|
+
export declare const venueBlockNum: (venueCode: string, len?: number) => string;
|
|
3
|
+
export declare const seatBlockNum: (sectionId: string, row: number, col: number, len?: number) => string;
|
|
4
|
+
export declare const fixtureBlockNum: (fixtureLocalIso: string, len?: number) => string;
|
|
5
|
+
export declare const rndBlockNum: (len?: number) => string;
|
|
6
|
+
export declare const buildTicketCodeNumeric: (args: {
|
|
5
7
|
venueCode: string;
|
|
6
8
|
fixtureLocalIso: string;
|
|
7
9
|
sectionId: string;
|
|
8
10
|
row: number;
|
|
9
11
|
col: number;
|
|
10
12
|
rndLen?: number;
|
|
13
|
+
dashed?: boolean;
|
|
11
14
|
}) => string;
|
|
12
|
-
export declare const
|
|
15
|
+
export declare const validateTicketCodeNumeric: (code: string) => boolean;
|
|
13
16
|
export declare const getTicketId: ({ fixtureId, sectionName, seatName, }: {
|
|
14
17
|
fixtureId: string;
|
|
15
18
|
sectionName: string;
|
|
16
19
|
seatName: string;
|
|
17
20
|
}) => string;
|
|
21
|
+
export declare const buildTicketCode: (args: {
|
|
22
|
+
venueCode: string;
|
|
23
|
+
fixtureLocalIso: string;
|
|
24
|
+
sectionId: string;
|
|
25
|
+
row: number;
|
|
26
|
+
col: number;
|
|
27
|
+
rndLen?: number;
|
|
28
|
+
dashed?: boolean;
|
|
29
|
+
}) => string;
|
|
30
|
+
export declare const validateTicketCode: (code: string) => boolean;
|
package/dist/ticket.js
CHANGED
|
@@ -1,78 +1,117 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getTicketId = exports.
|
|
3
|
+
exports.validateTicketCode = exports.buildTicketCode = exports.getTicketId = exports.validateTicketCodeNumeric = exports.buildTicketCodeNumeric = exports.rndBlockNum = exports.fixtureBlockNum = exports.seatBlockNum = exports.venueBlockNum = exports.luhn10 = void 0;
|
|
4
4
|
const node_crypto_1 = require("node:crypto");
|
|
5
|
-
|
|
6
|
-
const
|
|
5
|
+
// ---------- helpers ----------
|
|
6
|
+
const pad = (n, len) => n.toString().padStart(len, '0');
|
|
7
|
+
// Luhn mod-10 for digits
|
|
8
|
+
const luhn10 = (digits) => {
|
|
9
|
+
let sum = 0;
|
|
10
|
+
let dbl = false;
|
|
11
|
+
for (let i = digits.length - 1; i >= 0; i--) {
|
|
12
|
+
let d = digits.charCodeAt(i) - 48; // '0' -> 48
|
|
13
|
+
if (dbl) {
|
|
14
|
+
d *= 2;
|
|
15
|
+
if (d > 9)
|
|
16
|
+
d -= 9;
|
|
17
|
+
}
|
|
18
|
+
sum += d;
|
|
19
|
+
dbl = !dbl;
|
|
20
|
+
}
|
|
21
|
+
const check = (10 - (sum % 10)) % 10;
|
|
22
|
+
return String(check);
|
|
23
|
+
};
|
|
24
|
+
exports.luhn10 = luhn10;
|
|
25
|
+
// ---------- numeric blocks ----------
|
|
26
|
+
const venueBlockNum = (venueCode, len = 2) => {
|
|
7
27
|
try {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const n = (h[0] << 8) | h[1];
|
|
11
|
-
|
|
12
|
-
return code.slice(-len);
|
|
28
|
+
// Small, stable hash of the code -> 0..(10^len - 1)
|
|
29
|
+
const h = (0, node_crypto_1.createHash)('sha1').update(venueCode.toUpperCase()).digest(); // 20 bytes
|
|
30
|
+
const n = ((h[0] << 8) | h[1]) % 10 ** len;
|
|
31
|
+
return pad(n, len);
|
|
13
32
|
}
|
|
14
|
-
catch
|
|
15
|
-
return '';
|
|
33
|
+
catch {
|
|
34
|
+
return ''.padStart(len, '0');
|
|
16
35
|
}
|
|
17
36
|
};
|
|
18
|
-
exports.
|
|
19
|
-
|
|
20
|
-
const fixtureBlock = (fixtureLocalIso) => {
|
|
37
|
+
exports.venueBlockNum = venueBlockNum;
|
|
38
|
+
const seatBlockNum = (sectionId, row, col, len = 3) => {
|
|
21
39
|
try {
|
|
22
|
-
//
|
|
40
|
+
// Hash -> 16-bit -> modulo 10^len
|
|
41
|
+
const h = (0, node_crypto_1.createHash)('sha1').update(`${sectionId}:${row}:${col}`).digest();
|
|
42
|
+
const n16 = (h[0] << 8) | h[1];
|
|
43
|
+
const n = n16 % 10 ** len;
|
|
44
|
+
return pad(n, len);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return ''.padStart(len, '0');
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
exports.seatBlockNum = seatBlockNum;
|
|
51
|
+
const fixtureBlockNum = (fixtureLocalIso, len = 6) => {
|
|
52
|
+
try {
|
|
53
|
+
// Expect "YYYY-MM-DDThh:mm" (local, no Z).
|
|
23
54
|
const m = fixtureLocalIso.match(/^(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})/);
|
|
24
55
|
if (!m)
|
|
25
56
|
throw new Error('Bad fixtureLocalIso');
|
|
26
57
|
const [_, y, M, d, h, mnt] = m;
|
|
27
|
-
|
|
28
|
-
|
|
58
|
+
// YYMMDDhhmm is 10 digits; compress to len digits via mod 10^len.
|
|
59
|
+
const compact = Number(`${y.slice(2)}${M}${d}${h}${mnt}`); // e.g. 2510312000
|
|
60
|
+
const n = compact % 10 ** len;
|
|
61
|
+
return pad(n, len);
|
|
29
62
|
}
|
|
30
|
-
catch
|
|
31
|
-
return '';
|
|
63
|
+
catch {
|
|
64
|
+
return ''.padStart(len, '0');
|
|
32
65
|
}
|
|
33
66
|
};
|
|
34
|
-
exports.
|
|
35
|
-
|
|
36
|
-
const rndBlock = (len = 3) => {
|
|
67
|
+
exports.fixtureBlockNum = fixtureBlockNum;
|
|
68
|
+
const rndBlockNum = (len = 3) => {
|
|
37
69
|
try {
|
|
38
|
-
//
|
|
39
|
-
const
|
|
40
|
-
return (0,
|
|
70
|
+
// Uniform cryptographic integer in [0, 10^len)
|
|
71
|
+
const max = 10 ** len;
|
|
72
|
+
return pad((0, node_crypto_1.randomInt)(0, max), len);
|
|
41
73
|
}
|
|
42
|
-
catch
|
|
43
|
-
return '';
|
|
74
|
+
catch {
|
|
75
|
+
return ''.padStart(len, '0');
|
|
44
76
|
}
|
|
45
77
|
};
|
|
46
|
-
exports.
|
|
47
|
-
|
|
78
|
+
exports.rndBlockNum = rndBlockNum;
|
|
79
|
+
// ---------- builder & validator (numeric-only) ----------
|
|
80
|
+
const buildTicketCodeNumeric = (args) => {
|
|
48
81
|
try {
|
|
49
|
-
const VV = args.venueCode
|
|
50
|
-
const E = (0, exports.
|
|
51
|
-
const S = (0, exports.
|
|
52
|
-
const R = (0, exports.
|
|
53
|
-
const body = `${VV}${E}${S}${R}`;
|
|
54
|
-
const C = (0,
|
|
55
|
-
|
|
82
|
+
const VV = (0, exports.venueBlockNum)(args.venueCode, 2); // 2 digits
|
|
83
|
+
const E = (0, exports.fixtureBlockNum)(args.fixtureLocalIso, 6); // 6 digits
|
|
84
|
+
const S = (0, exports.seatBlockNum)(args.sectionId, args.row, args.col); // 3 digits
|
|
85
|
+
const R = (0, exports.rndBlockNum)(args.rndLen ?? 3); // 3 digits
|
|
86
|
+
const body = `${VV}${E}${S}${R}`; // 14 digits
|
|
87
|
+
const C = (0, exports.luhn10)(body); // 1 digit
|
|
88
|
+
const numeric = `${body}${C}`; // 15 digits total
|
|
89
|
+
if (args.dashed === false)
|
|
90
|
+
return numeric;
|
|
91
|
+
// Grouping 2-6-3-3-1 for readability
|
|
56
92
|
return `${VV}-${E}-${S}-${R}-${C}`;
|
|
57
93
|
}
|
|
58
|
-
catch
|
|
94
|
+
catch {
|
|
59
95
|
return '';
|
|
60
96
|
}
|
|
61
97
|
};
|
|
62
|
-
exports.
|
|
63
|
-
const
|
|
98
|
+
exports.buildTicketCodeNumeric = buildTicketCodeNumeric;
|
|
99
|
+
const validateTicketCodeNumeric = (code) => {
|
|
64
100
|
try {
|
|
65
|
-
const clean = code.replace(/-/g, '')
|
|
66
|
-
if (
|
|
67
|
-
return false;
|
|
101
|
+
const clean = code.replace(/-/g, '');
|
|
102
|
+
if (!/^\d{15}$/.test(clean))
|
|
103
|
+
return false; // 2+6+3+3+1
|
|
68
104
|
const body = clean.slice(0, -1);
|
|
69
105
|
const check = clean.slice(-1);
|
|
70
|
-
return (0,
|
|
106
|
+
return (0, exports.luhn10)(body) === check;
|
|
71
107
|
}
|
|
72
|
-
catch
|
|
108
|
+
catch {
|
|
73
109
|
return false;
|
|
74
110
|
}
|
|
75
111
|
};
|
|
76
|
-
exports.
|
|
112
|
+
exports.validateTicketCodeNumeric = validateTicketCodeNumeric;
|
|
77
113
|
const getTicketId = ({ fixtureId, sectionName, seatName, }) => `${sectionName}-${seatName}-${fixtureId}`;
|
|
78
114
|
exports.getTicketId = getTicketId;
|
|
115
|
+
// ---------- compatibility shim (if you want same names as before) ----------
|
|
116
|
+
exports.buildTicketCode = exports.buildTicketCodeNumeric;
|
|
117
|
+
exports.validateTicketCode = exports.validateTicketCodeNumeric;
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ibiliaze/stringman",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.10.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"build": "tsc",
|
|
9
9
|
"pub": "npm publish --access public",
|
|
10
|
-
"git": "git add .; git commit -m 'changes'; git tag -a v3.
|
|
10
|
+
"git": "git add .; git commit -m 'changes'; git tag -a v3.10.0 -m 'v3.10.0'; git push origin v3.10.0; git push",
|
|
11
11
|
"push": "npm run build; npm run git; npm run pub"
|
|
12
12
|
},
|
|
13
13
|
"author": "Ibi Hasanli",
|
package/src/ticket.ts
CHANGED
|
@@ -1,73 +1,110 @@
|
|
|
1
|
-
import { createHash,
|
|
2
|
-
import { b36, luhn36 } from '.';
|
|
1
|
+
import { createHash, randomInt } from 'node:crypto';
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
// ---------- helpers ----------
|
|
4
|
+
const pad = (n: number, len: number) => n.toString().padStart(len, '0');
|
|
5
|
+
|
|
6
|
+
// Luhn mod-10 for digits
|
|
7
|
+
export const luhn10 = (digits: string): string => {
|
|
8
|
+
let sum = 0;
|
|
9
|
+
let dbl = false;
|
|
10
|
+
for (let i = digits.length - 1; i >= 0; i--) {
|
|
11
|
+
let d = digits.charCodeAt(i) - 48; // '0' -> 48
|
|
12
|
+
if (dbl) {
|
|
13
|
+
d *= 2;
|
|
14
|
+
if (d > 9) d -= 9;
|
|
15
|
+
}
|
|
16
|
+
sum += d;
|
|
17
|
+
dbl = !dbl;
|
|
18
|
+
}
|
|
19
|
+
const check = (10 - (sum % 10)) % 10;
|
|
20
|
+
return String(check);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// ---------- numeric blocks ----------
|
|
24
|
+
export const venueBlockNum = (venueCode: string, len = 2) => {
|
|
5
25
|
try {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const n = (h[0] << 8) | h[1];
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
26
|
+
// Small, stable hash of the code -> 0..(10^len - 1)
|
|
27
|
+
const h = createHash('sha1').update(venueCode.toUpperCase()).digest(); // 20 bytes
|
|
28
|
+
const n = ((h[0] << 8) | h[1]) % 10 ** len;
|
|
29
|
+
return pad(n, len);
|
|
30
|
+
} catch {
|
|
31
|
+
return ''.padStart(len, '0');
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const seatBlockNum = (sectionId: string, row: number, col: number, len = 3) => {
|
|
36
|
+
try {
|
|
37
|
+
// Hash -> 16-bit -> modulo 10^len
|
|
38
|
+
const h = createHash('sha1').update(`${sectionId}:${row}:${col}`).digest();
|
|
39
|
+
const n16 = (h[0] << 8) | h[1];
|
|
40
|
+
const n = n16 % 10 ** len;
|
|
41
|
+
return pad(n, len);
|
|
42
|
+
} catch {
|
|
43
|
+
return ''.padStart(len, '0');
|
|
13
44
|
}
|
|
14
45
|
};
|
|
15
46
|
|
|
16
|
-
|
|
17
|
-
export const fixtureBlock = (fixtureLocalIso: string) => {
|
|
47
|
+
export const fixtureBlockNum = (fixtureLocalIso: string, len = 6) => {
|
|
18
48
|
try {
|
|
19
|
-
//
|
|
49
|
+
// Expect "YYYY-MM-DDThh:mm" (local, no Z).
|
|
20
50
|
const m = fixtureLocalIso.match(/^(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})/);
|
|
21
51
|
if (!m) throw new Error('Bad fixtureLocalIso');
|
|
22
52
|
const [_, y, M, d, h, mnt] = m;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
return
|
|
53
|
+
// YYMMDDhhmm is 10 digits; compress to len digits via mod 10^len.
|
|
54
|
+
const compact = Number(`${y.slice(2)}${M}${d}${h}${mnt}`); // e.g. 2510312000
|
|
55
|
+
const n = compact % 10 ** len;
|
|
56
|
+
return pad(n, len);
|
|
57
|
+
} catch {
|
|
58
|
+
return ''.padStart(len, '0');
|
|
27
59
|
}
|
|
28
60
|
};
|
|
29
61
|
|
|
30
|
-
|
|
31
|
-
export const rndBlock = (len = 3) => {
|
|
62
|
+
export const rndBlockNum = (len = 3) => {
|
|
32
63
|
try {
|
|
33
|
-
//
|
|
34
|
-
const
|
|
35
|
-
return
|
|
36
|
-
} catch
|
|
37
|
-
return '';
|
|
64
|
+
// Uniform cryptographic integer in [0, 10^len)
|
|
65
|
+
const max = 10 ** len;
|
|
66
|
+
return pad(randomInt(0, max), len);
|
|
67
|
+
} catch {
|
|
68
|
+
return ''.padStart(len, '0');
|
|
38
69
|
}
|
|
39
70
|
};
|
|
40
71
|
|
|
41
|
-
|
|
42
|
-
|
|
72
|
+
// ---------- builder & validator (numeric-only) ----------
|
|
73
|
+
export const buildTicketCodeNumeric = (args: {
|
|
74
|
+
venueCode: string; // e.g. "SB"
|
|
43
75
|
fixtureLocalIso: string; // "2025-10-31T20:00" in venue TZ
|
|
44
76
|
sectionId: string;
|
|
45
77
|
row: number;
|
|
46
78
|
col: number;
|
|
47
|
-
rndLen?: number; // 3
|
|
79
|
+
rndLen?: number; // default 3 (use 4 if you want more entropy)
|
|
80
|
+
dashed?: boolean; // default true (format with dashes)
|
|
48
81
|
}) => {
|
|
49
82
|
try {
|
|
50
|
-
const VV = args.venueCode
|
|
51
|
-
const E =
|
|
52
|
-
const S =
|
|
53
|
-
const R =
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
-
|
|
83
|
+
const VV = venueBlockNum(args.venueCode, 2); // 2 digits
|
|
84
|
+
const E = fixtureBlockNum(args.fixtureLocalIso, 6); // 6 digits
|
|
85
|
+
const S = seatBlockNum(args.sectionId, args.row, args.col); // 3 digits
|
|
86
|
+
const R = rndBlockNum(args.rndLen ?? 3); // 3 digits
|
|
87
|
+
|
|
88
|
+
const body = `${VV}${E}${S}${R}`; // 14 digits
|
|
89
|
+
const C = luhn10(body); // 1 digit
|
|
90
|
+
const numeric = `${body}${C}`; // 15 digits total
|
|
91
|
+
|
|
92
|
+
if (args.dashed === false) return numeric;
|
|
93
|
+
// Grouping 2-6-3-3-1 for readability
|
|
57
94
|
return `${VV}-${E}-${S}-${R}-${C}`;
|
|
58
|
-
} catch
|
|
95
|
+
} catch {
|
|
59
96
|
return '';
|
|
60
97
|
}
|
|
61
98
|
};
|
|
62
99
|
|
|
63
|
-
export const
|
|
100
|
+
export const validateTicketCodeNumeric = (code: string) => {
|
|
64
101
|
try {
|
|
65
|
-
const clean = code.replace(/-/g, '')
|
|
66
|
-
if (
|
|
102
|
+
const clean = code.replace(/-/g, '');
|
|
103
|
+
if (!/^\d{15}$/.test(clean)) return false; // 2+6+3+3+1
|
|
67
104
|
const body = clean.slice(0, -1);
|
|
68
105
|
const check = clean.slice(-1);
|
|
69
|
-
return
|
|
70
|
-
} catch
|
|
106
|
+
return luhn10(body) === check;
|
|
107
|
+
} catch {
|
|
71
108
|
return false;
|
|
72
109
|
}
|
|
73
110
|
};
|
|
@@ -81,3 +118,7 @@ export const getTicketId = ({
|
|
|
81
118
|
sectionName: string;
|
|
82
119
|
seatName: string;
|
|
83
120
|
}) => `${sectionName}-${seatName}-${fixtureId}`;
|
|
121
|
+
|
|
122
|
+
// ---------- compatibility shim (if you want same names as before) ----------
|
|
123
|
+
export const buildTicketCode = buildTicketCodeNumeric;
|
|
124
|
+
export const validateTicketCode = validateTicketCodeNumeric;
|