opticedge-cloud-utils 1.1.27 → 1.1.28

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
@@ -9,6 +9,7 @@ export * from './env';
9
9
  export * from './logger';
10
10
  export * from './number';
11
11
  export * from './parser';
12
+ export * from './pricecharting';
12
13
  export * from './pub';
13
14
  export * from './regex';
14
15
  export * from './retry';
package/dist/index.js CHANGED
@@ -25,6 +25,7 @@ __exportStar(require("./env"), exports);
25
25
  __exportStar(require("./logger"), exports);
26
26
  __exportStar(require("./number"), exports);
27
27
  __exportStar(require("./parser"), exports);
28
+ __exportStar(require("./pricecharting"), exports);
28
29
  __exportStar(require("./pub"), exports);
29
30
  __exportStar(require("./regex"), exports);
30
31
  __exportStar(require("./retry"), exports);
@@ -0,0 +1,18 @@
1
+ type CardParts = {
2
+ name: string;
3
+ number: string | null;
4
+ };
5
+ /**
6
+ * Split a card label into `{ name, number }`.
7
+ *
8
+ * Logic:
9
+ * 1. If `realNumber` (string) is provided — try to find it as a whole token at the END of `input`.
10
+ * Accepts " #123", "# 123", " 123", or "123" at the very end.
11
+ * If found, returns `{ name: <before>, number: realNumber }`.
12
+ * DOES NOT match numeric suffixes that are part of a larger token (e.g. "X123" won't match "123").
13
+ * 2. If `realNumber` is `null` or `undefined`, or not found — fallback:
14
+ * Use the last `#` to split, as in the legacy logic.
15
+ * 3. If no valid number found — returns `{ name: original, number: null }`.
16
+ */
17
+ export declare function splitNameAndNumber(input: string, realNumber?: string | null): CardParts;
18
+ export {};
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.splitNameAndNumber = splitNameAndNumber;
4
+ const regex_1 = require("./regex");
5
+ /**
6
+ * Split a card label into `{ name, number }`.
7
+ *
8
+ * Logic:
9
+ * 1. If `realNumber` (string) is provided — try to find it as a whole token at the END of `input`.
10
+ * Accepts " #123", "# 123", " 123", or "123" at the very end.
11
+ * If found, returns `{ name: <before>, number: realNumber }`.
12
+ * DOES NOT match numeric suffixes that are part of a larger token (e.g. "X123" won't match "123").
13
+ * 2. If `realNumber` is `null` or `undefined`, or not found — fallback:
14
+ * Use the last `#` to split, as in the legacy logic.
15
+ * 3. If no valid number found — returns `{ name: original, number: null }`.
16
+ */
17
+ function splitNameAndNumber(input, realNumber) {
18
+ const s = (input ?? '').trim();
19
+ if (!s)
20
+ return { name: '', number: null };
21
+ // 1) Try realNumber match at the end (if provided)
22
+ if (realNumber) {
23
+ const rn = String(realNumber).trim();
24
+ if (rn !== '') {
25
+ const escaped = (0, regex_1.escapeForRegex)(rn);
26
+ // require either start-of-string or a separator (# or whitespace) before the number,
27
+ // and anchor to the end to ensure it's at the end of the input.
28
+ // Examples matched: "... #123", "... # 123", "... 123", "...123" (if preceded by whitespace or start)
29
+ const re = new RegExp(`(?:^|[\\s#])${escaped}\\s*$`, 'i');
30
+ const match = re.exec(s);
31
+ if (match) {
32
+ // match.index points at start of the (?:^|[\s#]) group
33
+ let namePart = s.slice(0, match.index);
34
+ // remove trailing separators (# and whitespace) that preceded the number
35
+ namePart = namePart.replace(/[#\s]+$/g, '').trim();
36
+ return { name: namePart, number: rn };
37
+ }
38
+ // else fall through to fallback
39
+ }
40
+ }
41
+ // 2) Fallback: use last "#" style split (legacy behaviour)
42
+ const idx = s.lastIndexOf('#');
43
+ if (idx === -1)
44
+ return { name: s, number: null };
45
+ const namePart = s.slice(0, idx).trim();
46
+ const numberPart = s.slice(idx + 1).trim();
47
+ if (numberPart === '')
48
+ return { name: s, number: null };
49
+ return { name: namePart, number: numberPart };
50
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opticedge-cloud-utils",
3
- "version": "1.1.27",
3
+ "version": "1.1.28",
4
4
  "description": "Common utilities for cloud functions",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/src/index.ts CHANGED
@@ -9,6 +9,7 @@ export * from './env'
9
9
  export * from './logger'
10
10
  export * from './number'
11
11
  export * from './parser'
12
+ export * from './pricecharting'
12
13
  export * from './pub'
13
14
  export * from './regex'
14
15
  export * from './retry'
@@ -0,0 +1,51 @@
1
+ import { escapeForRegex } from './regex'
2
+
3
+ type CardParts = { name: string; number: string | null }
4
+
5
+ /**
6
+ * Split a card label into `{ name, number }`.
7
+ *
8
+ * Logic:
9
+ * 1. If `realNumber` (string) is provided — try to find it as a whole token at the END of `input`.
10
+ * Accepts " #123", "# 123", " 123", or "123" at the very end.
11
+ * If found, returns `{ name: <before>, number: realNumber }`.
12
+ * DOES NOT match numeric suffixes that are part of a larger token (e.g. "X123" won't match "123").
13
+ * 2. If `realNumber` is `null` or `undefined`, or not found — fallback:
14
+ * Use the last `#` to split, as in the legacy logic.
15
+ * 3. If no valid number found — returns `{ name: original, number: null }`.
16
+ */
17
+ export function splitNameAndNumber(input: string, realNumber?: string | null): CardParts {
18
+ const s = (input ?? '').trim()
19
+ if (!s) return { name: '', number: null }
20
+
21
+ // 1) Try realNumber match at the end (if provided)
22
+ if (realNumber) {
23
+ const rn = String(realNumber).trim()
24
+ if (rn !== '') {
25
+ const escaped = escapeForRegex(rn)
26
+ // require either start-of-string or a separator (# or whitespace) before the number,
27
+ // and anchor to the end to ensure it's at the end of the input.
28
+ // Examples matched: "... #123", "... # 123", "... 123", "...123" (if preceded by whitespace or start)
29
+ const re = new RegExp(`(?:^|[\\s#])${escaped}\\s*$`, 'i')
30
+ const match = re.exec(s)
31
+ if (match) {
32
+ // match.index points at start of the (?:^|[\s#]) group
33
+ let namePart = s.slice(0, match.index)
34
+ // remove trailing separators (# and whitespace) that preceded the number
35
+ namePart = namePart.replace(/[#\s]+$/g, '').trim()
36
+ return { name: namePart, number: rn }
37
+ }
38
+ // else fall through to fallback
39
+ }
40
+ }
41
+
42
+ // 2) Fallback: use last "#" style split (legacy behaviour)
43
+ const idx = s.lastIndexOf('#')
44
+ if (idx === -1) return { name: s, number: null }
45
+
46
+ const namePart = s.slice(0, idx).trim()
47
+ const numberPart = s.slice(idx + 1).trim()
48
+ if (numberPart === '') return { name: s, number: null }
49
+
50
+ return { name: namePart, number: numberPart }
51
+ }
@@ -0,0 +1,75 @@
1
+ // tests/utils.test.ts
2
+
3
+ import { splitNameAndNumber } from '../src/pricecharting'
4
+
5
+ describe('splitNameAndNumber (legacy fallback and new realNumber param)', () => {
6
+ const legacyCases: Array<[string, { name: string; number: string | null }]> = [
7
+ ['Charizard [1st Edition] #4', { name: 'Charizard [1st Edition]', number: '4' }],
8
+ ['Pikachu', { name: 'Pikachu', number: null }],
9
+ ['Mystery #', { name: 'Mystery #', number: null }], // nothing after '#'
10
+ ['#7', { name: '', number: '7' }], // valid number but empty name
11
+ ['Name # 123 ', { name: 'Name', number: '123' }], // trims spaces
12
+ ['X#Y #42', { name: 'X#Y', number: '42' }], // uses last '#'
13
+ [' ', { name: '', number: null }], // whitespace-only input
14
+ ['', { name: '', number: null }] // empty string
15
+ ]
16
+
17
+ test.each(legacyCases)('splitNameAndNumber("%s") -> %j (legacy fallback)', (input, expected) => {
18
+ expect(splitNameAndNumber(input)).toEqual(expected)
19
+ })
20
+
21
+ test('does not treat an internal "#" before last as separator', () => {
22
+ const input = 'Foo#bar#999'
23
+ expect(splitNameAndNumber(input)).toEqual({ name: 'Foo#bar', number: '999' })
24
+ })
25
+
26
+ test('handles undefined / null input gracefully', () => {
27
+ // @ts-expect-error intentional: testing undefined input
28
+ expect(splitNameAndNumber(undefined)).toEqual({ name: '', number: null })
29
+ // @ts-expect-error intentional: testing null input
30
+ expect(splitNameAndNumber(null)).toEqual({ name: '', number: null })
31
+ })
32
+
33
+ // -----------------------
34
+ // New tests for realNumber parameter
35
+ // -----------------------
36
+ const realNumberCases: Array<
37
+ [string, string | null | undefined, { name: string; number: string | null }]
38
+ > = [
39
+ // exact match with trailing "#"
40
+ ['test product name #123', '123', { name: 'test product name', number: '123' }],
41
+
42
+ // exact match with trailing plain digits
43
+ ['test product name 123', '123', { name: 'test product name', number: '123' }],
44
+
45
+ // realNumber provided and found even when there is a space before the number
46
+ ['foo bar # 42', '42', { name: 'foo bar', number: '42' }],
47
+
48
+ // realNumber provided that appears at the end with no '#'
49
+ ['Some Card 007', '007', { name: 'Some Card', number: '007' }],
50
+
51
+ // realNumber provided but NOT found as a whole token at end -> fallback to last '#' behavior
52
+ // input ends with "#123abc", realNumber '123' is a suffix of larger token -> fallback to last '#'
53
+ ['test product name #123abc', '123', { name: 'test product name', number: '123abc' }],
54
+
55
+ // multiple #'s; realNumber found at end
56
+ ['A#B#999', '999', { name: 'A#B', number: '999' }],
57
+
58
+ // realNumber is undefined/null -> fallback behavior (legacy)
59
+ ['Charizard [1st Edition] #4', undefined, { name: 'Charizard [1st Edition]', number: '4' }],
60
+ ['Charizard [1st Edition] #4', null, { name: 'Charizard [1st Edition]', number: '4' }]
61
+ ]
62
+
63
+ test.each(realNumberCases)(
64
+ 'splitNameAndNumber("%s", realNumber=%s) -> %j (realNumber cases)',
65
+ (input, realNumber, expected) => {
66
+ expect(splitNameAndNumber(input, realNumber)).toEqual(expected)
67
+ }
68
+ )
69
+
70
+ test('when realNumber is provided but not matched as whole token, fallback applies', () => {
71
+ const input = 'Example #X123'
72
+ // '123' is part of larger token 'X123' -> should FALLBACK and use last '#' -> number = 'X123'
73
+ expect(splitNameAndNumber(input, '123')).toEqual({ name: 'Example', number: 'X123' })
74
+ })
75
+ })