@ibiliaze/stringman 3.5.1 → 3.7.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 CHANGED
@@ -78,7 +78,7 @@ export declare const getCloudinaryPublicId: (url: string) => string;
78
78
  * query({ limit: 10, skip: 20, sortBy: 'createdAt:desc', empty: '' });
79
79
  * // → "?limit=10&skip=20&sortBy=createdAt%3Adesc"
80
80
  */
81
- export declare const query: (q: Record<string, any>) => string;
81
+ export declare const query: (q: Record<string, any>, filter?: (string | null | undefined)[]) => string;
82
82
  /**
83
83
  * Build a comma-separated string from a list of object keys.
84
84
  *
@@ -138,9 +138,25 @@ export declare const isVideoUrl: (url: string) => boolean;
138
138
  * @throws Error if no endpoint returns a valid IP.
139
139
  */
140
140
  export declare const getPublicIP: () => Promise<string>;
141
- export declare const getTicketId: ({ fixtureId, sectionName, seatName, }: {
142
- fixtureId: string;
143
- sectionName: string;
144
- seatName: string;
145
- }) => string;
146
141
  export declare const invalidPw: (password: string, passwordLength?: number) => string | void;
142
+ export declare const b36: (n: number) => string;
143
+ export declare const luhn36: (s: string) => string;
144
+ export declare const ticket: {
145
+ getTicketId: ({ fixtureId, sectionName, seatName, }: {
146
+ fixtureId: string;
147
+ sectionName: string;
148
+ seatName: string;
149
+ }) => string;
150
+ buildTicketCode: (args: {
151
+ venueCode: string;
152
+ fixtureLocalIso: string;
153
+ sectionId: string;
154
+ row: number;
155
+ col: number;
156
+ rndLen?: number;
157
+ }) => string;
158
+ validateTicketCode: (code: string) => boolean;
159
+ rndBlock: (len?: number) => string;
160
+ fixtureBlock: (fixtureLocalIso: string) => string;
161
+ seatBlock: (sectionId: string, row: number, col: number, len?: number) => string;
162
+ };
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.invalidPw = exports.getTicketId = exports.getPublicIP = exports.isVideoUrl = exports.getRandomString = exports.extractImageSrcs = exports.select = exports.query = exports.getCloudinaryPublicId = exports.dp = exports.megaTrim = exports.superTrim = void 0;
3
+ exports.ticket = exports.luhn36 = exports.b36 = exports.invalidPw = exports.getPublicIP = exports.isVideoUrl = exports.getRandomString = exports.extractImageSrcs = exports.select = exports.query = exports.getCloudinaryPublicId = exports.dp = exports.megaTrim = exports.superTrim = void 0;
4
+ const ticket_1 = require("./ticket");
4
5
  /**
5
6
  * Clean up extra whitespace in a string.
6
7
  *
@@ -97,10 +98,8 @@ exports.getCloudinaryPublicId = getCloudinaryPublicId;
97
98
  * query({ limit: 10, skip: 20, sortBy: 'createdAt:desc', empty: '' });
98
99
  * // → "?limit=10&skip=20&sortBy=createdAt%3Adesc"
99
100
  */
100
- const query = (q) => {
101
+ const query = (q, filter = ['', null, undefined, 'null', 'undefined']) => {
101
102
  try {
102
- // Values considered "invalid" and should be filtered out
103
- const filter = ['', null, undefined, 'null', 'undefined'];
104
103
  // Convert object to entries, filter out invalid keys/values
105
104
  const filtered = Object.entries(q).filter(([key, value]) => !filter.includes(key) && !filter.includes(value));
106
105
  // Build query string using URLSearchParams
@@ -228,8 +227,6 @@ const getPublicIP = async () => {
228
227
  throw new Error('Public IP unavailable');
229
228
  };
230
229
  exports.getPublicIP = getPublicIP;
231
- const getTicketId = ({ fixtureId, sectionName, seatName, }) => `${sectionName}-${seatName}-${fixtureId}`;
232
- exports.getTicketId = getTicketId;
233
230
  const invalidPw = (password, passwordLength = 8) => {
234
231
  try {
235
232
  if (password.length < passwordLength)
@@ -248,3 +245,52 @@ const invalidPw = (password, passwordLength = 8) => {
248
245
  }
249
246
  };
250
247
  exports.invalidPw = invalidPw;
248
+ const BASE36 = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
249
+ const b36 = (n) => {
250
+ try {
251
+ if (n === 0)
252
+ return '0';
253
+ let s = '';
254
+ let x = Math.floor(Math.abs(n));
255
+ while (x > 0) {
256
+ s = BASE36[x % 36] + s;
257
+ x = Math.floor(x / 36);
258
+ }
259
+ return s;
260
+ }
261
+ catch (e) {
262
+ return '';
263
+ }
264
+ };
265
+ exports.b36 = b36;
266
+ const luhn36 = (s) => {
267
+ try {
268
+ let sum = 0;
269
+ let alt = false;
270
+ for (let i = s.length - 1; i >= 0; i--) {
271
+ let v = BASE36.indexOf(s[i].toUpperCase());
272
+ if (v < 0)
273
+ continue;
274
+ if (alt) {
275
+ v *= 2;
276
+ v = Math.floor(v / 36) + (v % 36);
277
+ }
278
+ sum += v;
279
+ alt = !alt;
280
+ }
281
+ const check = (36 - (sum % 36)) % 36;
282
+ return BASE36[check];
283
+ }
284
+ catch (e) {
285
+ return '';
286
+ }
287
+ };
288
+ exports.luhn36 = luhn36;
289
+ exports.ticket = {
290
+ getTicketId: ticket_1.getTicketId,
291
+ buildTicketCode: ticket_1.buildTicketCode,
292
+ validateTicketCode: ticket_1.validateTicketCode,
293
+ rndBlock: ticket_1.rndBlock,
294
+ fixtureBlock: ticket_1.fixtureBlock,
295
+ seatBlock: ticket_1.seatBlock,
296
+ };
@@ -0,0 +1,17 @@
1
+ export declare const seatBlock: (sectionId: string, row: number, col: number, len?: number) => string;
2
+ export declare const fixtureBlock: (fixtureLocalIso: string) => string;
3
+ export declare const rndBlock: (len?: number) => string;
4
+ export declare const buildTicketCode: (args: {
5
+ venueCode: string;
6
+ fixtureLocalIso: string;
7
+ sectionId: string;
8
+ row: number;
9
+ col: number;
10
+ rndLen?: number;
11
+ }) => string;
12
+ export declare const validateTicketCode: (code: string) => boolean;
13
+ export declare const getTicketId: ({ fixtureId, sectionName, seatName, }: {
14
+ fixtureId: string;
15
+ sectionName: string;
16
+ seatName: string;
17
+ }) => string;
package/dist/ticket.js ADDED
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getTicketId = exports.validateTicketCode = exports.buildTicketCode = exports.rndBlock = exports.fixtureBlock = exports.seatBlock = void 0;
4
+ const node_crypto_1 = require("node:crypto");
5
+ const _1 = require(".");
6
+ const seatBlock = (sectionId, row, col, len = 3) => {
7
+ try {
8
+ const h = (0, node_crypto_1.createHash)('sha1').update(`${sectionId}:${row}:${col}`).digest(); // 20 bytes
9
+ // take first 2 bytes -> 0..65535
10
+ const n = (h[0] << 8) | h[1];
11
+ const code = (0, _1.b36)(n).padStart(3, '0'); // 3 chars base36
12
+ return code.slice(-len);
13
+ }
14
+ catch (e) {
15
+ return '';
16
+ }
17
+ };
18
+ exports.seatBlock = seatBlock;
19
+ // Fixture datetime block (local TZ string -> YYMMDDhhmm -> base36)
20
+ const fixtureBlock = (fixtureLocalIso) => {
21
+ try {
22
+ // fixtureLocalIso like "2025-10-31T20:00" in venue TZ (no Z)
23
+ const m = fixtureLocalIso.match(/^(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})/);
24
+ if (!m)
25
+ throw new Error('Bad fixtureLocalIso');
26
+ const [_, y, M, d, h, mnt] = m;
27
+ const compact = `${y.slice(2)}${M}${d}${h}${mnt}`; // YYMMDDhhmm as number
28
+ return (0, _1.b36)(Number(compact)).padStart(5, '0'); // ~5-6 chars
29
+ }
30
+ catch (e) {
31
+ return '';
32
+ }
33
+ };
34
+ exports.fixtureBlock = fixtureBlock;
35
+ // Random block
36
+ const rndBlock = (len = 3) => {
37
+ try {
38
+ // 16-bit random -> base36 3-4 chars
39
+ const n = (0, node_crypto_1.randomBytes)(2).readUInt16BE(0);
40
+ return (0, _1.b36)(n).padStart(3, '0').slice(-len);
41
+ }
42
+ catch (e) {
43
+ return '';
44
+ }
45
+ };
46
+ exports.rndBlock = rndBlock;
47
+ const buildTicketCode = (args) => {
48
+ try {
49
+ const VV = args.venueCode.toUpperCase().slice(0, 2);
50
+ const E = (0, exports.fixtureBlock)(args.fixtureLocalIso); // 5-6 chars
51
+ const S = (0, exports.seatBlock)(args.sectionId, args.row, args.col, 3); // 3 chars
52
+ const R = (0, exports.rndBlock)(args.rndLen ?? 3); // 3 chars
53
+ const body = `${VV}${E}${S}${R}`;
54
+ const C = (0, _1.luhn36)(body);
55
+ // Format with dashes for readability (optional)
56
+ return `${VV}-${E}-${S}-${R}-${C}`;
57
+ }
58
+ catch (e) {
59
+ return '';
60
+ }
61
+ };
62
+ exports.buildTicketCode = buildTicketCode;
63
+ const validateTicketCode = (code) => {
64
+ try {
65
+ const clean = code.replace(/-/g, '').toUpperCase();
66
+ if (!/^[0-9A-Z]{12,14}$/.test(clean))
67
+ return false;
68
+ const body = clean.slice(0, -1);
69
+ const check = clean.slice(-1);
70
+ return (0, _1.luhn36)(body) === check;
71
+ }
72
+ catch (e) {
73
+ return false;
74
+ }
75
+ };
76
+ exports.validateTicketCode = validateTicketCode;
77
+ const getTicketId = ({ fixtureId, sectionName, seatName, }) => `${sectionName}-${seatName}-${fixtureId}`;
78
+ exports.getTicketId = getTicketId;
package/package.json CHANGED
@@ -1,18 +1,22 @@
1
1
  {
2
2
  "name": "@ibiliaze/stringman",
3
- "version": "3.5.1",
3
+ "version": "3.7.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.5.1 -m 'v3.5.1'; git push origin v3.5.1; git push",
10
+ "git": "git add .; git commit -m 'changes'; git tag -a v3.7.0 -m 'v3.7.0'; git push origin v3.7.0; git push",
11
11
  "push": "npm run build; npm run git; npm run pub"
12
12
  },
13
13
  "author": "Ibi Hasanli",
14
14
  "license": "ISC",
15
+ "dependencies": {
16
+ "crypto": "^1.0.1"
17
+ },
15
18
  "devDependencies": {
19
+ "@types/node": "^24.7.2",
16
20
  "typescript": "^5.0.0"
17
21
  }
18
22
  }
package/src/index.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { buildTicketCode, fixtureBlock, getTicketId, rndBlock, seatBlock, validateTicketCode } from './ticket';
2
+
1
3
  /**
2
4
  * Clean up extra whitespace in a string.
3
5
  *
@@ -95,11 +97,8 @@ export const getCloudinaryPublicId = (url: string): string => {
95
97
  * query({ limit: 10, skip: 20, sortBy: 'createdAt:desc', empty: '' });
96
98
  * // → "?limit=10&skip=20&sortBy=createdAt%3Adesc"
97
99
  */
98
- export const query = (q: Record<string, any>): string => {
100
+ export const query = (q: Record<string, any>, filter = ['', null, undefined, 'null', 'undefined']): string => {
99
101
  try {
100
- // Values considered "invalid" and should be filtered out
101
- const filter = ['', null, undefined, 'null', 'undefined'];
102
-
103
102
  // Convert object to entries, filter out invalid keys/values
104
103
  const filtered = Object.entries(q).filter(([key, value]) => !filter.includes(key) && !filter.includes(value));
105
104
 
@@ -228,16 +227,6 @@ export const getPublicIP = async (): Promise<string> => {
228
227
  throw new Error('Public IP unavailable');
229
228
  };
230
229
 
231
- export const getTicketId = ({
232
- fixtureId,
233
- sectionName,
234
- seatName,
235
- }: {
236
- fixtureId: string;
237
- sectionName: string;
238
- seatName: string;
239
- }) => `${sectionName}-${seatName}-${fixtureId}`;
240
-
241
230
  export const invalidPw = (password: string, passwordLength: number = 8): string | void => {
242
231
  try {
243
232
  if (password.length < passwordLength) return `Password must be at least ${passwordLength} characters`;
@@ -249,3 +238,49 @@ export const invalidPw = (password: string, passwordLength: number = 8): string
249
238
  return 'Failed to validate password';
250
239
  }
251
240
  };
241
+
242
+ const BASE36 = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' as const;
243
+ export const b36 = (n: number) => {
244
+ try {
245
+ if (n === 0) return '0';
246
+ let s = '';
247
+ let x = Math.floor(Math.abs(n));
248
+ while (x > 0) {
249
+ s = BASE36[x % 36] + s;
250
+ x = Math.floor(x / 36);
251
+ }
252
+ return s;
253
+ } catch (e) {
254
+ return '';
255
+ }
256
+ };
257
+
258
+ export const luhn36 = (s: string): string => {
259
+ try {
260
+ let sum = 0;
261
+ let alt = false;
262
+ for (let i = s.length - 1; i >= 0; i--) {
263
+ let v = BASE36.indexOf(s[i].toUpperCase());
264
+ if (v < 0) continue;
265
+ if (alt) {
266
+ v *= 2;
267
+ v = Math.floor(v / 36) + (v % 36);
268
+ }
269
+ sum += v;
270
+ alt = !alt;
271
+ }
272
+ const check = (36 - (sum % 36)) % 36;
273
+ return BASE36[check];
274
+ } catch (e) {
275
+ return '';
276
+ }
277
+ };
278
+
279
+ export const ticket = {
280
+ getTicketId,
281
+ buildTicketCode,
282
+ validateTicketCode,
283
+ rndBlock,
284
+ fixtureBlock,
285
+ seatBlock,
286
+ };
package/src/ticket.ts ADDED
@@ -0,0 +1,83 @@
1
+ import { createHash, randomBytes } from 'node:crypto';
2
+ import { b36, luhn36 } from '.';
3
+
4
+ export const seatBlock = (sectionId: string, row: number, col: number, len = 3) => {
5
+ try {
6
+ const h = createHash('sha1').update(`${sectionId}:${row}:${col}`).digest(); // 20 bytes
7
+ // take first 2 bytes -> 0..65535
8
+ const n = (h[0] << 8) | h[1];
9
+ const code = b36(n).padStart(3, '0'); // 3 chars base36
10
+ return code.slice(-len);
11
+ } catch (e) {
12
+ return '';
13
+ }
14
+ };
15
+
16
+ // Fixture datetime block (local TZ string -> YYMMDDhhmm -> base36)
17
+ export const fixtureBlock = (fixtureLocalIso: string) => {
18
+ try {
19
+ // fixtureLocalIso like "2025-10-31T20:00" in venue TZ (no Z)
20
+ const m = fixtureLocalIso.match(/^(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})/);
21
+ if (!m) throw new Error('Bad fixtureLocalIso');
22
+ const [_, y, M, d, h, mnt] = m;
23
+ const compact = `${y.slice(2)}${M}${d}${h}${mnt}`; // YYMMDDhhmm as number
24
+ return b36(Number(compact)).padStart(5, '0'); // ~5-6 chars
25
+ } catch (e) {
26
+ return '';
27
+ }
28
+ };
29
+
30
+ // Random block
31
+ export const rndBlock = (len = 3) => {
32
+ try {
33
+ // 16-bit random -> base36 3-4 chars
34
+ const n = randomBytes(2).readUInt16BE(0);
35
+ return b36(n).padStart(3, '0').slice(-len);
36
+ } catch (e) {
37
+ return '';
38
+ }
39
+ };
40
+
41
+ export const buildTicketCode = (args: {
42
+ venueCode: string; // "SB"
43
+ fixtureLocalIso: string; // "2025-10-31T20:00" in venue TZ
44
+ sectionId: string;
45
+ row: number;
46
+ col: number;
47
+ rndLen?: number; // 3 or 4
48
+ }) => {
49
+ try {
50
+ const VV = args.venueCode.toUpperCase().slice(0, 2);
51
+ const E = fixtureBlock(args.fixtureLocalIso); // 5-6 chars
52
+ const S = seatBlock(args.sectionId, args.row, args.col, 3); // 3 chars
53
+ const R = rndBlock(args.rndLen ?? 3); // 3 chars
54
+ const body = `${VV}${E}${S}${R}`;
55
+ const C = luhn36(body);
56
+ // Format with dashes for readability (optional)
57
+ return `${VV}-${E}-${S}-${R}-${C}`;
58
+ } catch (e) {
59
+ return '';
60
+ }
61
+ };
62
+
63
+ export const validateTicketCode = (code: string) => {
64
+ try {
65
+ const clean = code.replace(/-/g, '').toUpperCase();
66
+ if (!/^[0-9A-Z]{12,14}$/.test(clean)) return false;
67
+ const body = clean.slice(0, -1);
68
+ const check = clean.slice(-1);
69
+ return luhn36(body) === check;
70
+ } catch (e) {
71
+ return false;
72
+ }
73
+ };
74
+
75
+ export const getTicketId = ({
76
+ fixtureId,
77
+ sectionName,
78
+ seatName,
79
+ }: {
80
+ fixtureId: string;
81
+ sectionName: string;
82
+ seatName: string;
83
+ }) => `${sectionName}-${seatName}-${fixtureId}`;