opticedge-cloud-utils 1.1.26 → 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 +1 -0
- package/dist/index.js +1 -0
- package/dist/pricecharting.d.ts +18 -0
- package/dist/pricecharting.js +50 -0
- package/dist/validator.d.ts +2 -1
- package/dist/validator.js +6 -1
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/pricecharting.ts +51 -0
- package/src/validator.ts +7 -1
- package/tests/pricecharting.test.ts +75 -0
- package/tests/validator.test.ts +69 -0
- package/tests/validator.ts +0 -37
package/dist/index.d.ts
CHANGED
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/dist/validator.d.ts
CHANGED
package/dist/validator.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.isValidEmail = void 0;
|
|
3
|
+
exports.isValidObjectId = exports.isValidEmail = void 0;
|
|
4
|
+
const mongodb_1 = require("mongodb");
|
|
4
5
|
/** Basic email validation — good enough for most production use cases */
|
|
5
6
|
const isValidEmail = (email) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.trim());
|
|
6
7
|
exports.isValidEmail = isValidEmail;
|
|
8
|
+
const isValidObjectId = (id) => {
|
|
9
|
+
return mongodb_1.ObjectId.isValid(id) && new mongodb_1.ObjectId(id).toHexString() === id;
|
|
10
|
+
};
|
|
11
|
+
exports.isValidObjectId = isValidObjectId;
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -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
|
+
}
|
package/src/validator.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
+
import { ObjectId } from 'mongodb'
|
|
2
|
+
|
|
1
3
|
/** Basic email validation — good enough for most production use cases */
|
|
2
4
|
const isValidEmail = (email: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.trim())
|
|
3
5
|
|
|
4
|
-
|
|
6
|
+
const isValidObjectId = (id: string): boolean => {
|
|
7
|
+
return ObjectId.isValid(id) && new ObjectId(id).toHexString() === id
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export { isValidEmail, isValidObjectId }
|
|
@@ -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
|
+
})
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { describe, it, expect } from '@jest/globals'
|
|
2
|
+
import { ObjectId } from 'mongodb'
|
|
3
|
+
import { isValidEmail, isValidObjectId } from '../src'
|
|
4
|
+
|
|
5
|
+
describe('isValidEmail', () => {
|
|
6
|
+
it('returns true for simple valid emails', () => {
|
|
7
|
+
expect(isValidEmail('test@example.com')).toBe(true)
|
|
8
|
+
expect(isValidEmail('user.name@domain.co')).toBe(true)
|
|
9
|
+
expect(isValidEmail('user_name+tag@sub.domain.org')).toBe(true)
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it('returns false for missing parts', () => {
|
|
13
|
+
expect(isValidEmail('')).toBe(false)
|
|
14
|
+
expect(isValidEmail('plainaddress')).toBe(false)
|
|
15
|
+
expect(isValidEmail('@no-local-part.com')).toBe(false)
|
|
16
|
+
expect(isValidEmail('username@')).toBe(false)
|
|
17
|
+
expect(isValidEmail('user@domain')).toBe(false)
|
|
18
|
+
expect(isValidEmail('username@.com')).toBe(false)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('handles whitespace correctly', () => {
|
|
22
|
+
expect(isValidEmail(' test@example.com ')).toBe(true)
|
|
23
|
+
expect(isValidEmail('\nuser@domain.com\t')).toBe(true)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('rejects clearly invalid characters or formats', () => {
|
|
27
|
+
expect(isValidEmail('user@@domain.com')).toBe(false)
|
|
28
|
+
expect(isValidEmail('user@domain,com')).toBe(false)
|
|
29
|
+
expect(isValidEmail('user@domain com')).toBe(false)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('accepts minimal valid domain structures', () => {
|
|
33
|
+
expect(isValidEmail('x@y.z')).toBe(true)
|
|
34
|
+
})
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
describe('isValidObjectId', () => {
|
|
38
|
+
it('returns true for valid ObjectId strings', () => {
|
|
39
|
+
const id = new ObjectId().toHexString()
|
|
40
|
+
expect(isValidObjectId(id)).toBe(true)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('returns false for invalid length', () => {
|
|
44
|
+
expect(isValidObjectId('123')).toBe(false)
|
|
45
|
+
expect(isValidObjectId('12345678901234567890123')).toBe(false)
|
|
46
|
+
expect(isValidObjectId('1234567890123456789012345')).toBe(false)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('returns false for non-hex characters', () => {
|
|
50
|
+
expect(isValidObjectId('zzzzzzzzzzzzzzzzzzzzzzzz')).toBe(false)
|
|
51
|
+
expect(isValidObjectId('12345678901234567890123g')).toBe(false)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('rejects 12-byte strings that ObjectId.isValid would accept', () => {
|
|
55
|
+
expect(isValidObjectId('123456789012')).toBe(false)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('is case-sensitive to exact hex string match', () => {
|
|
59
|
+
const id = new ObjectId().toHexString()
|
|
60
|
+
expect(isValidObjectId(id.toUpperCase())).toBe(false)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('returns false for empty or malformed input', () => {
|
|
64
|
+
expect(isValidObjectId('')).toBe(false)
|
|
65
|
+
expect(isValidObjectId(' ')).toBe(false)
|
|
66
|
+
expect(isValidObjectId('null')).toBe(false)
|
|
67
|
+
expect(isValidObjectId('undefined')).toBe(false)
|
|
68
|
+
})
|
|
69
|
+
})
|
package/tests/validator.ts
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
// isValidEmail.test.ts
|
|
2
|
-
import { describe, it, expect } from '@jest/globals'
|
|
3
|
-
import { isValidEmail } from '../src'
|
|
4
|
-
|
|
5
|
-
describe('isValidEmail', () => {
|
|
6
|
-
it('returns true for simple valid emails', () => {
|
|
7
|
-
expect(isValidEmail('test@example.com')).toBe(true)
|
|
8
|
-
expect(isValidEmail('user.name@domain.co')).toBe(true)
|
|
9
|
-
expect(isValidEmail('user_name+tag@sub.domain.org')).toBe(true)
|
|
10
|
-
})
|
|
11
|
-
|
|
12
|
-
it('returns false for missing parts', () => {
|
|
13
|
-
expect(isValidEmail('')).toBe(false)
|
|
14
|
-
expect(isValidEmail('plainaddress')).toBe(false)
|
|
15
|
-
expect(isValidEmail('@no-local-part.com')).toBe(false)
|
|
16
|
-
expect(isValidEmail('username@')).toBe(false)
|
|
17
|
-
expect(isValidEmail('user@domain')).toBe(false)
|
|
18
|
-
expect(isValidEmail('username@.com')).toBe(false)
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
it('handles whitespace correctly', () => {
|
|
22
|
-
expect(isValidEmail(' test@example.com ')).toBe(true) // trims input
|
|
23
|
-
expect(isValidEmail('\nuser@domain.com\t')).toBe(true)
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
it('rejects invalid characters or formats', () => {
|
|
27
|
-
expect(isValidEmail('user@@domain.com')).toBe(false)
|
|
28
|
-
expect(isValidEmail('user@domain,com')).toBe(false)
|
|
29
|
-
expect(isValidEmail('user@domain..com')).toBe(false)
|
|
30
|
-
expect(isValidEmail('user@.domain.com')).toBe(false)
|
|
31
|
-
expect(isValidEmail('user@domain com')).toBe(false)
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
it('accepts minimal valid domain structures', () => {
|
|
35
|
-
expect(isValidEmail('x@y.z')).toBe(true) // still valid
|
|
36
|
-
})
|
|
37
|
-
})
|