inviton-powerduck 0.0.152 → 0.0.154
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.
|
@@ -92,7 +92,7 @@ export class ExcelJsProvider {
|
|
|
92
92
|
const mySelf = this;
|
|
93
93
|
workbook.xlsx
|
|
94
94
|
.writeBuffer()
|
|
95
|
-
.then((data:
|
|
95
|
+
.then((data: any) => {
|
|
96
96
|
const blob = new Blob([data], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
|
|
97
97
|
PortalUtils.downloadBlob(
|
|
98
98
|
blob,
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility for deduplicating concurrent async calls and optionally caching results for a short TTL.
|
|
3
|
+
*
|
|
4
|
+
* - Deduplication: If multiple calls are made with the same arguments while one is in-flight,
|
|
5
|
+
* they will all share the same Promise instead of creating multiple requests.
|
|
6
|
+
*
|
|
7
|
+
* - Caching: If a TTL (time-to-live) is specified, the last successful (or optionally failed) result
|
|
8
|
+
* will be reused for repeated calls within the TTL window.
|
|
9
|
+
*
|
|
10
|
+
* Example usage:
|
|
11
|
+
*
|
|
12
|
+
* const apiCaller = new ThrottledApiCaller(fetchCart, { ttlMs: 5000 });
|
|
13
|
+
* const result = await apiCaller.call({ userId: "abc" });
|
|
14
|
+
*/
|
|
15
|
+
export class ThrottledApiCaller<TArgs, TResult> {
|
|
16
|
+
/**
|
|
17
|
+
* Stores in-flight Promises keyed by args hash.
|
|
18
|
+
* Ensures that simultaneous calls with the same args share the same Promise.
|
|
19
|
+
*/
|
|
20
|
+
private inflight = new Map<string, Promise<TResult>>();
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Stores cached results (or errors) along with expiration timestamps.
|
|
24
|
+
* Only used if ttlMs > 0.
|
|
25
|
+
*/
|
|
26
|
+
private cache = new Map<
|
|
27
|
+
string,
|
|
28
|
+
{ value?: TResult; error?: unknown; expiresAt: number }
|
|
29
|
+
>();
|
|
30
|
+
|
|
31
|
+
/** Function used to create a cache key from args. */
|
|
32
|
+
private readonly keyFn: (args: TArgs) => string;
|
|
33
|
+
|
|
34
|
+
/** Time-to-live (ms) for caching results. If 0, no result caching is applied. */
|
|
35
|
+
private readonly ttlMs: number;
|
|
36
|
+
|
|
37
|
+
/** Whether to cache failed results (errors). */
|
|
38
|
+
private readonly cacheErrors: boolean;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @param fn The async function you want to call in a deduped/cached manner.
|
|
42
|
+
* @param opts Options:
|
|
43
|
+
* - keyFn: custom function to turn args into a cache key (default: stable JSON stringify).
|
|
44
|
+
* - ttlMs: cache result duration in ms (default: 0, disabled).
|
|
45
|
+
* - cacheErrors: if true, errors are cached within TTL window (default: false).
|
|
46
|
+
*/
|
|
47
|
+
constructor(
|
|
48
|
+
private readonly fn: (args: TArgs) => Promise<TResult>,
|
|
49
|
+
opts: {
|
|
50
|
+
keyFn?: (args: TArgs) => string;
|
|
51
|
+
ttlMs?: number;
|
|
52
|
+
cacheErrors?: boolean;
|
|
53
|
+
} = {}
|
|
54
|
+
) {
|
|
55
|
+
this.keyFn = opts.keyFn ?? stableKey;
|
|
56
|
+
this.ttlMs = Math.max(0, opts.ttlMs ?? 0);
|
|
57
|
+
this.cacheErrors = !!opts.cacheErrors;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Executes the wrapped function in a deduplicated and optionally cached manner.
|
|
62
|
+
* - If a cached result is still fresh, return it immediately.
|
|
63
|
+
* - If an in-flight request exists for the same args, return that Promise.
|
|
64
|
+
* - Otherwise, run the function, store its Promise in inflight, and cache its result.
|
|
65
|
+
*/
|
|
66
|
+
async call(args: TArgs): Promise<TResult> {
|
|
67
|
+
const key = this.keyFn(args);
|
|
68
|
+
const now = Date.now();
|
|
69
|
+
|
|
70
|
+
// 1. Serve from cache if still valid
|
|
71
|
+
if (this.ttlMs > 0) {
|
|
72
|
+
const hit = this.cache.get(key);
|
|
73
|
+
if (hit && hit.expiresAt > now) {
|
|
74
|
+
if (hit.error !== undefined) throw hit.error;
|
|
75
|
+
return hit.value as TResult;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 2. Share ongoing in-flight call if one exists
|
|
80
|
+
const existing = this.inflight.get(key);
|
|
81
|
+
if (existing) return existing;
|
|
82
|
+
|
|
83
|
+
// 3. Run new request
|
|
84
|
+
const promise = (async () => {
|
|
85
|
+
try {
|
|
86
|
+
const result = await this.fn(args);
|
|
87
|
+
// Cache the result if TTL is set
|
|
88
|
+
if (this.ttlMs > 0) {
|
|
89
|
+
this.cache.set(key, {
|
|
90
|
+
value: result,
|
|
91
|
+
expiresAt: now + this.ttlMs,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
return result;
|
|
95
|
+
} catch (err) {
|
|
96
|
+
// Optionally cache the error too
|
|
97
|
+
if (this.ttlMs > 0 && this.cacheErrors) {
|
|
98
|
+
this.cache.set(key, {
|
|
99
|
+
error: err,
|
|
100
|
+
expiresAt: now + this.ttlMs,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
throw err;
|
|
104
|
+
} finally {
|
|
105
|
+
// Always clear inflight entry when done
|
|
106
|
+
this.inflight.delete(key);
|
|
107
|
+
}
|
|
108
|
+
})();
|
|
109
|
+
|
|
110
|
+
this.inflight.set(key, promise);
|
|
111
|
+
return promise;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Invalidate cache for a specific args set.
|
|
116
|
+
* Next call with these args will trigger a fresh request.
|
|
117
|
+
*/
|
|
118
|
+
invalidate(args: TArgs): void {
|
|
119
|
+
this.cache.delete(this.keyFn(args));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Clear all cache and in-flight requests.
|
|
124
|
+
*/
|
|
125
|
+
clear(): void {
|
|
126
|
+
this.cache.clear();
|
|
127
|
+
this.inflight.clear();
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Default stable key generator for args.
|
|
133
|
+
* Produces a deterministic string even if object keys are in different order.
|
|
134
|
+
*/
|
|
135
|
+
function stableKey(v: unknown): string {
|
|
136
|
+
const seen = new WeakSet();
|
|
137
|
+
const encode = (x: any): any => {
|
|
138
|
+
if (x && typeof x === "object") {
|
|
139
|
+
if (seen.has(x)) return "__cycle__";
|
|
140
|
+
seen.add(x);
|
|
141
|
+
if (Array.isArray(x)) return x.map(encode);
|
|
142
|
+
const out: Record<string, any> = {};
|
|
143
|
+
for (const k of Object.keys(x).sort()) out[k] = encode(x[k]);
|
|
144
|
+
return out;
|
|
145
|
+
}
|
|
146
|
+
return x;
|
|
147
|
+
};
|
|
148
|
+
return JSON.stringify(encode(v));
|
|
149
|
+
}
|
|
@@ -183,7 +183,7 @@ class TableExportModalComponent extends TsxComponent<TableExportModalBindingArgs
|
|
|
183
183
|
});
|
|
184
184
|
|
|
185
185
|
const mySelf = this;
|
|
186
|
-
workbook.xlsx.writeBuffer().then((data:
|
|
186
|
+
workbook.xlsx.writeBuffer().then((data: any) => {
|
|
187
187
|
const blob = new Blob([data], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
|
|
188
188
|
PortalUtils.downloadBlob(
|
|
189
189
|
blob,
|