@umituz/react-native-ai-fal-provider 1.0.96 → 1.0.98
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/package.json +1 -1
- package/src/infrastructure/utils/cost-tracker-queries.ts +67 -0
- package/src/infrastructure/utils/cost-tracker.ts +10 -58
- package/src/infrastructure/utils/general-helpers.util.ts +45 -0
- package/src/infrastructure/utils/helpers.util.ts +28 -150
- package/src/infrastructure/utils/image-helpers.util.ts +40 -0
- package/src/infrastructure/utils/prompt-helpers.util.ts +21 -0
- package/src/infrastructure/utils/timing-helpers.util.ts +56 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-ai-fal-provider",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.98",
|
|
4
4
|
"description": "FAL AI provider for React Native - implements IAIProvider interface for unified AI generation",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cost Tracker Queries
|
|
3
|
+
* Query functions for cost history analysis
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { GenerationCost, CostSummary } from "../../domain/entities/cost-tracking.types";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Calculate cost summary from history
|
|
10
|
+
*/
|
|
11
|
+
export function calculateCostSummary(
|
|
12
|
+
costHistory: readonly GenerationCost[],
|
|
13
|
+
currency: string
|
|
14
|
+
): CostSummary {
|
|
15
|
+
const completedCosts = costHistory.filter((c) => c.actualCost > 0);
|
|
16
|
+
const totalCost = completedCosts.reduce((sum, c) => sum + c.actualCost, 0);
|
|
17
|
+
const totalGenerations = completedCosts.length;
|
|
18
|
+
const averageCost = totalGenerations > 0 ? totalCost / totalGenerations : 0;
|
|
19
|
+
|
|
20
|
+
const modelBreakdown: Record<string, number> = {};
|
|
21
|
+
const operationBreakdown: Record<string, number> = {};
|
|
22
|
+
|
|
23
|
+
for (const cost of completedCosts) {
|
|
24
|
+
modelBreakdown[cost.model] = (modelBreakdown[cost.model] ?? 0) + cost.actualCost;
|
|
25
|
+
operationBreakdown[cost.operation] = (operationBreakdown[cost.operation] ?? 0) + cost.actualCost;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
totalCost,
|
|
30
|
+
totalGenerations,
|
|
31
|
+
averageCost,
|
|
32
|
+
currency,
|
|
33
|
+
modelBreakdown,
|
|
34
|
+
operationBreakdown,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Filter costs by model
|
|
40
|
+
*/
|
|
41
|
+
export function filterCostsByModel(
|
|
42
|
+
costHistory: readonly GenerationCost[],
|
|
43
|
+
modelId: string
|
|
44
|
+
): GenerationCost[] {
|
|
45
|
+
return costHistory.filter((c) => c.model === modelId);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Filter costs by operation type
|
|
50
|
+
*/
|
|
51
|
+
export function filterCostsByOperation(
|
|
52
|
+
costHistory: readonly GenerationCost[],
|
|
53
|
+
operation: string
|
|
54
|
+
): GenerationCost[] {
|
|
55
|
+
return costHistory.filter((c) => c.operation === operation);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Filter costs by time range
|
|
60
|
+
*/
|
|
61
|
+
export function filterCostsByTimeRange(
|
|
62
|
+
costHistory: readonly GenerationCost[],
|
|
63
|
+
startTime: number,
|
|
64
|
+
endTime: number
|
|
65
|
+
): GenerationCost[] {
|
|
66
|
+
return costHistory.filter((c) => c.timestamp >= startTime && c.timestamp <= endTime);
|
|
67
|
+
}
|
|
@@ -10,6 +10,12 @@ import type {
|
|
|
10
10
|
ModelCostInfo,
|
|
11
11
|
} from "../../domain/entities/cost-tracking.types";
|
|
12
12
|
import { findModelById } from "../../domain/constants/default-models.constants";
|
|
13
|
+
import {
|
|
14
|
+
calculateCostSummary,
|
|
15
|
+
filterCostsByModel,
|
|
16
|
+
filterCostsByOperation,
|
|
17
|
+
filterCostsByTimeRange,
|
|
18
|
+
} from "./cost-tracker-queries";
|
|
13
19
|
|
|
14
20
|
declare const __DEV__: boolean | undefined;
|
|
15
21
|
|
|
@@ -27,9 +33,6 @@ export class CostTracker {
|
|
|
27
33
|
};
|
|
28
34
|
}
|
|
29
35
|
|
|
30
|
-
/**
|
|
31
|
-
* Get cost information for a model
|
|
32
|
-
*/
|
|
33
36
|
getModelCostInfo(modelId: string): ModelCostInfo {
|
|
34
37
|
const model = findModelById(modelId);
|
|
35
38
|
|
|
@@ -52,17 +55,11 @@ export class CostTracker {
|
|
|
52
55
|
};
|
|
53
56
|
}
|
|
54
57
|
|
|
55
|
-
/**
|
|
56
|
-
* Calculate estimated cost for a generation
|
|
57
|
-
*/
|
|
58
58
|
calculateEstimatedCost(modelId: string): number {
|
|
59
59
|
const costInfo = this.getModelCostInfo(modelId);
|
|
60
60
|
return costInfo.costPerRequest;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
/**
|
|
64
|
-
* Start tracking a generation operation
|
|
65
|
-
*/
|
|
66
63
|
startOperation(modelId: string, operation: string): string {
|
|
67
64
|
const operationId = `${Date.now()}-${operation}`;
|
|
68
65
|
const estimatedCost = this.calculateEstimatedCost(modelId);
|
|
@@ -86,9 +83,6 @@ export class CostTracker {
|
|
|
86
83
|
return operationId;
|
|
87
84
|
}
|
|
88
85
|
|
|
89
|
-
/**
|
|
90
|
-
* Complete tracking for a generation operation
|
|
91
|
-
*/
|
|
92
86
|
completeOperation(
|
|
93
87
|
operationId: string,
|
|
94
88
|
modelId: string,
|
|
@@ -120,70 +114,28 @@ export class CostTracker {
|
|
|
120
114
|
return cost;
|
|
121
115
|
}
|
|
122
116
|
|
|
123
|
-
/**
|
|
124
|
-
* Get cost summary for all tracked operations
|
|
125
|
-
*/
|
|
126
117
|
getCostSummary(): CostSummary {
|
|
127
|
-
|
|
128
|
-
const totalCost = completedCosts.reduce((sum, c) => sum + c.actualCost, 0);
|
|
129
|
-
const totalGenerations = completedCosts.length;
|
|
130
|
-
const averageCost = totalGenerations > 0 ? totalCost / totalGenerations : 0;
|
|
131
|
-
|
|
132
|
-
const modelBreakdown: Record<string, number> = {};
|
|
133
|
-
const operationBreakdown: Record<string, number> = {};
|
|
134
|
-
|
|
135
|
-
for (const cost of completedCosts) {
|
|
136
|
-
modelBreakdown[cost.model] =
|
|
137
|
-
(modelBreakdown[cost.model] ?? 0) + cost.actualCost;
|
|
138
|
-
operationBreakdown[cost.operation] =
|
|
139
|
-
(operationBreakdown[cost.operation] ?? 0) + cost.actualCost;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
return {
|
|
143
|
-
totalCost,
|
|
144
|
-
totalGenerations,
|
|
145
|
-
averageCost,
|
|
146
|
-
currency: this.config.currency,
|
|
147
|
-
modelBreakdown,
|
|
148
|
-
operationBreakdown,
|
|
149
|
-
};
|
|
118
|
+
return calculateCostSummary(this.costHistory, this.config.currency);
|
|
150
119
|
}
|
|
151
120
|
|
|
152
|
-
/**
|
|
153
|
-
* Get cost history
|
|
154
|
-
*/
|
|
155
121
|
getCostHistory(): readonly GenerationCost[] {
|
|
156
122
|
return this.costHistory;
|
|
157
123
|
}
|
|
158
124
|
|
|
159
|
-
/**
|
|
160
|
-
* Clear cost history
|
|
161
|
-
*/
|
|
162
125
|
clearHistory(): void {
|
|
163
126
|
this.costHistory = [];
|
|
164
127
|
this.currentOperationCosts.clear();
|
|
165
128
|
}
|
|
166
129
|
|
|
167
|
-
/**
|
|
168
|
-
* Get costs for a specific model
|
|
169
|
-
*/
|
|
170
130
|
getCostsByModel(modelId: string): GenerationCost[] {
|
|
171
|
-
return this.costHistory
|
|
131
|
+
return filterCostsByModel(this.costHistory, modelId);
|
|
172
132
|
}
|
|
173
133
|
|
|
174
|
-
/**
|
|
175
|
-
* Get costs for a specific operation type
|
|
176
|
-
*/
|
|
177
134
|
getCostsByOperation(operation: string): GenerationCost[] {
|
|
178
|
-
return this.costHistory
|
|
135
|
+
return filterCostsByOperation(this.costHistory, operation);
|
|
179
136
|
}
|
|
180
137
|
|
|
181
|
-
/**
|
|
182
|
-
* Get costs for a specific time range
|
|
183
|
-
*/
|
|
184
138
|
getCostsByTimeRange(startTime: number, endTime: number): GenerationCost[] {
|
|
185
|
-
return this.costHistory
|
|
186
|
-
(c) => c.timestamp >= startTime && c.timestamp <= endTime,
|
|
187
|
-
);
|
|
139
|
+
return filterCostsByTimeRange(this.costHistory, startTime, endTime);
|
|
188
140
|
}
|
|
189
141
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* General Helper Utilities
|
|
3
|
+
* Common utility functions
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Format credit cost for display
|
|
8
|
+
*/
|
|
9
|
+
export function formatCreditCost(cost: number): string {
|
|
10
|
+
if (cost % 1 === 0) {
|
|
11
|
+
return cost.toString();
|
|
12
|
+
}
|
|
13
|
+
return cost.toFixed(2);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Build error message with context
|
|
18
|
+
*/
|
|
19
|
+
export function buildErrorMessage(
|
|
20
|
+
type: string,
|
|
21
|
+
context: Record<string, unknown>
|
|
22
|
+
): string {
|
|
23
|
+
const contextStr = Object.entries(context)
|
|
24
|
+
.map(([key, value]) => `${key}=${JSON.stringify(value)}`)
|
|
25
|
+
.join(", ");
|
|
26
|
+
return `${type}${contextStr ? ` (${contextStr})` : ""}`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Check if value is defined (not null or undefined)
|
|
31
|
+
*/
|
|
32
|
+
export function isDefined<T>(value: T | null | undefined): value is T {
|
|
33
|
+
return value !== null && value !== undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Filter out null and undefined values from object
|
|
38
|
+
*/
|
|
39
|
+
export function removeNullish<T extends Record<string, unknown>>(
|
|
40
|
+
obj: T
|
|
41
|
+
): Partial<T> {
|
|
42
|
+
return Object.fromEntries(
|
|
43
|
+
Object.entries(obj).filter(([_, value]) => isDefined(value))
|
|
44
|
+
) as Partial<T>;
|
|
45
|
+
}
|
|
@@ -1,151 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Helper Utilities
|
|
3
|
-
*
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
*/
|
|
31
|
-
export function getDataUriExtension(dataUri: string): string | null {
|
|
32
|
-
const match = dataUri.match(/^data:image\/(\w+);base64/);
|
|
33
|
-
return match ? match[1] : null;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Check if data URI is an image
|
|
38
|
-
*/
|
|
39
|
-
export function isImageDataUri(value: string): boolean {
|
|
40
|
-
return value.startsWith("data:image/");
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Calculate timeout with jitter to avoid thundering herd
|
|
45
|
-
*/
|
|
46
|
-
export function calculateTimeoutWithJitter(
|
|
47
|
-
baseTimeout: number,
|
|
48
|
-
jitterPercent: number = 0.1
|
|
49
|
-
): number {
|
|
50
|
-
const jitter = baseTimeout * jitterPercent;
|
|
51
|
-
const randomJitter = Math.random() * jitter - jitter / 2;
|
|
52
|
-
return Math.max(1000, baseTimeout + randomJitter);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Format credit cost for display
|
|
57
|
-
*/
|
|
58
|
-
export function formatCreditCost(cost: number): string {
|
|
59
|
-
if (cost % 1 === 0) {
|
|
60
|
-
return cost.toString();
|
|
61
|
-
}
|
|
62
|
-
return cost.toFixed(2);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Truncate prompt to maximum length
|
|
67
|
-
*/
|
|
68
|
-
export function truncatePrompt(prompt: string, maxLength: number = 5000): string {
|
|
69
|
-
if (prompt.length <= maxLength) {
|
|
70
|
-
return prompt;
|
|
71
|
-
}
|
|
72
|
-
return prompt.slice(0, maxLength - 3) + "...";
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Sanitize prompt by removing excessive whitespace
|
|
77
|
-
*/
|
|
78
|
-
export function sanitizePrompt(prompt: string): string {
|
|
79
|
-
return prompt.trim().replace(/\s+/g, " ");
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Build error message with context
|
|
84
|
-
*/
|
|
85
|
-
export function buildErrorMessage(
|
|
86
|
-
type: string,
|
|
87
|
-
context: Record<string, unknown>
|
|
88
|
-
): string {
|
|
89
|
-
const contextStr = Object.entries(context)
|
|
90
|
-
.map(([key, value]) => `${key}=${JSON.stringify(value)}`)
|
|
91
|
-
.join(", ");
|
|
92
|
-
return `${type}${contextStr ? ` (${contextStr})` : ""}`;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Check if value is defined (not null or undefined)
|
|
97
|
-
*/
|
|
98
|
-
export function isDefined<T>(value: T | null | undefined): value is T {
|
|
99
|
-
return value !== null && value !== undefined;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Filter out null and undefined values from object
|
|
104
|
-
*/
|
|
105
|
-
export function removeNullish<T extends Record<string, unknown>>(
|
|
106
|
-
obj: T
|
|
107
|
-
): Partial<T> {
|
|
108
|
-
return Object.fromEntries(
|
|
109
|
-
Object.entries(obj).filter(([_, value]) => isDefined(value))
|
|
110
|
-
) as Partial<T>;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Debounce function (for rate limiting)
|
|
115
|
-
*/
|
|
116
|
-
export function debounce<T extends (...args: never[]) => unknown>(
|
|
117
|
-
func: T,
|
|
118
|
-
wait: number
|
|
119
|
-
): (...args: Parameters<T>) => void {
|
|
120
|
-
let timeout: ReturnType<typeof setTimeout> | null = null;
|
|
121
|
-
|
|
122
|
-
return function executedFunction(...args: Parameters<T>) {
|
|
123
|
-
const later = () => {
|
|
124
|
-
timeout = null;
|
|
125
|
-
func(...args);
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
if (timeout) {
|
|
129
|
-
clearTimeout(timeout);
|
|
130
|
-
}
|
|
131
|
-
timeout = setTimeout(later, wait);
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Simple throttle function
|
|
137
|
-
*/
|
|
138
|
-
export function throttle<T extends (...args: never[]) => unknown>(
|
|
139
|
-
func: T,
|
|
140
|
-
limit: number
|
|
141
|
-
): (...args: Parameters<T>) => void {
|
|
142
|
-
let inThrottle = false;
|
|
143
|
-
|
|
144
|
-
return function executedFunction(...args: Parameters<T>) {
|
|
145
|
-
if (!inThrottle) {
|
|
146
|
-
func(...args);
|
|
147
|
-
inThrottle = true;
|
|
148
|
-
setTimeout(() => (inThrottle = false), limit);
|
|
149
|
-
}
|
|
150
|
-
};
|
|
151
|
-
}
|
|
2
|
+
* Helper Utilities - Re-exports
|
|
3
|
+
* Backward compatibility barrel file
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export {
|
|
7
|
+
formatImageDataUri,
|
|
8
|
+
extractBase64,
|
|
9
|
+
getDataUriExtension,
|
|
10
|
+
isImageDataUri,
|
|
11
|
+
} from "./image-helpers.util";
|
|
12
|
+
|
|
13
|
+
export {
|
|
14
|
+
truncatePrompt,
|
|
15
|
+
sanitizePrompt,
|
|
16
|
+
} from "./prompt-helpers.util";
|
|
17
|
+
|
|
18
|
+
export {
|
|
19
|
+
calculateTimeoutWithJitter,
|
|
20
|
+
debounce,
|
|
21
|
+
throttle,
|
|
22
|
+
} from "./timing-helpers.util";
|
|
23
|
+
|
|
24
|
+
export {
|
|
25
|
+
formatCreditCost,
|
|
26
|
+
buildErrorMessage,
|
|
27
|
+
isDefined,
|
|
28
|
+
removeNullish,
|
|
29
|
+
} from "./general-helpers.util";
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image Helper Utilities
|
|
3
|
+
* Functions for image data URI manipulation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Format image as data URI if not already formatted
|
|
8
|
+
*/
|
|
9
|
+
export function formatImageDataUri(base64: string): string {
|
|
10
|
+
if (base64.startsWith("data:")) {
|
|
11
|
+
return base64;
|
|
12
|
+
}
|
|
13
|
+
return `data:image/jpeg;base64,${base64}`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Extract base64 from data URI
|
|
18
|
+
*/
|
|
19
|
+
export function extractBase64(dataUri: string): string {
|
|
20
|
+
if (!dataUri.startsWith("data:")) {
|
|
21
|
+
return dataUri;
|
|
22
|
+
}
|
|
23
|
+
const parts = dataUri.split(",");
|
|
24
|
+
return parts.length > 1 ? parts[1] : dataUri;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Get file extension from data URI
|
|
29
|
+
*/
|
|
30
|
+
export function getDataUriExtension(dataUri: string): string | null {
|
|
31
|
+
const match = dataUri.match(/^data:image\/(\w+);base64/);
|
|
32
|
+
return match ? match[1] : null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Check if data URI is an image
|
|
37
|
+
*/
|
|
38
|
+
export function isImageDataUri(value: string): boolean {
|
|
39
|
+
return value.startsWith("data:image/");
|
|
40
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompt Helper Utilities
|
|
3
|
+
* Functions for prompt manipulation and sanitization
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Truncate prompt to maximum length
|
|
8
|
+
*/
|
|
9
|
+
export function truncatePrompt(prompt: string, maxLength: number = 5000): string {
|
|
10
|
+
if (prompt.length <= maxLength) {
|
|
11
|
+
return prompt;
|
|
12
|
+
}
|
|
13
|
+
return prompt.slice(0, maxLength - 3) + "...";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Sanitize prompt by removing excessive whitespace
|
|
18
|
+
*/
|
|
19
|
+
export function sanitizePrompt(prompt: string): string {
|
|
20
|
+
return prompt.trim().replace(/\s+/g, " ");
|
|
21
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Timing Helper Utilities
|
|
3
|
+
* Functions for timing, debouncing, and throttling
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Calculate timeout with jitter to avoid thundering herd
|
|
8
|
+
*/
|
|
9
|
+
export function calculateTimeoutWithJitter(
|
|
10
|
+
baseTimeout: number,
|
|
11
|
+
jitterPercent: number = 0.1
|
|
12
|
+
): number {
|
|
13
|
+
const jitter = baseTimeout * jitterPercent;
|
|
14
|
+
const randomJitter = Math.random() * jitter - jitter / 2;
|
|
15
|
+
return Math.max(1000, baseTimeout + randomJitter);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Debounce function (for rate limiting)
|
|
20
|
+
*/
|
|
21
|
+
export function debounce<T extends (...args: never[]) => unknown>(
|
|
22
|
+
func: T,
|
|
23
|
+
wait: number
|
|
24
|
+
): (...args: Parameters<T>) => void {
|
|
25
|
+
let timeout: ReturnType<typeof setTimeout> | null = null;
|
|
26
|
+
|
|
27
|
+
return function executedFunction(...args: Parameters<T>) {
|
|
28
|
+
const later = () => {
|
|
29
|
+
timeout = null;
|
|
30
|
+
func(...args);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
if (timeout) {
|
|
34
|
+
clearTimeout(timeout);
|
|
35
|
+
}
|
|
36
|
+
timeout = setTimeout(later, wait);
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Simple throttle function
|
|
42
|
+
*/
|
|
43
|
+
export function throttle<T extends (...args: never[]) => unknown>(
|
|
44
|
+
func: T,
|
|
45
|
+
limit: number
|
|
46
|
+
): (...args: Parameters<T>) => void {
|
|
47
|
+
let inThrottle = false;
|
|
48
|
+
|
|
49
|
+
return function executedFunction(...args: Parameters<T>) {
|
|
50
|
+
if (!inThrottle) {
|
|
51
|
+
func(...args);
|
|
52
|
+
inThrottle = true;
|
|
53
|
+
setTimeout(() => (inThrottle = false), limit);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}
|