@talex-touch/utils 1.0.18 → 1.0.21
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/channel/index.ts +49 -1
- package/common/index.ts +2 -0
- package/common/search/gather.ts +45 -0
- package/common/search/index.ts +67 -0
- package/common/storage/constants.ts +16 -2
- package/common/storage/entity/index.ts +2 -1
- package/common/storage/entity/openers.ts +32 -0
- package/common/storage/entity/shortcut-settings.ts +22 -0
- package/common/storage/shortcut-storage.ts +58 -0
- package/common/utils/file.ts +62 -0
- package/common/{utils.ts → utils/index.ts} +14 -2
- package/common/utils/polling.ts +184 -0
- package/common/utils/task-queue.ts +108 -0
- package/common/utils/time.ts +374 -0
- package/core-box/README.md +8 -8
- package/core-box/builder/index.ts +6 -0
- package/core-box/builder/tuff-builder.example.ts.bak +258 -0
- package/core-box/builder/tuff-builder.ts +1162 -0
- package/core-box/index.ts +5 -2
- package/core-box/run-tests.sh +7 -0
- package/core-box/search.ts +1 -536
- package/core-box/tuff/index.ts +6 -0
- package/core-box/tuff/tuff-dsl.ts +1412 -0
- package/electron/clipboard-helper.ts +199 -0
- package/electron/env-tool.ts +36 -2
- package/electron/file-parsers/index.ts +8 -0
- package/electron/file-parsers/parsers/text-parser.ts +109 -0
- package/electron/file-parsers/registry.ts +92 -0
- package/electron/file-parsers/types.ts +58 -0
- package/electron/index.ts +3 -0
- package/eventbus/index.ts +0 -7
- package/index.ts +3 -1
- package/package.json +4 -29
- package/plugin/channel.ts +48 -16
- package/plugin/index.ts +194 -30
- package/plugin/log/types.ts +11 -0
- package/plugin/node/index.ts +4 -0
- package/plugin/node/logger-manager.ts +113 -0
- package/plugin/{log → node}/logger.ts +41 -7
- package/plugin/plugin-source.ts +74 -0
- package/plugin/preload.ts +5 -15
- package/plugin/providers/index.ts +2 -0
- package/plugin/providers/registry.ts +47 -0
- package/plugin/providers/types.ts +54 -0
- package/plugin/risk/index.ts +1 -0
- package/plugin/risk/types.ts +20 -0
- package/plugin/sdk/enum/bridge-event.ts +4 -0
- package/plugin/sdk/enum/index.ts +1 -0
- package/plugin/sdk/hooks/bridge.ts +68 -0
- package/plugin/sdk/hooks/index.ts +2 -1
- package/plugin/sdk/hooks/life-cycle.ts +2 -4
- package/plugin/sdk/index.ts +2 -0
- package/plugin/sdk/storage.ts +84 -0
- package/plugin/sdk/types.ts +2 -2
- package/plugin/sdk/window/index.ts +5 -3
- package/preload/index.ts +2 -0
- package/preload/loading.ts +15 -0
- package/preload/renderer.ts +41 -0
- package/renderer/hooks/arg-mapper.ts +79 -0
- package/renderer/hooks/index.ts +2 -0
- package/renderer/hooks/initialize.ts +198 -0
- package/renderer/index.ts +3 -0
- package/renderer/storage/app-settings.ts +2 -0
- package/renderer/storage/base-storage.ts +1 -0
- package/renderer/storage/openers.ts +11 -0
- package/renderer/touch-sdk/env.ts +106 -0
- package/renderer/touch-sdk/index.ts +108 -0
- package/renderer/touch-sdk/terminal.ts +85 -0
- package/renderer/touch-sdk/utils.ts +61 -0
- package/search/levenshtein-utils.ts +39 -0
- package/search/types.ts +16 -16
- package/types/index.ts +2 -1
- package/types/modules/base.ts +146 -0
- package/types/modules/index.ts +4 -0
- package/types/modules/module-lifecycle.ts +148 -0
- package/types/modules/module-manager.ts +99 -0
- package/types/modules/module.ts +112 -0
- package/types/touch-app-core.ts +16 -93
- package/core-box/types.ts +0 -384
- package/plugin/log/logger-manager.ts +0 -60
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
type ConsoleLike = Pick<typeof console, 'debug' | 'warn'>
|
|
2
|
+
|
|
3
|
+
export interface AdaptiveTaskQueueOptions {
|
|
4
|
+
/**
|
|
5
|
+
* 估算单个任务的执行耗时(毫秒)。用于动态计算每批处理量。
|
|
6
|
+
* 若不确定,可传入 1~2 之间的经验值。
|
|
7
|
+
*/
|
|
8
|
+
estimatedTaskTimeMs?: number
|
|
9
|
+
/**
|
|
10
|
+
* 每次让出事件循环前的最长时间窗口(毫秒),默认约等于一帧的 17ms。
|
|
11
|
+
*/
|
|
12
|
+
yieldIntervalMs?: number
|
|
13
|
+
/**
|
|
14
|
+
* 限制每批最大处理数量,避免估算误差导致批次过大。
|
|
15
|
+
*/
|
|
16
|
+
maxBatchSize?: number
|
|
17
|
+
/**
|
|
18
|
+
* 自定义日志输出目标,默认使用 `console`。
|
|
19
|
+
*/
|
|
20
|
+
logger?: ConsoleLike
|
|
21
|
+
/**
|
|
22
|
+
* 方便调试的标签,会出现在日志里。
|
|
23
|
+
*/
|
|
24
|
+
label?: string
|
|
25
|
+
/**
|
|
26
|
+
* 每次批处理完成后触发,可用于自定义进度上报。
|
|
27
|
+
*/
|
|
28
|
+
onYield?: (context: {
|
|
29
|
+
processed: number
|
|
30
|
+
total: number
|
|
31
|
+
batchSize: number
|
|
32
|
+
elapsedMs: number
|
|
33
|
+
}) => void
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const DEFAULT_YIELD_INTERVAL_MS = 17
|
|
37
|
+
|
|
38
|
+
async function delay(ms: number): Promise<void> {
|
|
39
|
+
if (ms <= 0) return
|
|
40
|
+
await new Promise<void>((resolve) => setTimeout(resolve, ms))
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 以“自适应批处理”方式执行一组任务。
|
|
45
|
+
*
|
|
46
|
+
* - 根据传入的任务量与预估耗时动态计算批大小
|
|
47
|
+
* - 每批处理后,会等待(默认 17ms)以释放事件循环,避免主线程卡顿
|
|
48
|
+
* - 支持在批处理完成时回调进度
|
|
49
|
+
*/
|
|
50
|
+
export async function runAdaptiveTaskQueue<T>(
|
|
51
|
+
items: readonly T[],
|
|
52
|
+
handler: (item: T, index: number) => Promise<void> | void,
|
|
53
|
+
options: AdaptiveTaskQueueOptions = {}
|
|
54
|
+
): Promise<void> {
|
|
55
|
+
const total = items.length
|
|
56
|
+
if (total === 0) return
|
|
57
|
+
|
|
58
|
+
const {
|
|
59
|
+
estimatedTaskTimeMs = 1,
|
|
60
|
+
yieldIntervalMs = DEFAULT_YIELD_INTERVAL_MS,
|
|
61
|
+
maxBatchSize,
|
|
62
|
+
logger = console,
|
|
63
|
+
label = 'AdaptiveTaskQueue',
|
|
64
|
+
onYield
|
|
65
|
+
} = options
|
|
66
|
+
|
|
67
|
+
const safeTaskMs = Math.max(estimatedTaskTimeMs, 0.1)
|
|
68
|
+
const computedBatchSize = Math.max(1, Math.floor(yieldIntervalMs / safeTaskMs))
|
|
69
|
+
const batchSize = maxBatchSize ? Math.min(maxBatchSize, computedBatchSize) : computedBatchSize
|
|
70
|
+
|
|
71
|
+
const currentPerformance = typeof globalThis !== 'undefined' ? (globalThis as any)?.performance : undefined
|
|
72
|
+
const now = () => (currentPerformance ? currentPerformance.now() : Date.now())
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
const startTime = now()
|
|
76
|
+
|
|
77
|
+
logger.debug?.(
|
|
78
|
+
`[${label}] Starting queue for ${total} item(s). batchSize=${batchSize}, estimated=${safeTaskMs.toFixed(2)}ms`
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
for (let index = 0; index < total; index++) {
|
|
82
|
+
await handler(items[index], index)
|
|
83
|
+
|
|
84
|
+
const processed = index + 1
|
|
85
|
+
if (processed % batchSize === 0 && processed < total) {
|
|
86
|
+
const beforeYield = now()
|
|
87
|
+
await delay(yieldIntervalMs)
|
|
88
|
+
const afterYield = now()
|
|
89
|
+
const elapsedMs = beforeYield - startTime
|
|
90
|
+
logger.debug?.(
|
|
91
|
+
`[${label}] Yielded after ${processed}/${total} item(s); wait ${(
|
|
92
|
+
afterYield - beforeYield
|
|
93
|
+
).toFixed(1)}ms`
|
|
94
|
+
)
|
|
95
|
+
onYield?.({
|
|
96
|
+
processed,
|
|
97
|
+
total,
|
|
98
|
+
batchSize,
|
|
99
|
+
elapsedMs
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const endTime = now()
|
|
105
|
+
logger.debug?.(
|
|
106
|
+
`[${label}] Completed ${total} item(s) in ${((endTime - startTime) / 1000).toFixed(2)}s`
|
|
107
|
+
)
|
|
108
|
+
}
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom error class representing an operation timeout.
|
|
3
|
+
* This error is thrown when a Promise does not resolve within a specified time limit.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```ts
|
|
7
|
+
* try {
|
|
8
|
+
* await withTimeout(someLongRunningPromise(), 1000);
|
|
9
|
+
* } catch (error) {
|
|
10
|
+
* if (error instanceof TimeoutError) {
|
|
11
|
+
* console.error('Operation took too long!');
|
|
12
|
+
* }
|
|
13
|
+
* }
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export class TimeoutError extends Error {
|
|
17
|
+
/**
|
|
18
|
+
* Creates an instance of TimeoutError.
|
|
19
|
+
* @param message - An optional message describing the timeout error. Defaults to 'Operation timed out'.
|
|
20
|
+
*/
|
|
21
|
+
constructor(message: string = 'Operation timed out') {
|
|
22
|
+
super(message);
|
|
23
|
+
this.name = 'TimeoutError';
|
|
24
|
+
// Maintain proper stack trace for the error.
|
|
25
|
+
if (Error.captureStackTrace) {
|
|
26
|
+
Error.captureStackTrace(this, TimeoutError);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Executes a promise and throws a TimeoutError if it does not resolve within the specified time.
|
|
33
|
+
* This is a robust alternative to a timeout that resolves with a special value,
|
|
34
|
+
* as it uses idiomatic error handling (`try...catch`).
|
|
35
|
+
*
|
|
36
|
+
* @template T The type of the promise's resolution value.
|
|
37
|
+
* @param promise - The promise to execute.
|
|
38
|
+
* @param ms - The timeout duration in milliseconds. If `ms` is 0 or negative, the original promise is returned without a timeout.
|
|
39
|
+
* @returns A promise that resolves with the result of the input promise, or rejects with a `TimeoutError`.
|
|
40
|
+
* @throws {TimeoutError} If the promise does not resolve within the specified time.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```ts
|
|
44
|
+
* import { withTimeout, TimeoutError } from '@talex-touch/utils';
|
|
45
|
+
*
|
|
46
|
+
* async function fetchDataWithTimeout() {
|
|
47
|
+
* try {
|
|
48
|
+
* const result = await withTimeout(fetch('https://api.example.com/data'), 3000); // 3-second timeout
|
|
49
|
+
* console.log('Data fetched:', await result.json());
|
|
50
|
+
* } catch (error) {
|
|
51
|
+
* if (error instanceof TimeoutError) {
|
|
52
|
+
* console.error('Fetch request timed out!');
|
|
53
|
+
* } else {
|
|
54
|
+
* console.error('Fetch failed:', error);
|
|
55
|
+
* }
|
|
56
|
+
* }
|
|
57
|
+
* }
|
|
58
|
+
*
|
|
59
|
+
* fetchDataWithTimeout();
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export function withTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {
|
|
63
|
+
if (ms <= 0) {
|
|
64
|
+
// If timeout is 0 or negative, return the original Promise without a timeout mechanism.
|
|
65
|
+
return promise;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Create a promise that rejects in <ms> milliseconds
|
|
69
|
+
const timeout = new Promise<never>((_, reject) => {
|
|
70
|
+
const id = setTimeout(() => {
|
|
71
|
+
clearTimeout(id);
|
|
72
|
+
reject(new TimeoutError(`Promise timed out after ${ms} ms`));
|
|
73
|
+
}, ms);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Race the input promise against the timeout promise
|
|
77
|
+
return Promise.race([promise, timeout]);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Represents a value that can either be a fixed type `T` or a function that dynamically
|
|
82
|
+
* computes `T` based on the current attempt number.
|
|
83
|
+
* @template T The type of the value.
|
|
84
|
+
*/
|
|
85
|
+
type DynamicValue<T> = T | ((attempt: number) => T);
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Defines a generic asynchronous function type that returns a Promise.
|
|
89
|
+
* @template T The type of the Promise's resolution value.
|
|
90
|
+
*/
|
|
91
|
+
type AsyncFunction<T> = (...args: any[]) => Promise<T>;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Defines the options for configuring the retrier behavior.
|
|
95
|
+
*/
|
|
96
|
+
export interface RetrierOptions {
|
|
97
|
+
/**
|
|
98
|
+
* The maximum number of retries.
|
|
99
|
+
* - If a `number`, it represents the fixed number of retries (e.g., `2` means 1 initial attempt + 2 retries = total 3 attempts).
|
|
100
|
+
* - If a `function`, it dynamically calculates the maximum retries based on the current attempt number (starting from 1).
|
|
101
|
+
* @defaultValue 2 (meaning 1 initial attempt + 2 retries, total 3 attempts)
|
|
102
|
+
*/
|
|
103
|
+
maxRetries?: DynamicValue<number>;
|
|
104
|
+
/**
|
|
105
|
+
* The timeout duration for each individual attempt in milliseconds.
|
|
106
|
+
* - If a `number`, it represents a fixed timeout for each attempt.
|
|
107
|
+
* - If a `function`, it dynamically calculates the timeout based on the current attempt number.
|
|
108
|
+
* @defaultValue 5000 (5 seconds)
|
|
109
|
+
*/
|
|
110
|
+
timeoutMs?: DynamicValue<number>;
|
|
111
|
+
/**
|
|
112
|
+
* Callback invoked at the beginning of each attempt.
|
|
113
|
+
* @param attempt - The current attempt number (starts from 1).
|
|
114
|
+
* @param error - The error from the previous attempt, if any (undefined for the first attempt).
|
|
115
|
+
*/
|
|
116
|
+
onAttempt?: (attempt: number, error?: Error) => void;
|
|
117
|
+
/**
|
|
118
|
+
* Callback invoked upon successful completion of the operation.
|
|
119
|
+
* @template T The type of the successful result.
|
|
120
|
+
* @param result - The successful result of the operation.
|
|
121
|
+
* @param attempt - The attempt number on which the operation succeeded.
|
|
122
|
+
*/
|
|
123
|
+
onSuccess?: <T>(result: T, attempt: number) => void;
|
|
124
|
+
/**
|
|
125
|
+
* Callback invoked when all retries have been exhausted and the operation ultimately fails.
|
|
126
|
+
* @param error - The final error that caused the operation to fail.
|
|
127
|
+
* @param attempt - The total number of attempts made before final failure.
|
|
128
|
+
*/
|
|
129
|
+
onFailure?: (error: Error, attempt: number) => void;
|
|
130
|
+
/**
|
|
131
|
+
* Callback invoked before initiating a retry, indicating that a retry will occur.
|
|
132
|
+
* @param attempt - The attempt number that just failed and will lead to the next retry.
|
|
133
|
+
* @param error - The error that caused the current attempt to fail.
|
|
134
|
+
*/
|
|
135
|
+
onRetry?: (attempt: number, error: Error) => void;
|
|
136
|
+
/**
|
|
137
|
+
* Callback invoked when a single attempt times out. Note that this is a specific type of failure that leads to a retry.
|
|
138
|
+
* @param attempt - The attempt number that timed out.
|
|
139
|
+
* @param ms - The timeout duration (in milliseconds) that was set for this specific attempt.
|
|
140
|
+
*/
|
|
141
|
+
onTimeout?: (attempt: number, ms: number) => void;
|
|
142
|
+
/**
|
|
143
|
+
* A predicate function that determines whether a given error should trigger a retry.
|
|
144
|
+
* If this function returns `false`, the retrier will immediately stop and throw the error.
|
|
145
|
+
* By default, all errors will trigger a retry (returns `true`).
|
|
146
|
+
* @param error - The error that occurred.
|
|
147
|
+
* @param attempt - The current attempt number.
|
|
148
|
+
* @returns `true` if a retry should occur, `false` otherwise.
|
|
149
|
+
*/
|
|
150
|
+
shouldRetry?: (error: Error, attempt: number) => boolean;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Creates a retrier factory function that can wrap any asynchronous method
|
|
155
|
+
* to add configurable retry logic with timeout and event callbacks.
|
|
156
|
+
*
|
|
157
|
+
* @param options - Configuration options for the retrier.
|
|
158
|
+
* @returns A higher-order function. This returned function takes an asynchronous method
|
|
159
|
+
* and returns a new, wrapped async method with the specified retry logic.
|
|
160
|
+
*
|
|
161
|
+
* @example
|
|
162
|
+
* ```ts
|
|
163
|
+
* import { createRetrier, TimeoutError } from '@talex-touch/utils';
|
|
164
|
+
*
|
|
165
|
+
* // Example of a flaky asynchronous task
|
|
166
|
+
* let globalCallCount = 0;
|
|
167
|
+
* async function flakyApiCall(data: string): Promise<string> {
|
|
168
|
+
* globalCallCount++;
|
|
169
|
+
* console.log(` [API] Attempt ${globalCallCount}: Calling API with ${data}...`);
|
|
170
|
+
* const delay = Math.random() * 1500 + 500; // 500ms to 2000ms
|
|
171
|
+
* await new Promise(resolve => setTimeout(resolve, delay));
|
|
172
|
+
*
|
|
173
|
+
* if (globalCallCount < 3) { // Fail first 2 attempts
|
|
174
|
+
* throw new Error(`API failed on attempt ${globalCallCount}`);
|
|
175
|
+
* }
|
|
176
|
+
* console.log(` [API] Attempt ${globalCallCount}: API call successful for ${data}!`);
|
|
177
|
+
* return `Data received: ${data} on attempt ${globalCallCount}`;
|
|
178
|
+
* }
|
|
179
|
+
*
|
|
180
|
+
* async function runCustomRetrier() {
|
|
181
|
+
* console.log('\n--- Running Custom Retrier Example ---');
|
|
182
|
+
* globalCallCount = 0; // Reset for example
|
|
183
|
+
*
|
|
184
|
+
* const myRetrier = createRetrier({
|
|
185
|
+
* maxRetries: 3, // Total 4 attempts
|
|
186
|
+
* timeoutMs: (attempt) => 1000 + (attempt * 500), // Increasing timeout: 1.5s, 2s, 2.5s, 3s
|
|
187
|
+
* onAttempt: (att, err) => console.log(`[Retrier] Attempt ${att} starting. Last err: ${err?.message || 'N/A'}`),
|
|
188
|
+
* onSuccess: (res, att) => console.log(`[Retrier] ✅ Success on attempt ${att}: ${res}`),
|
|
189
|
+
* onFailure: (err, att) => console.error(`[Retrier] ❌ Final failure after ${att} attempts: ${err.message}`),
|
|
190
|
+
* onRetry: (att, err) => console.warn(`[Retrier] 🔄 Retrying. Attempt ${att} failed with: ${err.message}`),
|
|
191
|
+
* onTimeout: (att, ms) => console.warn(`[Retrier] ⏰ Attempt ${att} timed out after ${ms}ms.`),
|
|
192
|
+
* shouldRetry: (error) => !(error instanceof TypeError), // Don't retry on TypeError
|
|
193
|
+
* });
|
|
194
|
+
*
|
|
195
|
+
* const wrappedFlakyApiCall = myRetrier(flakyApiCall);
|
|
196
|
+
*
|
|
197
|
+
* try {
|
|
198
|
+
* const result = await wrappedFlakyApiCall('item-X');
|
|
199
|
+
* console.log('Final Operation Result:', result);
|
|
200
|
+
* } catch (error: any) {
|
|
201
|
+
* console.error('Operation ultimately failed:', error.message);
|
|
202
|
+
* if (error instanceof TimeoutError) {
|
|
203
|
+
* console.error('Specifically: A timeout occurred.');
|
|
204
|
+
* }
|
|
205
|
+
* }
|
|
206
|
+
* }
|
|
207
|
+
*
|
|
208
|
+
* runCustomRetrier();
|
|
209
|
+
* ```
|
|
210
|
+
*/
|
|
211
|
+
export function createRetrier(options: RetrierOptions = {}) {
|
|
212
|
+
const {
|
|
213
|
+
maxRetries = 2, // Default: 2 retries (total 3 attempts)
|
|
214
|
+
timeoutMs = 5000, // Default: 5 seconds timeout per attempt
|
|
215
|
+
onAttempt,
|
|
216
|
+
onSuccess,
|
|
217
|
+
onFailure,
|
|
218
|
+
onRetry,
|
|
219
|
+
onTimeout,
|
|
220
|
+
shouldRetry = () => true, // Default: retry on all errors
|
|
221
|
+
} = options;
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Resolves a DynamicValue to its concrete value for the given attempt.
|
|
225
|
+
* @template T The type of the dynamic value.
|
|
226
|
+
* @param value - The DynamicValue to resolve.
|
|
227
|
+
* @param attempt - The current attempt number.
|
|
228
|
+
* @returns The concrete value.
|
|
229
|
+
*/
|
|
230
|
+
const resolveDynamicValue = <T_Val>(value: DynamicValue<T_Val>, attempt: number): T_Val => {
|
|
231
|
+
return typeof value === 'function' ? (value as (attempt: number) => T_Val)(attempt) : value;
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* A higher-order function that takes an asynchronous function and returns a new function
|
|
236
|
+
* with retry capabilities applied.
|
|
237
|
+
*
|
|
238
|
+
* @template T The expected return type of the wrapped asynchronous function.
|
|
239
|
+
* @template Func The type of the asynchronous function to be wrapped.
|
|
240
|
+
* @param func - The original asynchronous function to be wrapped with retry logic.
|
|
241
|
+
* @returns A new asynchronous function that, when called, executes the original function
|
|
242
|
+
* with retry attempts and timeout handling as configured.
|
|
243
|
+
*/
|
|
244
|
+
return function <T, Func extends AsyncFunction<T>>(func: Func): ((...args: Parameters<Func>) => Promise<T>) {
|
|
245
|
+
return async function (this: ThisParameterType<Func>, ...args: Parameters<Func>): Promise<T> {
|
|
246
|
+
let currentAttempt = 0;
|
|
247
|
+
let lastError: Error | undefined;
|
|
248
|
+
|
|
249
|
+
// Resolve maxRetries once or dynamically per attempt based on DynamicValue type
|
|
250
|
+
const calculatedMaxRetries = resolveDynamicValue(maxRetries, 1); // Calculate for the first attempt as starting point
|
|
251
|
+
|
|
252
|
+
// Loop through attempts up to the maximum allowed retries
|
|
253
|
+
while (currentAttempt <= calculatedMaxRetries) {
|
|
254
|
+
currentAttempt++;
|
|
255
|
+
onAttempt?.(currentAttempt, lastError); // Notify about current attempt, including previous error if any
|
|
256
|
+
|
|
257
|
+
// Resolve timeoutMs dynamically for the current attempt
|
|
258
|
+
const currentTimeoutMs = resolveDynamicValue(timeoutMs, currentAttempt);
|
|
259
|
+
|
|
260
|
+
try {
|
|
261
|
+
// Execute the original function with its original `this` context and arguments,
|
|
262
|
+
// wrapped by the `withTimeout` utility.
|
|
263
|
+
const result = await withTimeout(func.apply(this, args), currentTimeoutMs);
|
|
264
|
+
onSuccess?.(result, currentAttempt); // Notify success
|
|
265
|
+
return result; // Return the successful result
|
|
266
|
+
} catch (error: any) {
|
|
267
|
+
lastError = error; // Store the error for the next attempt's `onAttempt` callback
|
|
268
|
+
|
|
269
|
+
// Notify if the specific error was a timeout
|
|
270
|
+
if (error instanceof TimeoutError) {
|
|
271
|
+
onTimeout?.(currentAttempt, currentTimeoutMs);
|
|
272
|
+
} else {
|
|
273
|
+
// console.warn(`Attempt ${currentAttempt} failed with error:`, error.message); // Example internal logging
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Determine if a retry should proceed based on the `shouldRetry` predicate and max attempts
|
|
277
|
+
const shouldProceedRetry = shouldRetry(error, currentAttempt);
|
|
278
|
+
|
|
279
|
+
if (!shouldProceedRetry || currentAttempt > calculatedMaxRetries) {
|
|
280
|
+
onFailure?.(error, currentAttempt); // Notify final failure
|
|
281
|
+
throw error; // Throw the error if no more retries or `shouldRetry` returns false
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
onRetry?.(currentAttempt, error); // Notify that a retry will occur
|
|
285
|
+
// Optional: Add a delay here for exponential backoff or other retry strategies.
|
|
286
|
+
// e.g., `await new Promise(resolve => setTimeout(resolve, Math.pow(2, currentAttempt) * 100));`
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
// This line should ideally not be reached if the loop's conditions are correct and errors are always thrown.
|
|
290
|
+
throw new Error('Retrier exhausted all attempts without success. This is an unexpected state.');
|
|
291
|
+
};
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* A convenience function to apply default retry logic to an asynchronous method.
|
|
297
|
+
* It uses sensible default configurations:
|
|
298
|
+
* - `maxRetries`: 2 (meaning 1 initial attempt + 2 retries, total 3 attempts)
|
|
299
|
+
* - `timeoutMs`: 5000 (5 seconds timeout per attempt)
|
|
300
|
+
* - Includes default logging for attempts, success, failure, and retries.
|
|
301
|
+
*
|
|
302
|
+
* @template T The expected return type of the asynchronous function.
|
|
303
|
+
* @template Func The type of the asynchronous function to be wrapped.
|
|
304
|
+
* @param func - The asynchronous method to be wrapped with default retry logic.
|
|
305
|
+
* @returns A new asynchronous method that automatically includes the default retry behavior.
|
|
306
|
+
*
|
|
307
|
+
* @example
|
|
308
|
+
* ```ts
|
|
309
|
+
* import { useRetrier, TimeoutError } from './your-module'; // Assuming your module path
|
|
310
|
+
*
|
|
311
|
+
* // Example of an async operation that might fail randomly
|
|
312
|
+
* let operationCount = 0;
|
|
313
|
+
* async function unstableOperation(): Promise<string> {
|
|
314
|
+
* operationCount++;
|
|
315
|
+
* console.log(` [Op] Executing unstableOperation, attempt ${operationCount}.`);
|
|
316
|
+
* const success = Math.random() > 0.6; // 40% chance of success
|
|
317
|
+
* await new Promise(resolve => setTimeout(resolve, 800 + Math.random() * 500)); // 0.8s to 1.3s delay
|
|
318
|
+
*
|
|
319
|
+
* if (!success && operationCount < 3) { // Fail first 2 attempts often
|
|
320
|
+
* throw new Error(`Unstable operation failed on call ${operationCount}.`);
|
|
321
|
+
* }
|
|
322
|
+
* console.log(` [Op] Unstable operation succeeded on call ${operationCount}.`);
|
|
323
|
+
* return `Data from unstable operation (Call ${operationCount})`;
|
|
324
|
+
* }
|
|
325
|
+
*
|
|
326
|
+
* async function runUseRetrier() {
|
|
327
|
+
* console.log('\n--- Running UseRetrier Example ---');
|
|
328
|
+
* operationCount = 0; // Reset for example
|
|
329
|
+
*
|
|
330
|
+
* const reliableOperation = useRetrier(unstableOperation);
|
|
331
|
+
*
|
|
332
|
+
* try {
|
|
333
|
+
* const result = await reliableOperation();
|
|
334
|
+
* console.log('Final Result (UseRetrier):', result);
|
|
335
|
+
* } catch (error: any) {
|
|
336
|
+
* console.error('Operation failed completely (UseRetrier):', error.message);
|
|
337
|
+
* if (error instanceof TimeoutError) {
|
|
338
|
+
* console.error('Specifically: A timeout occurred for UseRetrier example.');
|
|
339
|
+
* }
|
|
340
|
+
* }
|
|
341
|
+
*
|
|
342
|
+
* // Another attempt, showing success
|
|
343
|
+
* operationCount = 0;
|
|
344
|
+
* try {
|
|
345
|
+
* const result = await reliableOperation();
|
|
346
|
+
* console.log('Final Result (UseRetrier, second run):', result);
|
|
347
|
+
* } catch (error: any) {
|
|
348
|
+
* console.error('Operation failed completely (UseRetrier, second run):', error.message);
|
|
349
|
+
* }
|
|
350
|
+
* }
|
|
351
|
+
*
|
|
352
|
+
* runUseRetrier();
|
|
353
|
+
* ```
|
|
354
|
+
*/
|
|
355
|
+
export function useRetrier<T, Func extends AsyncFunction<T>>(func: Func): ((...args: Parameters<Func>) => Promise<T>) {
|
|
356
|
+
// Create a retrier instance with default options and common logging callbacks.
|
|
357
|
+
const defaultRetrier = createRetrier({
|
|
358
|
+
maxRetries: 2, // Default: 2 retries
|
|
359
|
+
timeoutMs: 5000, // Default: 5 seconds timeout
|
|
360
|
+
onAttempt: (attempt, error) => {
|
|
361
|
+
if (attempt > 1) {
|
|
362
|
+
console.log(`[UseRetrier Default] Attempt ${attempt} (after previous error: ${error?.message || 'None'})...`);
|
|
363
|
+
} else {
|
|
364
|
+
console.log(`[UseRetrier Default] Starting initial attempt ${attempt}...`);
|
|
365
|
+
}
|
|
366
|
+
},
|
|
367
|
+
onSuccess: (result, attempt) => console.log(`[UseRetrier Default] ✅ Succeeded on attempt ${attempt}. Result:`, result),
|
|
368
|
+
onFailure: (error, attempt) => console.error(`[UseRetrier Default] ❌ Failed after ${attempt} attempts. Final Error:`, error.message),
|
|
369
|
+
onRetry: (attempt, error) => console.warn(`[UseRetrier Default] 🔄 Will retry. Attempt ${attempt} failed with: ${error.message}`),
|
|
370
|
+
onTimeout: (attempt, ms) => console.warn(`[UseRetrier Default] ⏰ Attempt ${attempt} timed out after ${ms}ms.`),
|
|
371
|
+
});
|
|
372
|
+
// Use the created default retrier to wrap the provided function.
|
|
373
|
+
return defaultRetrier(func);
|
|
374
|
+
}
|
package/core-box/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Core Box Package
|
|
2
2
|
|
|
3
|
-
The Core Box package provides unified type definitions and utility functions for the
|
|
3
|
+
The Core Box package provides unified type definitions and utility functions for the TUFF search box system. This package contains the foundational types and tools used across the entire project for search result management and plugin integration.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
@@ -112,14 +112,14 @@ const codeItem = SearchUtils.createDataItem({
|
|
|
112
112
|
```typescript
|
|
113
113
|
const urlItem = SearchUtils.createSearchItem({
|
|
114
114
|
name: "GitHub Repository",
|
|
115
|
-
desc: "
|
|
115
|
+
desc: "TUFF project",
|
|
116
116
|
pluginName: "web-search",
|
|
117
117
|
render: {
|
|
118
118
|
mode: RenderMode.URL,
|
|
119
119
|
content: "https://github.com/talex-touch/talex-touch", // The actual URL to load
|
|
120
120
|
preview: {
|
|
121
121
|
enabled: true,
|
|
122
|
-
title: "
|
|
122
|
+
title: "Tuff", // Preview metadata
|
|
123
123
|
description: "Modern desktop application framework", // Preview description
|
|
124
124
|
image: "https://github.com/talex-touch.png" // Preview image
|
|
125
125
|
}
|
|
@@ -181,11 +181,11 @@ interface IRenderConfig {
|
|
|
181
181
|
This package is automatically exported from `@talex-touch/utils`:
|
|
182
182
|
|
|
183
183
|
```typescript
|
|
184
|
-
import {
|
|
185
|
-
ISearchItem,
|
|
186
|
-
IDataItem,
|
|
187
|
-
SearchUtils,
|
|
188
|
-
RenderMode
|
|
184
|
+
import {
|
|
185
|
+
ISearchItem,
|
|
186
|
+
IDataItem,
|
|
187
|
+
SearchUtils,
|
|
188
|
+
RenderMode
|
|
189
189
|
} from '@talex-touch/utils';
|
|
190
190
|
```
|
|
191
191
|
|