@sylphx/flow 1.7.0 → 1.8.1
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/CHANGELOG.md +78 -0
- package/assets/agents/coder.md +72 -119
- package/assets/agents/orchestrator.md +26 -90
- package/assets/agents/reviewer.md +76 -47
- package/assets/agents/writer.md +82 -63
- package/assets/output-styles/silent.md +141 -8
- package/assets/rules/code-standards.md +9 -33
- package/assets/rules/core.md +67 -59
- package/package.json +2 -12
- package/src/commands/flow/execute.ts +470 -0
- package/src/commands/flow/index.ts +11 -0
- package/src/commands/flow/prompt.ts +35 -0
- package/src/commands/flow/setup.ts +312 -0
- package/src/commands/flow/targets.ts +18 -0
- package/src/commands/flow/types.ts +47 -0
- package/src/commands/flow-command.ts +18 -967
- package/src/commands/flow-orchestrator.ts +14 -5
- package/src/commands/hook-command.ts +1 -1
- package/src/commands/init-core.ts +12 -3
- package/src/commands/run-command.ts +1 -1
- package/src/config/rules.ts +1 -1
- package/src/core/error-handling.ts +1 -1
- package/src/core/loop-controller.ts +1 -1
- package/src/core/state-detector.ts +1 -1
- package/src/core/target-manager.ts +1 -1
- package/src/index.ts +1 -1
- package/src/shared/files/index.ts +1 -1
- package/src/shared/processing/index.ts +1 -1
- package/src/targets/claude-code.ts +3 -3
- package/src/targets/opencode.ts +3 -3
- package/src/utils/agent-enhancer.ts +2 -2
- package/src/utils/{mcp-config.ts → config/mcp-config.ts} +4 -4
- package/src/utils/{paths.ts → config/paths.ts} +1 -1
- package/src/utils/{settings.ts → config/settings.ts} +1 -1
- package/src/utils/{target-config.ts → config/target-config.ts} +5 -5
- package/src/utils/{target-utils.ts → config/target-utils.ts} +3 -3
- package/src/utils/display/banner.ts +25 -0
- package/src/utils/display/status.ts +55 -0
- package/src/utils/{file-operations.ts → files/file-operations.ts} +2 -2
- package/src/utils/files/jsonc.ts +36 -0
- package/src/utils/{sync-utils.ts → files/sync-utils.ts} +3 -3
- package/src/utils/index.ts +42 -61
- package/src/utils/version.ts +47 -0
- package/src/components/benchmark-monitor.tsx +0 -331
- package/src/components/reindex-progress.tsx +0 -261
- package/src/composables/functional/index.ts +0 -14
- package/src/composables/functional/useEnvironment.ts +0 -171
- package/src/composables/functional/useFileSystem.ts +0 -139
- package/src/composables/index.ts +0 -4
- package/src/composables/useEnv.ts +0 -13
- package/src/composables/useRuntimeConfig.ts +0 -27
- package/src/core/ai-sdk.ts +0 -603
- package/src/core/app-factory.ts +0 -381
- package/src/core/builtin-agents.ts +0 -9
- package/src/core/command-system.ts +0 -550
- package/src/core/config-system.ts +0 -550
- package/src/core/connection-pool.ts +0 -390
- package/src/core/di-container.ts +0 -155
- package/src/core/headless-display.ts +0 -96
- package/src/core/interfaces/index.ts +0 -22
- package/src/core/interfaces/repository.interface.ts +0 -91
- package/src/core/interfaces/service.interface.ts +0 -133
- package/src/core/interfaces.ts +0 -96
- package/src/core/result.ts +0 -351
- package/src/core/service-config.ts +0 -252
- package/src/core/session-service.ts +0 -121
- package/src/core/storage-factory.ts +0 -115
- package/src/core/stream-handler.ts +0 -288
- package/src/core/type-utils.ts +0 -427
- package/src/core/unified-storage.ts +0 -456
- package/src/core/validation/limit.ts +0 -46
- package/src/core/validation/query.ts +0 -20
- package/src/db/auto-migrate.ts +0 -322
- package/src/db/base-database-client.ts +0 -144
- package/src/db/cache-db.ts +0 -218
- package/src/db/cache-schema.ts +0 -75
- package/src/db/database.ts +0 -70
- package/src/db/index.ts +0 -252
- package/src/db/memory-db.ts +0 -153
- package/src/db/memory-schema.ts +0 -29
- package/src/db/schema.ts +0 -289
- package/src/db/session-repository.ts +0 -733
- package/src/domains/index.ts +0 -6
- package/src/domains/utilities/index.ts +0 -6
- package/src/domains/utilities/time/index.ts +0 -5
- package/src/domains/utilities/time/tools.ts +0 -291
- package/src/services/agent-service.ts +0 -273
- package/src/services/evaluation-service.ts +0 -271
- package/src/services/functional/evaluation-logic.ts +0 -296
- package/src/services/functional/file-processor.ts +0 -273
- package/src/services/functional/index.ts +0 -12
- package/src/services/memory.service.ts +0 -476
- package/src/types/api/batch.ts +0 -108
- package/src/types/api/errors.ts +0 -118
- package/src/types/api/index.ts +0 -55
- package/src/types/api/requests.ts +0 -76
- package/src/types/api/responses.ts +0 -180
- package/src/types/api/websockets.ts +0 -85
- package/src/types/benchmark.ts +0 -49
- package/src/types/database.types.ts +0 -510
- package/src/types/memory-types.ts +0 -63
- package/src/utils/advanced-tokenizer.ts +0 -191
- package/src/utils/ai-model-fetcher.ts +0 -19
- package/src/utils/async-file-operations.ts +0 -516
- package/src/utils/audio-player.ts +0 -345
- package/src/utils/codebase-helpers.ts +0 -211
- package/src/utils/console-ui.ts +0 -79
- package/src/utils/database-errors.ts +0 -140
- package/src/utils/debug-logger.ts +0 -49
- package/src/utils/file-scanner.ts +0 -259
- package/src/utils/help.ts +0 -20
- package/src/utils/immutable-cache.ts +0 -106
- package/src/utils/jsonc.ts +0 -158
- package/src/utils/memory-tui.ts +0 -414
- package/src/utils/models-dev.ts +0 -91
- package/src/utils/parallel-operations.ts +0 -487
- package/src/utils/process-manager.ts +0 -155
- package/src/utils/prompts.ts +0 -120
- package/src/utils/search-tool-builder.ts +0 -214
- package/src/utils/session-manager.ts +0 -168
- package/src/utils/session-title.ts +0 -87
- package/src/utils/simplified-errors.ts +0 -410
- package/src/utils/template-engine.ts +0 -94
- package/src/utils/test-audio.ts +0 -71
- package/src/utils/todo-context.ts +0 -46
- package/src/utils/token-counter.ts +0 -288
- /package/src/utils/{cli-output.ts → display/cli-output.ts} +0 -0
- /package/src/utils/{logger.ts → display/logger.ts} +0 -0
- /package/src/utils/{notifications.ts → display/notifications.ts} +0 -0
- /package/src/utils/{secret-utils.ts → security/secret-utils.ts} +0 -0
- /package/src/utils/{security.ts → security/security.ts} +0 -0
|
@@ -1,487 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Parallel Operations Utility
|
|
3
|
-
* Provides utilities for executing async operations in parallel with proper error handling and resource management
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { chunk } from './functional/array.js';
|
|
7
|
-
import { logger } from './logger.js';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Configuration for parallel operations
|
|
11
|
-
*/
|
|
12
|
-
export interface ParallelOptions {
|
|
13
|
-
/** Maximum number of concurrent operations */
|
|
14
|
-
concurrency?: number;
|
|
15
|
-
/** Whether to continue on error or stop immediately */
|
|
16
|
-
continueOnError?: boolean;
|
|
17
|
-
/** Timeout for individual operations (ms) */
|
|
18
|
-
timeout?: number;
|
|
19
|
-
/** Delay between batches (ms) */
|
|
20
|
-
batchDelay?: number;
|
|
21
|
-
/** Progress callback */
|
|
22
|
-
onProgress?: (completed: number, total: number, current?: unknown) => void;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Result of a parallel operation
|
|
27
|
-
*/
|
|
28
|
-
export interface ParallelResult<T> {
|
|
29
|
-
/** Successful results */
|
|
30
|
-
successful: Array<{ index: number; result: T; item: unknown }>;
|
|
31
|
-
/** Failed operations */
|
|
32
|
-
failed: Array<{ index: number; error: Error; item: unknown }>;
|
|
33
|
-
/** Total number of operations */
|
|
34
|
-
total: number;
|
|
35
|
-
/** Success count */
|
|
36
|
-
successCount: number;
|
|
37
|
-
/** Failure count */
|
|
38
|
-
failureCount: number;
|
|
39
|
-
/** Execution time in milliseconds */
|
|
40
|
-
duration: number;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Batch operation configuration
|
|
45
|
-
*/
|
|
46
|
-
export interface BatchOptions<T> extends ParallelOptions {
|
|
47
|
-
/** Function to process each batch */
|
|
48
|
-
processor: (batch: T[], batchIndex: number) => Promise<unknown[]>;
|
|
49
|
-
/** Batch size */
|
|
50
|
-
batchSize?: number;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Execute operations in parallel with controlled concurrency
|
|
55
|
-
*/
|
|
56
|
-
export async function parallel<T>(
|
|
57
|
-
items: unknown[],
|
|
58
|
-
operation: (item: unknown, index: number) => Promise<T>,
|
|
59
|
-
options: ParallelOptions = {}
|
|
60
|
-
): Promise<ParallelResult<T>> {
|
|
61
|
-
const startTime = Date.now();
|
|
62
|
-
const { concurrency = 10, continueOnError = true, timeout = 30000, onProgress } = options;
|
|
63
|
-
|
|
64
|
-
logger.debug('Starting parallel operations', {
|
|
65
|
-
itemCount: items.length,
|
|
66
|
-
concurrency,
|
|
67
|
-
continueOnError,
|
|
68
|
-
timeout,
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
// FUNCTIONAL: Use chunk and reduce to accumulate results instead of imperative loop
|
|
72
|
-
const batches = chunk(concurrency)(items);
|
|
73
|
-
|
|
74
|
-
const results = await batches.reduce<Promise<ParallelResult<T>>>(
|
|
75
|
-
async (accPromise, batch, batchIdx) => {
|
|
76
|
-
const acc = await accPromise;
|
|
77
|
-
|
|
78
|
-
const batchPromises = batch.map(async (item, batchIndex) => {
|
|
79
|
-
const globalIndex = batchIdx * concurrency + batchIndex;
|
|
80
|
-
|
|
81
|
-
try {
|
|
82
|
-
// Add timeout to individual operations
|
|
83
|
-
const operationPromise = operation(item, globalIndex);
|
|
84
|
-
const timeoutPromise = new Promise<never>((_, reject) => {
|
|
85
|
-
setTimeout(() => reject(new Error(`Operation timeout after ${timeout}ms`)), timeout);
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
const result = await Promise.race([operationPromise, timeoutPromise]);
|
|
89
|
-
|
|
90
|
-
// Report progress
|
|
91
|
-
if (onProgress) {
|
|
92
|
-
onProgress(acc.successCount + acc.failureCount + 1, items.length, item);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return { success: true as const, index: globalIndex, result, item };
|
|
96
|
-
} catch (error) {
|
|
97
|
-
const errorObj = error instanceof Error ? error : new Error(String(error));
|
|
98
|
-
|
|
99
|
-
logger.warn('Parallel operation failed', {
|
|
100
|
-
index: globalIndex,
|
|
101
|
-
error: errorObj.message,
|
|
102
|
-
item: typeof item === 'object' ? JSON.stringify(item) : item,
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
// Re-throw if not continuing on error
|
|
106
|
-
if (!continueOnError) {
|
|
107
|
-
throw errorObj;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
return { success: false as const, index: globalIndex, error: errorObj, item };
|
|
111
|
-
}
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
// Wait for current batch to complete
|
|
115
|
-
const batchResults = await Promise.all(batchPromises);
|
|
116
|
-
|
|
117
|
-
// FUNCTIONAL: Partition results into successful/failed
|
|
118
|
-
const newSuccessful = batchResults
|
|
119
|
-
.filter((r) => r.success)
|
|
120
|
-
.map((r) => ({ index: r.index, result: r.result, item: r.item }));
|
|
121
|
-
const newFailed = batchResults
|
|
122
|
-
.filter((r) => !r.success)
|
|
123
|
-
.map((r) => ({ index: r.index, error: r.error, item: r.item }));
|
|
124
|
-
|
|
125
|
-
// Add delay between batches if specified
|
|
126
|
-
if (options.batchDelay && batchIdx < batches.length - 1) {
|
|
127
|
-
await new Promise((resolve) => setTimeout(resolve, options.batchDelay!));
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
return {
|
|
131
|
-
successful: [...acc.successful, ...newSuccessful],
|
|
132
|
-
failed: [...acc.failed, ...newFailed],
|
|
133
|
-
total: items.length,
|
|
134
|
-
successCount: acc.successCount + newSuccessful.length,
|
|
135
|
-
failureCount: acc.failureCount + newFailed.length,
|
|
136
|
-
duration: 0, // Will be set after reduce completes
|
|
137
|
-
};
|
|
138
|
-
},
|
|
139
|
-
Promise.resolve({
|
|
140
|
-
successful: [],
|
|
141
|
-
failed: [],
|
|
142
|
-
total: items.length,
|
|
143
|
-
successCount: 0,
|
|
144
|
-
failureCount: 0,
|
|
145
|
-
duration: 0,
|
|
146
|
-
})
|
|
147
|
-
);
|
|
148
|
-
|
|
149
|
-
// Set final duration
|
|
150
|
-
results.duration = Date.now() - startTime;
|
|
151
|
-
|
|
152
|
-
logger.info('Parallel operations completed', {
|
|
153
|
-
total: results.total,
|
|
154
|
-
successCount: results.successCount,
|
|
155
|
-
failureCount: results.failureCount,
|
|
156
|
-
duration: results.duration,
|
|
157
|
-
successRate: `${((results.successCount / results.total) * 100).toFixed(1)}%`,
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
return results;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Execute operations in parallel with automatic retry for failed operations
|
|
165
|
-
*/
|
|
166
|
-
export async function parallelWithRetry<T>(
|
|
167
|
-
items: unknown[],
|
|
168
|
-
operation: (item: unknown, index: number) => Promise<T>,
|
|
169
|
-
options: ParallelOptions & { maxRetries?: number; retryDelay?: number } = {}
|
|
170
|
-
): Promise<ParallelResult<T>> {
|
|
171
|
-
const { maxRetries = 3, retryDelay = 1000, ...parallelOptions } = options;
|
|
172
|
-
|
|
173
|
-
const result = await parallel(items, operation, parallelOptions);
|
|
174
|
-
|
|
175
|
-
// FUNCTIONAL: Retry failed operations using reduce instead of for loop
|
|
176
|
-
const attempts = Array.from({ length: maxRetries }, (_, i) => i + 1);
|
|
177
|
-
|
|
178
|
-
const finalResult = await attempts.reduce<Promise<ParallelResult<T>>>(
|
|
179
|
-
async (accPromise, attempt) => {
|
|
180
|
-
const acc = await accPromise;
|
|
181
|
-
|
|
182
|
-
if (acc.failed.length === 0) {
|
|
183
|
-
return acc; // No more failures to retry
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
logger.info(`Retrying failed operations (attempt ${attempt}/${maxRetries})`, {
|
|
187
|
-
failedCount: acc.failed.length,
|
|
188
|
-
retryDelay,
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
// Wait before retry
|
|
192
|
-
if (retryDelay > 0) {
|
|
193
|
-
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// Retry only failed items
|
|
197
|
-
const failedItems = acc.failed.map((f) => f.item);
|
|
198
|
-
const retryResult = await parallel(failedItems, operation, parallelOptions);
|
|
199
|
-
|
|
200
|
-
// Merge results immutably
|
|
201
|
-
return {
|
|
202
|
-
successful: [...acc.successful, ...retryResult.successful],
|
|
203
|
-
failed: retryResult.failed,
|
|
204
|
-
total: acc.total,
|
|
205
|
-
successCount: acc.successful.length + retryResult.successful.length,
|
|
206
|
-
failureCount: retryResult.failed.length,
|
|
207
|
-
duration: acc.duration,
|
|
208
|
-
};
|
|
209
|
-
},
|
|
210
|
-
Promise.resolve(result)
|
|
211
|
-
);
|
|
212
|
-
|
|
213
|
-
return finalResult;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Process items in batches with parallel execution within each batch
|
|
218
|
-
*/
|
|
219
|
-
export async function batchParallel<T, R>(
|
|
220
|
-
items: T[],
|
|
221
|
-
processor: (batch: T[], batchIndex: number) => Promise<R[]>,
|
|
222
|
-
options: BatchOptions<T> = {}
|
|
223
|
-
): Promise<R[]> {
|
|
224
|
-
const { batchSize = 50, concurrency = 3, continueOnError = true, onProgress } = options;
|
|
225
|
-
|
|
226
|
-
// FUNCTIONAL: Use chunk utility instead of for loop
|
|
227
|
-
const batches = chunk(batchSize)(items);
|
|
228
|
-
|
|
229
|
-
logger.info('Starting batch parallel processing', {
|
|
230
|
-
totalItems: items.length,
|
|
231
|
-
batchSize,
|
|
232
|
-
batchCount: batches.length,
|
|
233
|
-
concurrency,
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
const processBatch = async (batch: T[], batchIndex: number): Promise<R[]> => {
|
|
237
|
-
try {
|
|
238
|
-
const result = await processor(batch, batchIndex);
|
|
239
|
-
|
|
240
|
-
if (onProgress) {
|
|
241
|
-
onProgress((batchIndex + 1) * batchSize, items.length, batch);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
return result;
|
|
245
|
-
} catch (error) {
|
|
246
|
-
logger.error('Batch processing failed', {
|
|
247
|
-
batchIndex,
|
|
248
|
-
batchSize: batch.length,
|
|
249
|
-
error: (error as Error).message,
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
if (!continueOnError) {
|
|
253
|
-
throw error;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
return [];
|
|
257
|
-
}
|
|
258
|
-
};
|
|
259
|
-
|
|
260
|
-
// Process batches in parallel
|
|
261
|
-
const results = await parallel(batches, (batch, index) => processBatch(batch as T[], index), {
|
|
262
|
-
concurrency,
|
|
263
|
-
continueOnError,
|
|
264
|
-
onProgress,
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
// Flatten successful results
|
|
268
|
-
return results.successful.flatMap((r) => r.result as R[]);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
/**
|
|
272
|
-
* Parallel map operation
|
|
273
|
-
*/
|
|
274
|
-
export async function parallelMap<T, R>(
|
|
275
|
-
items: T[],
|
|
276
|
-
mapper: (item: T, index: number) => Promise<R>,
|
|
277
|
-
options?: ParallelOptions
|
|
278
|
-
): Promise<R[]> {
|
|
279
|
-
const result = await parallel(items, mapper, options);
|
|
280
|
-
|
|
281
|
-
if (result.failureCount > 0) {
|
|
282
|
-
logger.warn('parallelMap had failures', {
|
|
283
|
-
total: result.total,
|
|
284
|
-
failures: result.failureCount,
|
|
285
|
-
});
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
// Return results in original order
|
|
289
|
-
const orderedResults = new Array(result.total);
|
|
290
|
-
result.successful.forEach(({ index, result }) => {
|
|
291
|
-
orderedResults[index] = result;
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
return orderedResults.filter(Boolean);
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
/**
|
|
298
|
-
* Parallel filter operation
|
|
299
|
-
*/
|
|
300
|
-
export async function parallelFilter<T>(
|
|
301
|
-
items: T[],
|
|
302
|
-
predicate: (item: T, index: number) => Promise<boolean>,
|
|
303
|
-
options?: ParallelOptions
|
|
304
|
-
): Promise<T[]> {
|
|
305
|
-
const results = await parallelMap(
|
|
306
|
-
items,
|
|
307
|
-
async (item, index) => ({
|
|
308
|
-
item,
|
|
309
|
-
passes: await predicate(item, index),
|
|
310
|
-
}),
|
|
311
|
-
options
|
|
312
|
-
);
|
|
313
|
-
|
|
314
|
-
return results.filter((r) => r.passes).map((r) => r.item);
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
/**
|
|
318
|
-
* Parallel reduce operation (for associative operations)
|
|
319
|
-
*/
|
|
320
|
-
export async function parallelReduce<T>(
|
|
321
|
-
items: T[],
|
|
322
|
-
reducer: (acc: T, item: T) => Promise<T>,
|
|
323
|
-
initialValue: T,
|
|
324
|
-
options?: ParallelOptions
|
|
325
|
-
): Promise<T> {
|
|
326
|
-
if (items.length === 0) {
|
|
327
|
-
return initialValue;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// For small arrays, use regular reduce
|
|
331
|
-
if (items.length <= 100) {
|
|
332
|
-
// FUNCTIONAL: Use reduce instead of for-of loop
|
|
333
|
-
return await items.reduce(async (accPromise, item) => {
|
|
334
|
-
const acc = await accPromise;
|
|
335
|
-
return await reducer(acc, item);
|
|
336
|
-
}, Promise.resolve(initialValue));
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
// For large arrays, split into chunks and reduce in parallel
|
|
340
|
-
const chunkSize = Math.ceil(items.length / (options?.concurrency || 10));
|
|
341
|
-
|
|
342
|
-
// FUNCTIONAL: Use chunk utility instead of for loop
|
|
343
|
-
const chunks = chunk(chunkSize)(items);
|
|
344
|
-
|
|
345
|
-
const chunkResults = await parallelMap(
|
|
346
|
-
chunks,
|
|
347
|
-
async (chunkItems) => {
|
|
348
|
-
// FUNCTIONAL: Use reduce instead of for-of loop
|
|
349
|
-
return await chunkItems.reduce(async (accPromise, item) => {
|
|
350
|
-
const acc = await accPromise;
|
|
351
|
-
return await reducer(acc, item);
|
|
352
|
-
}, Promise.resolve(initialValue));
|
|
353
|
-
},
|
|
354
|
-
options
|
|
355
|
-
);
|
|
356
|
-
|
|
357
|
-
// FUNCTIONAL: Combine chunk results using reduce instead of for-of loop
|
|
358
|
-
return await chunkResults.reduce(async (accPromise, chunkResult) => {
|
|
359
|
-
const acc = await accPromise;
|
|
360
|
-
return await reducer(acc, chunkResult);
|
|
361
|
-
}, Promise.resolve(initialValue));
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
/**
|
|
365
|
-
* Execute multiple async operations in parallel and return all results
|
|
366
|
-
*/
|
|
367
|
-
export async function all<T>(operations: Array<() => Promise<T>>): Promise<T[]> {
|
|
368
|
-
const promises = operations.map((op) => op());
|
|
369
|
-
return Promise.all(promises);
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
/**
|
|
373
|
-
* Execute multiple async operations in parallel and return first successful result
|
|
374
|
-
*/
|
|
375
|
-
export async function any<T>(operations: Array<() => Promise<T>>): Promise<T> {
|
|
376
|
-
const promises = operations.map(async (op, index) => {
|
|
377
|
-
try {
|
|
378
|
-
return { success: true, result: await op(), index };
|
|
379
|
-
} catch (error) {
|
|
380
|
-
return { success: false, error, index };
|
|
381
|
-
}
|
|
382
|
-
});
|
|
383
|
-
|
|
384
|
-
const results = await Promise.all(promises);
|
|
385
|
-
const successful = results.find((r) => r.success);
|
|
386
|
-
|
|
387
|
-
if (successful) {
|
|
388
|
-
return successful.result as T;
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
// If none succeeded, throw the first error
|
|
392
|
-
const firstFailure = results.find((r) => !r.success);
|
|
393
|
-
if (firstFailure) {
|
|
394
|
-
throw firstFailure.error;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
throw new Error('No operations provided');
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
/**
|
|
401
|
-
* Parallel queue interface
|
|
402
|
-
*/
|
|
403
|
-
export interface ParallelQueueInstance<T> {
|
|
404
|
-
add(item: T): Promise<unknown>;
|
|
405
|
-
size(): number;
|
|
406
|
-
clear(): void;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
/**
|
|
410
|
-
* Create a parallel execution queue with controlled concurrency
|
|
411
|
-
*/
|
|
412
|
-
export function createParallelQueue<T>(
|
|
413
|
-
processor: (item: T) => Promise<unknown>,
|
|
414
|
-
concurrency = 10
|
|
415
|
-
): ParallelQueueInstance<T> {
|
|
416
|
-
// Closure-based state
|
|
417
|
-
const queue: Array<{
|
|
418
|
-
item: T;
|
|
419
|
-
resolve: (value: unknown) => void;
|
|
420
|
-
reject: (error: Error) => void;
|
|
421
|
-
}> = [];
|
|
422
|
-
let running = 0;
|
|
423
|
-
|
|
424
|
-
const process = async (): Promise<void> => {
|
|
425
|
-
if (running >= concurrency || queue.length === 0) {
|
|
426
|
-
return;
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
running++;
|
|
430
|
-
const { item, resolve, reject } = queue.shift()!;
|
|
431
|
-
|
|
432
|
-
try {
|
|
433
|
-
const result = await processor(item);
|
|
434
|
-
resolve(result);
|
|
435
|
-
} catch (error) {
|
|
436
|
-
reject(error as Error);
|
|
437
|
-
} finally {
|
|
438
|
-
running--;
|
|
439
|
-
// Process next item in queue
|
|
440
|
-
setImmediate(() => process());
|
|
441
|
-
}
|
|
442
|
-
};
|
|
443
|
-
|
|
444
|
-
const add = async (item: T): Promise<unknown> => {
|
|
445
|
-
return new Promise((resolve, reject) => {
|
|
446
|
-
queue.push({ item, resolve, reject });
|
|
447
|
-
process();
|
|
448
|
-
});
|
|
449
|
-
};
|
|
450
|
-
|
|
451
|
-
const size = (): number => {
|
|
452
|
-
return queue.length;
|
|
453
|
-
};
|
|
454
|
-
|
|
455
|
-
const clear = (): void => {
|
|
456
|
-
queue.length = 0;
|
|
457
|
-
};
|
|
458
|
-
|
|
459
|
-
return {
|
|
460
|
-
add,
|
|
461
|
-
size,
|
|
462
|
-
clear,
|
|
463
|
-
};
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
/**
|
|
467
|
-
* @deprecated Use createParallelQueue() for new code
|
|
468
|
-
*/
|
|
469
|
-
export class ParallelQueue<T> {
|
|
470
|
-
private instance: ParallelQueueInstance<T>;
|
|
471
|
-
|
|
472
|
-
constructor(processor: (item: T) => Promise<unknown>, concurrency = 10) {
|
|
473
|
-
this.instance = createParallelQueue(processor, concurrency);
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
async add(item: T): Promise<unknown> {
|
|
477
|
-
return this.instance.add(item);
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
size(): number {
|
|
481
|
-
return this.instance.size();
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
clear(): void {
|
|
485
|
-
return this.instance.clear();
|
|
486
|
-
}
|
|
487
|
-
}
|
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ProcessManager interface for managing child processes
|
|
3
|
-
*/
|
|
4
|
-
export interface ProcessManager {
|
|
5
|
-
trackChildProcess(childProcess: any): void;
|
|
6
|
-
killAllProcesses(): Promise<void>;
|
|
7
|
-
// Internal for testing - exposed for tests to access state
|
|
8
|
-
readonly _state?: ProcessManagerState;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Internal state for ProcessManager
|
|
13
|
-
*/
|
|
14
|
-
interface ProcessManagerState {
|
|
15
|
-
readonly childProcesses: Set<any>;
|
|
16
|
-
isShuttingDown: boolean;
|
|
17
|
-
readonly signalHandlers: Map<string, (...args: any[]) => void>;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Create a ProcessManager instance
|
|
22
|
-
*/
|
|
23
|
-
export function createProcessManager(): ProcessManager {
|
|
24
|
-
const state: ProcessManagerState = {
|
|
25
|
-
childProcesses: new Set(),
|
|
26
|
-
isShuttingDown: false,
|
|
27
|
-
signalHandlers: new Map(),
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Cleanup signal handlers and reset state (for testing)
|
|
32
|
-
*/
|
|
33
|
-
const cleanup = (): void => {
|
|
34
|
-
// Remove signal handlers
|
|
35
|
-
for (const [signal, handler] of state.signalHandlers.entries()) {
|
|
36
|
-
process.removeListener(signal as any, handler);
|
|
37
|
-
}
|
|
38
|
-
state.signalHandlers.clear();
|
|
39
|
-
|
|
40
|
-
// Clear child processes
|
|
41
|
-
state.childProcesses.clear();
|
|
42
|
-
state.isShuttingDown = false;
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Kill all tracked child processes
|
|
47
|
-
*/
|
|
48
|
-
const killAllProcesses = async (): Promise<void> => {
|
|
49
|
-
const killPromises = Array.from(state.childProcesses).map(async (childProcess) => {
|
|
50
|
-
try {
|
|
51
|
-
if (childProcess && !childProcess.killed) {
|
|
52
|
-
childProcess.kill('SIGTERM');
|
|
53
|
-
|
|
54
|
-
// Force kill if it doesn't stop after 2 seconds
|
|
55
|
-
setTimeout(() => {
|
|
56
|
-
if (!childProcess.killed) {
|
|
57
|
-
childProcess.kill('SIGKILL');
|
|
58
|
-
}
|
|
59
|
-
}, 2000);
|
|
60
|
-
}
|
|
61
|
-
} catch (_error) {
|
|
62
|
-
// Silently handle kill errors
|
|
63
|
-
}
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
await Promise.all(killPromises);
|
|
67
|
-
state.childProcesses.clear();
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Setup signal handlers for graceful shutdown
|
|
72
|
-
*/
|
|
73
|
-
const setupSignalHandlers = (): void => {
|
|
74
|
-
const shutdown = async (_signal: string) => {
|
|
75
|
-
if (state.isShuttingDown) {
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
state.isShuttingDown = true;
|
|
79
|
-
|
|
80
|
-
await killAllProcesses();
|
|
81
|
-
process.exit(0);
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
// Create and store signal handlers
|
|
85
|
-
const sigintHandler = () => shutdown('SIGINT');
|
|
86
|
-
const sigtermHandler = () => shutdown('SIGTERM');
|
|
87
|
-
const sighupHandler = () => shutdown('SIGHUP');
|
|
88
|
-
|
|
89
|
-
state.signalHandlers.set('SIGINT', sigintHandler);
|
|
90
|
-
state.signalHandlers.set('SIGTERM', sigtermHandler);
|
|
91
|
-
state.signalHandlers.set('SIGHUP', sighupHandler);
|
|
92
|
-
|
|
93
|
-
// Handle termination signals
|
|
94
|
-
process.on('SIGINT', sigintHandler);
|
|
95
|
-
process.on('SIGTERM', sigtermHandler);
|
|
96
|
-
process.on('SIGHUP', sighupHandler);
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Track a child process for cleanup on shutdown
|
|
101
|
-
*/
|
|
102
|
-
const trackChildProcess = (childProcess: any): void => {
|
|
103
|
-
state.childProcesses.add(childProcess);
|
|
104
|
-
|
|
105
|
-
// Remove from tracking when process exits
|
|
106
|
-
childProcess.on('exit', () => {
|
|
107
|
-
state.childProcesses.delete(childProcess);
|
|
108
|
-
});
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
// Setup signal handlers when instance is created
|
|
112
|
-
setupSignalHandlers();
|
|
113
|
-
|
|
114
|
-
const manager: ProcessManager & { _cleanup?: () => void; _state?: ProcessManagerState } = {
|
|
115
|
-
trackChildProcess,
|
|
116
|
-
killAllProcesses,
|
|
117
|
-
_state: state, // Expose state for testing
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
// Expose cleanup for testing
|
|
121
|
-
(manager as any)._cleanup = cleanup;
|
|
122
|
-
|
|
123
|
-
return manager;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Singleton instance for backward compatibility
|
|
127
|
-
let _processManagerInstance: ProcessManager | null = null;
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Get the singleton ProcessManager instance
|
|
131
|
-
* @deprecated Use createProcessManager() for new code
|
|
132
|
-
*/
|
|
133
|
-
export class ProcessManager {
|
|
134
|
-
static getInstance(): ProcessManager {
|
|
135
|
-
if (!_processManagerInstance) {
|
|
136
|
-
_processManagerInstance = createProcessManager();
|
|
137
|
-
}
|
|
138
|
-
return _processManagerInstance;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Reset singleton instance (for testing only)
|
|
143
|
-
* @internal
|
|
144
|
-
*/
|
|
145
|
-
static resetInstance(): void {
|
|
146
|
-
if (_processManagerInstance) {
|
|
147
|
-
// Call cleanup if available
|
|
148
|
-
const cleanup = (_processManagerInstance as any)._cleanup;
|
|
149
|
-
if (cleanup) {
|
|
150
|
-
cleanup();
|
|
151
|
-
}
|
|
152
|
-
_processManagerInstance = null;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|