@umituz/react-native-subscription 2.27.115 → 2.27.116

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.
Files changed (36) hide show
  1. package/package.json +1 -1
  2. package/src/domains/credits/infrastructure/CreditsRepository.ts +16 -19
  3. package/src/domains/credits/utils/creditCalculations.ts +6 -11
  4. package/src/domains/wallet/infrastructure/repositories/TransactionRepository.ts +17 -41
  5. package/src/domains/wallet/infrastructure/services/ProductMetadataService.ts +2 -6
  6. package/src/shared/infrastructure/firestore/collectionUtils.ts +67 -0
  7. package/src/shared/infrastructure/firestore/index.ts +6 -0
  8. package/src/shared/infrastructure/firestore/resultUtils.ts +68 -0
  9. package/src/shared/infrastructure/index.ts +6 -0
  10. package/src/shared/presentation/hooks/index.ts +6 -0
  11. package/src/shared/presentation/hooks/useAsyncState.ts +72 -0
  12. package/src/shared/presentation/hooks/useServiceCall.ts +66 -0
  13. package/src/shared/types/CommonTypes.ts +6 -1
  14. package/src/shared/types/ReactTypes.ts +80 -0
  15. package/src/shared/utils/arrayUtils.core.ts +81 -0
  16. package/src/shared/utils/arrayUtils.query.ts +118 -0
  17. package/src/shared/utils/arrayUtils.transforms.ts +116 -0
  18. package/src/shared/utils/arrayUtils.ts +19 -0
  19. package/src/shared/utils/index.ts +14 -0
  20. package/src/shared/utils/numberUtils.aggregate.ts +35 -0
  21. package/src/shared/utils/numberUtils.core.ts +73 -0
  22. package/src/shared/utils/numberUtils.format.ts +42 -0
  23. package/src/shared/utils/numberUtils.math.ts +48 -0
  24. package/src/shared/utils/numberUtils.ts +9 -0
  25. package/src/shared/utils/stringUtils.case.ts +64 -0
  26. package/src/shared/utils/stringUtils.check.ts +65 -0
  27. package/src/shared/utils/stringUtils.format.ts +84 -0
  28. package/src/shared/utils/stringUtils.generate.ts +47 -0
  29. package/src/shared/utils/stringUtils.modify.ts +67 -0
  30. package/src/shared/utils/stringUtils.ts +10 -0
  31. package/src/shared/utils/validators.ts +187 -0
  32. package/src/utils/dateUtils.compare.ts +65 -0
  33. package/src/utils/dateUtils.core.ts +67 -0
  34. package/src/utils/dateUtils.format.ts +138 -0
  35. package/src/utils/dateUtils.math.ts +112 -0
  36. package/src/utils/dateUtils.ts +6 -28
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Array Utilities - Core Operations
3
+ * Basic array manipulation functions
4
+ */
5
+
6
+ /**
7
+ * Check if array is empty
8
+ */
9
+ export function isEmpty<T>(arr: readonly T[] | null | undefined): boolean {
10
+ return !arr || arr.length === 0;
11
+ }
12
+
13
+ /**
14
+ * Check if array has elements
15
+ */
16
+ export function isNotEmpty<T>(arr: readonly T[] | null | undefined): boolean {
17
+ return !isEmpty(arr);
18
+ }
19
+
20
+ /**
21
+ * Get first element of array or null
22
+ */
23
+ export function first<T>(arr: readonly T[] | null | undefined): T | null {
24
+ if (!arr || arr.length === 0) return null;
25
+ return arr[0] ?? null;
26
+ }
27
+
28
+ /**
29
+ * Get last element of array or null
30
+ */
31
+ export function last<T>(arr: readonly T[] | null | undefined): T | null {
32
+ if (!arr || arr.length === 0) return null;
33
+ return arr[arr.length - 1] ?? null;
34
+ }
35
+
36
+ /**
37
+ * Get nth element of array or null
38
+ */
39
+ export function nth<T>(arr: readonly T[], index: number): T | null {
40
+ if (index < 0) index = arr.length + index;
41
+ if (index < 0 || index >= arr.length) return null;
42
+ return arr[index] ?? null;
43
+ }
44
+
45
+ /**
46
+ * Remove first element from array
47
+ */
48
+ export function removeFirst<T>(arr: T[]): T[] {
49
+ return arr.slice(1);
50
+ }
51
+
52
+ /**
53
+ * Remove last element from array
54
+ */
55
+ export function removeLast<T>(arr: T[]): T[] {
56
+ return arr.slice(0, -1);
57
+ }
58
+
59
+ /**
60
+ * Remove element at index
61
+ */
62
+ export function removeAt<T>(arr: readonly T[], index: number): T[] {
63
+ return arr.filter((_, i) => i !== index);
64
+ }
65
+
66
+ /**
67
+ * Remove elements that match a predicate
68
+ */
69
+ export function removeWhere<T>(
70
+ arr: readonly T[],
71
+ predicate: (item: T, index: number) => boolean
72
+ ): T[] {
73
+ return arr.filter((item, index) => !predicate(item, index));
74
+ }
75
+
76
+ /**
77
+ * Unique array elements (removes duplicates)
78
+ */
79
+ export function unique<T>(arr: readonly T[]): T[] {
80
+ return Array.from(new Set(arr));
81
+ }
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Array Utilities - Query Operations
3
+ * Array searching, filtering, and grouping functions
4
+ */
5
+
6
+ /**
7
+ * Group array elements by a key function
8
+ */
9
+ export function groupBy<T, K extends string | number | symbol>(
10
+ arr: readonly T[],
11
+ keyFn: (item: T, index: number) => K
12
+ ): Record<K, T[]> {
13
+ return arr.reduce((result, item, index) => {
14
+ const key = keyFn(item, index);
15
+ if (!result[key]) {
16
+ result[key] = [];
17
+ }
18
+ result[key].push(item);
19
+ return result;
20
+ }, {} as Record<K, T[]>);
21
+ }
22
+
23
+ /**
24
+ * Unique array elements by a key function
25
+ */
26
+ export function uniqueBy<T, K>(arr: readonly T[], keyFn: (item: T) => K): T[] {
27
+ const seen = new Set<K>();
28
+ return arr.filter((item) => {
29
+ const key = keyFn(item);
30
+ if (seen.has(key)) return false;
31
+ seen.add(key);
32
+ return true;
33
+ });
34
+ }
35
+
36
+ /**
37
+ * Shuffle array (Fisher-Yates algorithm)
38
+ */
39
+ export function shuffle<T>(arr: readonly T[]): T[] {
40
+ const result = [...arr];
41
+ for (let i = result.length - 1; i > 0; i--) {
42
+ const j = Math.floor(Math.random() * (i + 1));
43
+ const temp = result[i]!;
44
+ result[i] = result[j]!;
45
+ result[j] = temp;
46
+ }
47
+ return result;
48
+ }
49
+
50
+ /**
51
+ * Sort array by a key function
52
+ */
53
+ export function sortBy<T>(
54
+ arr: readonly T[],
55
+ keyFn: (item: T) => string | number,
56
+ order: "asc" | "desc" = "asc"
57
+ ): T[] {
58
+ return [...arr].sort((a, b) => {
59
+ const keyA = keyFn(a);
60
+ const keyB = keyFn(b);
61
+ if (keyA < keyB) return order === "asc" ? -1 : 1;
62
+ if (keyA > keyB) return order === "asc" ? 1 : -1;
63
+ return 0;
64
+ });
65
+ }
66
+
67
+ /**
68
+ * Pick random element(s) from array
69
+ */
70
+ export function sample<T>(arr: readonly T[], count: number = 1): T[] {
71
+ if (arr.length === 0) return [];
72
+ const shuffled = shuffle([...arr]);
73
+ return shuffled.slice(0, Math.min(count, arr.length));
74
+ }
75
+
76
+ /**
77
+ * Find intersection of two arrays
78
+ */
79
+ export function intersection<T>(arr1: readonly T[], arr2: readonly T[]): T[] {
80
+ const set2 = new Set(arr2);
81
+ return arr1.filter((item) => set2.has(item));
82
+ }
83
+
84
+ /**
85
+ * Find difference of two arrays (elements in arr1 but not in arr2)
86
+ */
87
+ export function difference<T>(arr1: readonly T[], arr2: readonly T[]): T[] {
88
+ const set2 = new Set(arr2);
89
+ return arr1.filter((item) => !set2.has(item));
90
+ }
91
+
92
+ /**
93
+ * Find symmetric difference of two arrays (elements in either array but not both)
94
+ */
95
+ export function symmetricDifference<T>(arr1: readonly T[], arr2: readonly T[]): T[] {
96
+ const set1 = new Set(arr1);
97
+ const set2 = new Set(arr2);
98
+ return [
99
+ ...arr1.filter((item) => !set2.has(item)),
100
+ ...arr2.filter((item) => !set1.has(item)),
101
+ ];
102
+ }
103
+
104
+ /**
105
+ * Check if array contains all elements of another array
106
+ */
107
+ export function containsAll<T>(arr: readonly T[], values: readonly T[]): boolean {
108
+ const arrSet = new Set(arr);
109
+ return values.every((value) => arrSet.has(value));
110
+ }
111
+
112
+ /**
113
+ * Check if array contains any element of another array
114
+ */
115
+ export function containsAny<T>(arr: readonly T[], values: readonly T[]): boolean {
116
+ const arrSet = new Set(arr);
117
+ return values.some((value) => arrSet.has(value));
118
+ }
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Array Utilities - Transformations
3
+ * Array transformation and manipulation functions
4
+ */
5
+
6
+ /**
7
+ * Chunk array into smaller arrays of specified size
8
+ */
9
+ export function chunk<T>(arr: readonly T[], size: number): T[][] {
10
+ if (size <= 0) return [];
11
+ const result: T[][] = [];
12
+ for (let i = 0; i < arr.length; i += size) {
13
+ result.push(arr.slice(i, i + size));
14
+ }
15
+ return result;
16
+ }
17
+
18
+ /**
19
+ * Flatten array one level deep
20
+ */
21
+ export function flatten<T>(arr: readonly (readonly T[] | T)[]): T[] {
22
+ const result: T[] = [];
23
+ for (const item of arr) {
24
+ if (Array.isArray(item)) {
25
+ result.push(...item);
26
+ } else if (item !== undefined) {
27
+ result.push(item as T);
28
+ }
29
+ }
30
+ return result;
31
+ }
32
+
33
+ /**
34
+ * Flatten array recursively
35
+ */
36
+ export function flattenDeep<T>(arr: readonly unknown[]): T[] {
37
+ const result: T[] = [];
38
+ const stack = [...arr];
39
+
40
+ while (stack.length > 0) {
41
+ const item = stack.shift();
42
+ if (Array.isArray(item)) {
43
+ stack.unshift(...item);
44
+ } else if (item !== undefined) {
45
+ result.push(item as T);
46
+ }
47
+ }
48
+
49
+ return result;
50
+ }
51
+
52
+ /**
53
+ * Zip two arrays together
54
+ */
55
+ export function zip<T, U>(arr1: readonly T[], arr2: readonly U[]): [T, U][] {
56
+ const length = Math.min(arr1.length, arr2.length);
57
+ const result: [T, U][] = [];
58
+ for (let i = 0; i < length; i++) {
59
+ result.push([arr1[i]!, arr2[i]!]);
60
+ }
61
+ return result;
62
+ }
63
+
64
+ /**
65
+ * Partition array into two arrays based on predicate
66
+ */
67
+ export function partition<T>(
68
+ arr: readonly T[],
69
+ predicate: (item: T, index: number) => boolean
70
+ ): [T[], T[]] {
71
+ const truthy: T[] = [];
72
+ const falsy: T[] = [];
73
+
74
+ for (let i = 0; i < arr.length; i++) {
75
+ const item = arr[i];
76
+ if (item !== undefined) {
77
+ if (predicate(item, i)) {
78
+ truthy.push(item);
79
+ } else {
80
+ falsy.push(item);
81
+ }
82
+ }
83
+ }
84
+
85
+ return [truthy, falsy];
86
+ }
87
+
88
+ /**
89
+ * Move element from one index to another
90
+ */
91
+ export function move<T>(arr: readonly T[], from: number, to: number): T[] {
92
+ if (from < 0 || from >= arr.length || to < 0 || to >= arr.length) {
93
+ return [...arr];
94
+ }
95
+ const result = [...arr];
96
+ const element = result[from];
97
+ if (element === undefined) return result;
98
+
99
+ result.splice(from, 1);
100
+ result.splice(to, 0, element);
101
+ return result;
102
+ }
103
+
104
+ /**
105
+ * Swap two elements in array
106
+ */
107
+ export function swap<T>(arr: readonly T[], index1: number, index2: number): T[] {
108
+ if (index1 < 0 || index1 >= arr.length || index2 < 0 || index2 >= arr.length) {
109
+ return [...arr];
110
+ }
111
+ const result = [...arr];
112
+ const temp = result[index1];
113
+ result[index1] = result[index2]!;
114
+ result[index2] = temp!;
115
+ return result;
116
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Array Utilities
3
+ * Re-exports all array utility modules
4
+ */
5
+
6
+ export * from "./arrayUtils.core";
7
+ export * from "./arrayUtils.transforms";
8
+ export * from "./arrayUtils.query";
9
+
10
+ /**
11
+ * Create an array of numbers from start to end (inclusive)
12
+ */
13
+ export function range(start: number, end: number, step: number = 1): number[] {
14
+ const result: number[] = [];
15
+ for (let i = start; step > 0 ? i <= end : i >= end; i += step) {
16
+ result.push(i);
17
+ }
18
+ return result;
19
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Shared Utilities
3
+ */
4
+
5
+ export * from "./arrayUtils";
6
+ export * from "./BaseError";
7
+ export * from "./InsufficientCreditsError";
8
+ export * from "./Logger";
9
+ export * from "./numberUtils";
10
+ export * from "./Result";
11
+ export * from "./stringUtils";
12
+ export * from "./SubscriptionConfig";
13
+ export * from "./SubscriptionError";
14
+ export * from "./validators";
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Number Utilities - Aggregation
3
+ * Array aggregation functions for numbers
4
+ */
5
+
6
+ /**
7
+ * Calculate the sum of an array of numbers
8
+ */
9
+ export function sum(values: number[]): number {
10
+ return values.reduce((acc, val) => acc + val, 0);
11
+ }
12
+
13
+ /**
14
+ * Calculate the average of an array of numbers
15
+ */
16
+ export function average(values: number[]): number {
17
+ if (values.length === 0) return 0;
18
+ return sum(values) / values.length;
19
+ }
20
+
21
+ /**
22
+ * Find the minimum value in an array
23
+ */
24
+ export function min(values: number[]): number | null {
25
+ if (values.length === 0) return null;
26
+ return Math.min(...values);
27
+ }
28
+
29
+ /**
30
+ * Find the maximum value in an array
31
+ */
32
+ export function max(values: number[]): number | null {
33
+ if (values.length === 0) return null;
34
+ return Math.max(...values);
35
+ }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Number Utilities - Core Operations
3
+ * Basic numeric calculation functions
4
+ */
5
+
6
+ /**
7
+ * Clamp a number between min and max values
8
+ */
9
+ export function clamp(value: number, min: number, max: number): number {
10
+ return Math.min(Math.max(value, min), max);
11
+ }
12
+
13
+ /**
14
+ * Round a number to specified decimal places
15
+ */
16
+ export function roundTo(value: number, decimals: number = 2): number {
17
+ const multiplier = Math.pow(10, decimals);
18
+ return Math.round(value * multiplier) / multiplier;
19
+ }
20
+
21
+ /**
22
+ * Calculate percentage
23
+ */
24
+ export function calculatePercentage(value: number, total: number): number {
25
+ if (total === 0) return 0;
26
+ return (value / total) * 100;
27
+ }
28
+
29
+ /**
30
+ * Calculate percentage and clamp between 0-100
31
+ */
32
+ export function calculatePercentageClamped(value: number, total: number): number {
33
+ return clamp(calculatePercentage(value, total), 0, 100);
34
+ }
35
+
36
+ /**
37
+ * Check if two numbers are approximately equal (within epsilon)
38
+ */
39
+ export function isApproximatelyEqual(a: number, b: number, epsilon: number = 0.0001): boolean {
40
+ return Math.abs(a - b) < epsilon;
41
+ }
42
+
43
+ /**
44
+ * Safe division that returns 0 instead of NaN
45
+ */
46
+ export function safeDivide(numerator: number, denominator: number): number {
47
+ if (denominator === 0) return 0;
48
+ return numerator / denominator;
49
+ }
50
+
51
+ /**
52
+ * Calculate remaining value after subtraction with floor at 0
53
+ */
54
+ export function calculateRemaining(current: number, cost: number): number {
55
+ return Math.max(0, current - cost);
56
+ }
57
+
58
+ /**
59
+ * Check if user can afford a cost
60
+ */
61
+ export function canAfford(balance: number | null | undefined, cost: number): boolean {
62
+ if (balance === null || balance === undefined) return false;
63
+ return balance >= cost;
64
+ }
65
+
66
+ /**
67
+ * Calculate credit percentage for UI display
68
+ */
69
+ export function calculateCreditPercentage(current: number | null | undefined, max: number): number {
70
+ if (current === null || current === undefined) return 0;
71
+ if (max <= 0) return 100;
72
+ return calculatePercentageClamped(current, max);
73
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Number Utilities - Formatting
3
+ * Number formatting and display functions
4
+ */
5
+
6
+ /**
7
+ * Format number with thousands separator
8
+ */
9
+ export function formatNumber(value: number, locale: string = "en-US"): string {
10
+ return new Intl.NumberFormat(locale).format(value);
11
+ }
12
+
13
+ /**
14
+ * Format number as compact string (e.g., 1K, 1M, 1B)
15
+ */
16
+ export function formatCompactNumber(value: number, locale: string = "en-US"): string {
17
+ return new Intl.NumberFormat(locale, {
18
+ notation: "compact",
19
+ compactDisplay: "short",
20
+ }).format(value);
21
+ }
22
+
23
+ /**
24
+ * Round to nearest multiple (e.g., round to nearest 0.99)
25
+ */
26
+ export function roundToNearest(value: number, multiple: number): number {
27
+ return Math.round(value / multiple) * multiple;
28
+ }
29
+
30
+ /**
31
+ * Floor to nearest multiple
32
+ */
33
+ export function floorToNearest(value: number, multiple: number): number {
34
+ return Math.floor(value / multiple) * multiple;
35
+ }
36
+
37
+ /**
38
+ * Ceiling to nearest multiple
39
+ */
40
+ export function ceilToNearest(value: number, multiple: number): number {
41
+ return Math.ceil(value / multiple) * multiple;
42
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Number Utilities - Math Operations
3
+ * Advanced mathematical operations
4
+ */
5
+
6
+ import { clamp } from "./numberUtils.core";
7
+
8
+ /**
9
+ * Linear interpolation between two values
10
+ */
11
+ export function lerp(start: number, end: number, progress: number): number {
12
+ return start + (end - start) * clamp(progress, 0, 1);
13
+ }
14
+
15
+ /**
16
+ * Map a value from one range to another
17
+ */
18
+ export function mapRange(
19
+ value: number,
20
+ inMin: number,
21
+ inMax: number,
22
+ outMin: number,
23
+ outMax: number
24
+ ): number {
25
+ const progress = (value - inMin) / (inMax - inMin);
26
+ return lerp(outMin, outMax, progress);
27
+ }
28
+
29
+ /**
30
+ * Check if a number is within range (inclusive)
31
+ */
32
+ export function isInRange(value: number, min: number, max: number): boolean {
33
+ return value >= min && value <= max;
34
+ }
35
+
36
+ /**
37
+ * Calculate tax amount
38
+ */
39
+ export function calculateTax(subtotal: number, taxRate: number): number {
40
+ return subtotal * (taxRate / 100);
41
+ }
42
+
43
+ /**
44
+ * Calculate total with tax
45
+ */
46
+ export function calculateTotalWithTax(subtotal: number, taxRate: number): number {
47
+ return subtotal + calculateTax(subtotal, taxRate);
48
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Number Utilities
3
+ * Re-exports all number utility modules
4
+ */
5
+
6
+ export * from "./numberUtils.aggregate";
7
+ export * from "./numberUtils.core";
8
+ export * from "./numberUtils.format";
9
+ export * from "./numberUtils.math";
@@ -0,0 +1,64 @@
1
+ /**
2
+ * String Utilities - Case Conversion
3
+ * String case transformation functions
4
+ */
5
+
6
+ /**
7
+ * Capitalize first letter of a string
8
+ */
9
+ export function capitalize(str: string): string {
10
+ if (!str) return "";
11
+ return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
12
+ }
13
+
14
+ /**
15
+ * Capitalize first letter of each word
16
+ */
17
+ export function titleCase(str: string): string {
18
+ if (!str) return "";
19
+ return str
20
+ .toLowerCase()
21
+ .split(" ")
22
+ .map((word) => capitalize(word))
23
+ .join(" ");
24
+ }
25
+
26
+ /**
27
+ * Convert string to kebab-case
28
+ */
29
+ export function kebabCase(str: string): string {
30
+ if (!str) return "";
31
+ return str
32
+ .replace(/([a-z])([A-Z])/g, "$1-$2")
33
+ .replace(/[\s_]+/g, "-")
34
+ .toLowerCase();
35
+ }
36
+
37
+ /**
38
+ * Convert string to snake_case
39
+ */
40
+ export function snakeCase(str: string): string {
41
+ if (!str) return "";
42
+ return str
43
+ .replace(/([a-z])([A-Z])/g, "$1_$2")
44
+ .replace(/[\s-]+/g, "_")
45
+ .toLowerCase();
46
+ }
47
+
48
+ /**
49
+ * Convert string to camelCase
50
+ */
51
+ export function camelCase(str: string): string {
52
+ if (!str) return "";
53
+ return str
54
+ .replace(/[-_\s]+(.)?/g, (_, char) => (char ? char.toUpperCase() : ""))
55
+ .replace(/^[A-Z]/, (char) => char.toLowerCase());
56
+ }
57
+
58
+ /**
59
+ * Convert string to PascalCase
60
+ */
61
+ export function pascalCase(str: string): string {
62
+ if (!str) return "";
63
+ return capitalize(camelCase(str));
64
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * String Utilities - Check Operations
3
+ * String validation and comparison functions
4
+ */
5
+
6
+ /**
7
+ * Check if value is a non-empty string
8
+ */
9
+ function isNonEmptyString(value: unknown): value is string {
10
+ return typeof value === "string" && value.trim().length > 0;
11
+ }
12
+
13
+ /**
14
+ * Check if string contains a substring (case insensitive)
15
+ */
16
+ export function containsIgnoreCase(str: string, search: string): boolean {
17
+ return str.toLowerCase().includes(search.toLowerCase());
18
+ }
19
+
20
+ /**
21
+ * Check if string starts with a prefix (case insensitive)
22
+ */
23
+ export function startsWithIgnoreCase(str: string, prefix: string): boolean {
24
+ return str.toLowerCase().startsWith(prefix.toLowerCase());
25
+ }
26
+
27
+ /**
28
+ * Check if string ends with a suffix (case insensitive)
29
+ */
30
+ export function endsWithIgnoreCase(str: string, suffix: string): boolean {
31
+ return str.toLowerCase().endsWith(suffix.toLowerCase());
32
+ }
33
+
34
+ /**
35
+ * Check if a string is a valid email (basic validation)
36
+ */
37
+ export function isValidEmailString(value: unknown): value is string {
38
+ if (!isNonEmptyString(value)) {
39
+ return false;
40
+ }
41
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
42
+ return emailRegex.test(value);
43
+ }
44
+
45
+ /**
46
+ * Check if value is a valid URL (basic validation)
47
+ */
48
+ export function isValidUrlString(value: unknown): value is string {
49
+ if (!isNonEmptyString(value)) {
50
+ return false;
51
+ }
52
+ try {
53
+ new URL(value as string);
54
+ return true;
55
+ } catch {
56
+ return false;
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Check if a string is a valid product identifier
62
+ */
63
+ export function isValidProductIdString(value: unknown): value is string {
64
+ return isNonEmptyString(value) && /^[a-zA-Z0-9._-]+$/.test(value as string);
65
+ }