@computekit/react 0.1.2 → 0.1.3
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/dist/index.cjs +603 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +341 -2
- package/dist/index.d.ts +341 -2
- package/dist/index.js +602 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.tsx +1175 -0
package/dist/index.cjs
CHANGED
|
@@ -168,6 +168,607 @@ function useWasmSupport() {
|
|
|
168
168
|
const kit = useComputeKit();
|
|
169
169
|
return kit.isWasmSupported();
|
|
170
170
|
}
|
|
171
|
+
function createInitialPipelineState(stages) {
|
|
172
|
+
return {
|
|
173
|
+
status: "idle",
|
|
174
|
+
stages: stages.map((config) => ({
|
|
175
|
+
id: config.id,
|
|
176
|
+
name: config.name,
|
|
177
|
+
functionName: config.functionName,
|
|
178
|
+
status: "pending",
|
|
179
|
+
retryCount: 0,
|
|
180
|
+
options: config.options
|
|
181
|
+
})),
|
|
182
|
+
currentStageIndex: -1,
|
|
183
|
+
currentStage: null,
|
|
184
|
+
progress: 0,
|
|
185
|
+
output: null,
|
|
186
|
+
input: null,
|
|
187
|
+
error: null,
|
|
188
|
+
startedAt: null,
|
|
189
|
+
completedAt: null,
|
|
190
|
+
totalDuration: null,
|
|
191
|
+
stageResults: [],
|
|
192
|
+
metrics: {
|
|
193
|
+
totalStages: stages.length,
|
|
194
|
+
completedStages: 0,
|
|
195
|
+
failedStages: 0,
|
|
196
|
+
skippedStages: 0,
|
|
197
|
+
totalRetries: 0,
|
|
198
|
+
slowestStage: null,
|
|
199
|
+
fastestStage: null,
|
|
200
|
+
averageStageDuration: 0,
|
|
201
|
+
timeline: []
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
function formatDuration(ms) {
|
|
206
|
+
if (ms < 1e3) return `${ms.toFixed(0)}ms`;
|
|
207
|
+
if (ms < 6e4) return `${(ms / 1e3).toFixed(2)}s`;
|
|
208
|
+
return `${(ms / 6e4).toFixed(2)}min`;
|
|
209
|
+
}
|
|
210
|
+
function usePipeline(stageConfigs, options = {}) {
|
|
211
|
+
const kit = useComputeKit();
|
|
212
|
+
const abortControllerRef = react.useRef(null);
|
|
213
|
+
const pausedRef = react.useRef(false);
|
|
214
|
+
const resumePromiseRef = react.useRef(null);
|
|
215
|
+
const [state, setState] = react.useState(
|
|
216
|
+
() => createInitialPipelineState(stageConfigs)
|
|
217
|
+
);
|
|
218
|
+
const stages = react.useMemo(() => stageConfigs, [stageConfigs]);
|
|
219
|
+
const addTimelineEvent = react.useCallback(
|
|
220
|
+
(stageId, stageName, event, duration, error) => {
|
|
221
|
+
if (options.trackTimeline === false) return;
|
|
222
|
+
setState((prev) => ({
|
|
223
|
+
...prev,
|
|
224
|
+
metrics: {
|
|
225
|
+
...prev.metrics,
|
|
226
|
+
timeline: [
|
|
227
|
+
...prev.metrics.timeline,
|
|
228
|
+
{
|
|
229
|
+
stageId,
|
|
230
|
+
stageName,
|
|
231
|
+
event,
|
|
232
|
+
timestamp: Date.now(),
|
|
233
|
+
duration,
|
|
234
|
+
error
|
|
235
|
+
}
|
|
236
|
+
]
|
|
237
|
+
}
|
|
238
|
+
}));
|
|
239
|
+
},
|
|
240
|
+
[options.trackTimeline]
|
|
241
|
+
);
|
|
242
|
+
const updateMetrics = react.useCallback(
|
|
243
|
+
(_completedStage, allStages) => {
|
|
244
|
+
const completedStages = allStages.filter((s) => s.status === "completed");
|
|
245
|
+
const durations = completedStages.filter((s) => s.duration !== void 0).map((s) => ({ id: s.id, name: s.name, duration: s.duration }));
|
|
246
|
+
const slowest = durations.length ? durations.reduce((a, b) => a.duration > b.duration ? a : b) : null;
|
|
247
|
+
const fastest = durations.length ? durations.reduce((a, b) => a.duration < b.duration ? a : b) : null;
|
|
248
|
+
const avgDuration = durations.length ? durations.reduce((sum, d) => sum + d.duration, 0) / durations.length : 0;
|
|
249
|
+
return {
|
|
250
|
+
totalStages: allStages.length,
|
|
251
|
+
completedStages: completedStages.length,
|
|
252
|
+
failedStages: allStages.filter((s) => s.status === "failed").length,
|
|
253
|
+
skippedStages: allStages.filter((s) => s.status === "skipped").length,
|
|
254
|
+
totalRetries: allStages.reduce((sum, s) => sum + s.retryCount, 0),
|
|
255
|
+
slowestStage: slowest,
|
|
256
|
+
fastestStage: fastest,
|
|
257
|
+
averageStageDuration: avgDuration
|
|
258
|
+
};
|
|
259
|
+
},
|
|
260
|
+
[]
|
|
261
|
+
);
|
|
262
|
+
const executeStage = react.useCallback(
|
|
263
|
+
async (stageConfig, stageIndex, input, previousResults, signal) => {
|
|
264
|
+
const maxRetries = stageConfig.maxRetries ?? 0;
|
|
265
|
+
const retryDelay = stageConfig.retryDelay ?? 1e3;
|
|
266
|
+
let lastError;
|
|
267
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
268
|
+
if (signal.aborted) {
|
|
269
|
+
return { success: false, error: new Error("Pipeline cancelled") };
|
|
270
|
+
}
|
|
271
|
+
if (pausedRef.current) {
|
|
272
|
+
await new Promise((resolve, reject) => {
|
|
273
|
+
resumePromiseRef.current = { resolve, reject };
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
if (stageConfig.shouldSkip?.(input, previousResults)) {
|
|
277
|
+
setState((prev) => {
|
|
278
|
+
const newStages = [...prev.stages];
|
|
279
|
+
newStages[stageIndex] = {
|
|
280
|
+
...newStages[stageIndex],
|
|
281
|
+
status: "skipped"
|
|
282
|
+
};
|
|
283
|
+
return {
|
|
284
|
+
...prev,
|
|
285
|
+
stages: newStages
|
|
286
|
+
};
|
|
287
|
+
});
|
|
288
|
+
addTimelineEvent(stageConfig.id, stageConfig.name, "skipped");
|
|
289
|
+
options.onStageComplete?.(state.stages[stageIndex]);
|
|
290
|
+
return { success: true, output: previousResults[previousResults.length - 1] };
|
|
291
|
+
}
|
|
292
|
+
const transformedInput = stageConfig.transformInput ? stageConfig.transformInput(input, previousResults) : input;
|
|
293
|
+
const startTime = performance.now();
|
|
294
|
+
setState((prev) => {
|
|
295
|
+
const newStages = [...prev.stages];
|
|
296
|
+
newStages[stageIndex] = {
|
|
297
|
+
...newStages[stageIndex],
|
|
298
|
+
status: "running",
|
|
299
|
+
input: transformedInput,
|
|
300
|
+
startedAt: Date.now(),
|
|
301
|
+
retryCount: attempt
|
|
302
|
+
};
|
|
303
|
+
return {
|
|
304
|
+
...prev,
|
|
305
|
+
stages: newStages,
|
|
306
|
+
currentStageIndex: stageIndex,
|
|
307
|
+
currentStage: newStages[stageIndex]
|
|
308
|
+
};
|
|
309
|
+
});
|
|
310
|
+
if (attempt === 0) {
|
|
311
|
+
addTimelineEvent(stageConfig.id, stageConfig.name, "started");
|
|
312
|
+
options.onStageStart?.(state.stages[stageIndex]);
|
|
313
|
+
} else {
|
|
314
|
+
addTimelineEvent(stageConfig.id, stageConfig.name, "retry");
|
|
315
|
+
options.onStageRetry?.(state.stages[stageIndex], attempt);
|
|
316
|
+
}
|
|
317
|
+
try {
|
|
318
|
+
const result = await kit.run(stageConfig.functionName, transformedInput, {
|
|
319
|
+
...stageConfig.options,
|
|
320
|
+
signal,
|
|
321
|
+
onProgress: (progress) => {
|
|
322
|
+
setState((prev) => {
|
|
323
|
+
const newStages = [...prev.stages];
|
|
324
|
+
newStages[stageIndex] = {
|
|
325
|
+
...newStages[stageIndex],
|
|
326
|
+
progress: progress.percent
|
|
327
|
+
};
|
|
328
|
+
const stageProgress = progress.percent / 100;
|
|
329
|
+
const overallProgress = (stageIndex + stageProgress) / stages.length * 100;
|
|
330
|
+
return {
|
|
331
|
+
...prev,
|
|
332
|
+
stages: newStages,
|
|
333
|
+
progress: overallProgress
|
|
334
|
+
};
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
const duration = performance.now() - startTime;
|
|
339
|
+
const transformedOutput = stageConfig.transformOutput ? stageConfig.transformOutput(result) : result;
|
|
340
|
+
setState((prev) => {
|
|
341
|
+
const newStages = [...prev.stages];
|
|
342
|
+
newStages[stageIndex] = {
|
|
343
|
+
...newStages[stageIndex],
|
|
344
|
+
status: "completed",
|
|
345
|
+
output: transformedOutput,
|
|
346
|
+
completedAt: Date.now(),
|
|
347
|
+
duration,
|
|
348
|
+
progress: 100
|
|
349
|
+
};
|
|
350
|
+
const newMetrics = {
|
|
351
|
+
...prev.metrics,
|
|
352
|
+
...updateMetrics(newStages[stageIndex], newStages)
|
|
353
|
+
};
|
|
354
|
+
return {
|
|
355
|
+
...prev,
|
|
356
|
+
stages: newStages,
|
|
357
|
+
metrics: newMetrics,
|
|
358
|
+
progress: (stageIndex + 1) / stages.length * 100
|
|
359
|
+
};
|
|
360
|
+
});
|
|
361
|
+
addTimelineEvent(stageConfig.id, stageConfig.name, "completed", duration);
|
|
362
|
+
options.onStageComplete?.(state.stages[stageIndex]);
|
|
363
|
+
return { success: true, output: transformedOutput };
|
|
364
|
+
} catch (err) {
|
|
365
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
366
|
+
if (attempt < maxRetries) {
|
|
367
|
+
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
const duration = performance.now() - startTime;
|
|
371
|
+
setState((prev) => {
|
|
372
|
+
const newStages = [...prev.stages];
|
|
373
|
+
newStages[stageIndex] = {
|
|
374
|
+
...newStages[stageIndex],
|
|
375
|
+
status: "failed",
|
|
376
|
+
error: lastError,
|
|
377
|
+
completedAt: Date.now(),
|
|
378
|
+
duration
|
|
379
|
+
};
|
|
380
|
+
return {
|
|
381
|
+
...prev,
|
|
382
|
+
stages: newStages,
|
|
383
|
+
metrics: {
|
|
384
|
+
...prev.metrics,
|
|
385
|
+
failedStages: prev.metrics.failedStages + 1
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
});
|
|
389
|
+
addTimelineEvent(
|
|
390
|
+
stageConfig.id,
|
|
391
|
+
stageConfig.name,
|
|
392
|
+
"failed",
|
|
393
|
+
duration,
|
|
394
|
+
lastError.message
|
|
395
|
+
);
|
|
396
|
+
options.onStageError?.(state.stages[stageIndex], lastError);
|
|
397
|
+
return { success: false, error: lastError };
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
return { success: false, error: lastError };
|
|
401
|
+
},
|
|
402
|
+
[kit, stages, state.stages, addTimelineEvent, updateMetrics, options]
|
|
403
|
+
);
|
|
404
|
+
const run = react.useCallback(
|
|
405
|
+
async (input) => {
|
|
406
|
+
if (abortControllerRef.current) {
|
|
407
|
+
abortControllerRef.current.abort();
|
|
408
|
+
}
|
|
409
|
+
const abortController = new AbortController();
|
|
410
|
+
abortControllerRef.current = abortController;
|
|
411
|
+
pausedRef.current = false;
|
|
412
|
+
const startTime = Date.now();
|
|
413
|
+
setState(() => ({
|
|
414
|
+
...createInitialPipelineState(stages),
|
|
415
|
+
status: "running",
|
|
416
|
+
input,
|
|
417
|
+
startedAt: startTime
|
|
418
|
+
}));
|
|
419
|
+
const stageResults = [];
|
|
420
|
+
let currentInput = input;
|
|
421
|
+
let finalError = null;
|
|
422
|
+
for (let i = 0; i < stages.length; i++) {
|
|
423
|
+
if (abortController.signal.aborted) {
|
|
424
|
+
setState((prev) => ({
|
|
425
|
+
...prev,
|
|
426
|
+
status: "cancelled",
|
|
427
|
+
completedAt: Date.now(),
|
|
428
|
+
totalDuration: Date.now() - startTime
|
|
429
|
+
}));
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
const result = await executeStage(
|
|
433
|
+
stages[i],
|
|
434
|
+
i,
|
|
435
|
+
currentInput,
|
|
436
|
+
stageResults,
|
|
437
|
+
abortController.signal
|
|
438
|
+
);
|
|
439
|
+
if (!result.success) {
|
|
440
|
+
finalError = result.error ?? new Error("Stage failed");
|
|
441
|
+
if (options.stopOnError !== false) {
|
|
442
|
+
setState((prev) => ({
|
|
443
|
+
...prev,
|
|
444
|
+
status: "failed",
|
|
445
|
+
error: finalError,
|
|
446
|
+
stageResults,
|
|
447
|
+
completedAt: Date.now(),
|
|
448
|
+
totalDuration: Date.now() - startTime
|
|
449
|
+
}));
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
if (result.output !== void 0) {
|
|
454
|
+
stageResults.push(result.output);
|
|
455
|
+
currentInput = result.output;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
setState((prev) => ({
|
|
459
|
+
...prev,
|
|
460
|
+
status: finalError ? "failed" : "completed",
|
|
461
|
+
output: currentInput ?? null,
|
|
462
|
+
error: finalError,
|
|
463
|
+
stageResults,
|
|
464
|
+
completedAt: Date.now(),
|
|
465
|
+
totalDuration: Date.now() - startTime,
|
|
466
|
+
currentStageIndex: -1,
|
|
467
|
+
currentStage: null,
|
|
468
|
+
progress: 100
|
|
469
|
+
}));
|
|
470
|
+
options.onStateChange?.(state);
|
|
471
|
+
},
|
|
472
|
+
[stages, executeStage, options, state]
|
|
473
|
+
);
|
|
474
|
+
const cancel = react.useCallback(() => {
|
|
475
|
+
if (abortControllerRef.current) {
|
|
476
|
+
abortControllerRef.current.abort();
|
|
477
|
+
abortControllerRef.current = null;
|
|
478
|
+
}
|
|
479
|
+
if (resumePromiseRef.current) {
|
|
480
|
+
resumePromiseRef.current.reject(new Error("Pipeline cancelled"));
|
|
481
|
+
resumePromiseRef.current = null;
|
|
482
|
+
}
|
|
483
|
+
setState((prev) => ({
|
|
484
|
+
...prev,
|
|
485
|
+
status: "cancelled",
|
|
486
|
+
completedAt: Date.now(),
|
|
487
|
+
totalDuration: prev.startedAt ? Date.now() - prev.startedAt : null
|
|
488
|
+
}));
|
|
489
|
+
}, []);
|
|
490
|
+
const reset = react.useCallback(() => {
|
|
491
|
+
cancel();
|
|
492
|
+
setState(createInitialPipelineState(stages));
|
|
493
|
+
}, [cancel, stages]);
|
|
494
|
+
const pause = react.useCallback(() => {
|
|
495
|
+
pausedRef.current = true;
|
|
496
|
+
setState((prev) => ({
|
|
497
|
+
...prev,
|
|
498
|
+
status: "paused"
|
|
499
|
+
}));
|
|
500
|
+
}, []);
|
|
501
|
+
const resume = react.useCallback(() => {
|
|
502
|
+
pausedRef.current = false;
|
|
503
|
+
if (resumePromiseRef.current) {
|
|
504
|
+
resumePromiseRef.current.resolve();
|
|
505
|
+
resumePromiseRef.current = null;
|
|
506
|
+
}
|
|
507
|
+
setState((prev) => ({
|
|
508
|
+
...prev,
|
|
509
|
+
status: "running"
|
|
510
|
+
}));
|
|
511
|
+
}, []);
|
|
512
|
+
const retry = react.useCallback(async () => {
|
|
513
|
+
if (state.status !== "failed" || !state.input) return;
|
|
514
|
+
const failedIndex = state.stages.findIndex((s) => s.status === "failed");
|
|
515
|
+
if (failedIndex === -1) return;
|
|
516
|
+
const retryInput = failedIndex === 0 ? state.input : state.stageResults[failedIndex - 1];
|
|
517
|
+
const abortController = new AbortController();
|
|
518
|
+
abortControllerRef.current = abortController;
|
|
519
|
+
setState((prev) => ({
|
|
520
|
+
...prev,
|
|
521
|
+
status: "running",
|
|
522
|
+
error: null
|
|
523
|
+
}));
|
|
524
|
+
const stageResults = [...state.stageResults.slice(0, failedIndex)];
|
|
525
|
+
let currentInput = retryInput;
|
|
526
|
+
for (let i = failedIndex; i < stages.length; i++) {
|
|
527
|
+
if (abortController.signal.aborted) {
|
|
528
|
+
setState((prev) => ({ ...prev, status: "cancelled" }));
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
const result = await executeStage(
|
|
532
|
+
stages[i],
|
|
533
|
+
i,
|
|
534
|
+
currentInput,
|
|
535
|
+
stageResults,
|
|
536
|
+
abortController.signal
|
|
537
|
+
);
|
|
538
|
+
if (!result.success) {
|
|
539
|
+
setState((prev) => ({
|
|
540
|
+
...prev,
|
|
541
|
+
status: "failed",
|
|
542
|
+
error: result.error ?? new Error("Stage failed"),
|
|
543
|
+
stageResults
|
|
544
|
+
}));
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
if (result.output !== void 0) {
|
|
548
|
+
stageResults.push(result.output);
|
|
549
|
+
currentInput = result.output;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
setState((prev) => ({
|
|
553
|
+
...prev,
|
|
554
|
+
status: "completed",
|
|
555
|
+
output: currentInput,
|
|
556
|
+
stageResults,
|
|
557
|
+
completedAt: Date.now(),
|
|
558
|
+
totalDuration: prev.startedAt ? Date.now() - prev.startedAt : null,
|
|
559
|
+
progress: 100
|
|
560
|
+
}));
|
|
561
|
+
}, [state, stages, executeStage]);
|
|
562
|
+
const getReport = react.useCallback(() => {
|
|
563
|
+
const stageDetails = state.stages.map((stage) => ({
|
|
564
|
+
name: stage.name,
|
|
565
|
+
status: stage.status,
|
|
566
|
+
duration: stage.duration ? formatDuration(stage.duration) : "-",
|
|
567
|
+
error: stage.error?.message
|
|
568
|
+
}));
|
|
569
|
+
const timeline = state.metrics.timeline.map((event) => {
|
|
570
|
+
const time = new Date(event.timestamp).toISOString().split("T")[1].split(".")[0];
|
|
571
|
+
const duration = event.duration ? ` (${formatDuration(event.duration)})` : "";
|
|
572
|
+
const error = event.error ? ` - ${event.error}` : "";
|
|
573
|
+
return `[${time}] ${event.stageName}: ${event.event}${duration}${error}`;
|
|
574
|
+
});
|
|
575
|
+
const insights = [];
|
|
576
|
+
if (state.metrics.slowestStage) {
|
|
577
|
+
insights.push(
|
|
578
|
+
`Slowest stage: ${state.metrics.slowestStage.name} (${formatDuration(
|
|
579
|
+
state.metrics.slowestStage.duration
|
|
580
|
+
)})`
|
|
581
|
+
);
|
|
582
|
+
}
|
|
583
|
+
if (state.metrics.fastestStage) {
|
|
584
|
+
insights.push(
|
|
585
|
+
`Fastest stage: ${state.metrics.fastestStage.name} (${formatDuration(
|
|
586
|
+
state.metrics.fastestStage.duration
|
|
587
|
+
)})`
|
|
588
|
+
);
|
|
589
|
+
}
|
|
590
|
+
if (state.metrics.totalRetries > 0) {
|
|
591
|
+
insights.push(`Total retries: ${state.metrics.totalRetries}`);
|
|
592
|
+
}
|
|
593
|
+
if (state.metrics.averageStageDuration > 0) {
|
|
594
|
+
insights.push(
|
|
595
|
+
`Average stage duration: ${formatDuration(state.metrics.averageStageDuration)}`
|
|
596
|
+
);
|
|
597
|
+
}
|
|
598
|
+
const successRate = state.metrics.totalStages > 0 ? state.metrics.completedStages / state.metrics.totalStages * 100 : 0;
|
|
599
|
+
const summary = [
|
|
600
|
+
`Pipeline Status: ${state.status.toUpperCase()}`,
|
|
601
|
+
`Stages: ${state.metrics.completedStages}/${state.metrics.totalStages} completed`,
|
|
602
|
+
`Success Rate: ${successRate.toFixed(0)}%`,
|
|
603
|
+
state.totalDuration ? `Total Duration: ${formatDuration(state.totalDuration)}` : "",
|
|
604
|
+
state.error ? `Error: ${state.error.message}` : ""
|
|
605
|
+
].filter(Boolean).join("\n");
|
|
606
|
+
return {
|
|
607
|
+
summary,
|
|
608
|
+
stageDetails,
|
|
609
|
+
timeline,
|
|
610
|
+
insights,
|
|
611
|
+
metrics: state.metrics
|
|
612
|
+
};
|
|
613
|
+
}, [state]);
|
|
614
|
+
const isStageComplete = react.useCallback(
|
|
615
|
+
(stageId) => {
|
|
616
|
+
const stage = state.stages.find((s) => s.id === stageId);
|
|
617
|
+
return stage?.status === "completed";
|
|
618
|
+
},
|
|
619
|
+
[state.stages]
|
|
620
|
+
);
|
|
621
|
+
const getStage = react.useCallback(
|
|
622
|
+
(stageId) => {
|
|
623
|
+
return state.stages.find((s) => s.id === stageId);
|
|
624
|
+
},
|
|
625
|
+
[state.stages]
|
|
626
|
+
);
|
|
627
|
+
react.useEffect(() => {
|
|
628
|
+
if (options.autoRun && options.initialInput !== void 0) {
|
|
629
|
+
run(options.initialInput);
|
|
630
|
+
}
|
|
631
|
+
}, []);
|
|
632
|
+
react.useEffect(() => {
|
|
633
|
+
return () => {
|
|
634
|
+
cancel();
|
|
635
|
+
};
|
|
636
|
+
}, [cancel]);
|
|
637
|
+
return {
|
|
638
|
+
...state,
|
|
639
|
+
run,
|
|
640
|
+
cancel,
|
|
641
|
+
reset,
|
|
642
|
+
pause,
|
|
643
|
+
resume,
|
|
644
|
+
retry,
|
|
645
|
+
getReport,
|
|
646
|
+
isRunning: state.status === "running",
|
|
647
|
+
isComplete: state.status === "completed",
|
|
648
|
+
isFailed: state.status === "failed",
|
|
649
|
+
isStageComplete,
|
|
650
|
+
getStage
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
function useParallelBatch(functionName, options = {}) {
|
|
654
|
+
const kit = useComputeKit();
|
|
655
|
+
const abortControllerRef = react.useRef(null);
|
|
656
|
+
const [state, setState] = react.useState({
|
|
657
|
+
result: null,
|
|
658
|
+
loading: false,
|
|
659
|
+
progress: 0,
|
|
660
|
+
completedCount: 0,
|
|
661
|
+
totalCount: 0
|
|
662
|
+
});
|
|
663
|
+
const run = react.useCallback(
|
|
664
|
+
async (items) => {
|
|
665
|
+
if (abortControllerRef.current) {
|
|
666
|
+
abortControllerRef.current.abort();
|
|
667
|
+
}
|
|
668
|
+
const abortController = new AbortController();
|
|
669
|
+
abortControllerRef.current = abortController;
|
|
670
|
+
setState({
|
|
671
|
+
result: null,
|
|
672
|
+
loading: true,
|
|
673
|
+
progress: 0,
|
|
674
|
+
completedCount: 0,
|
|
675
|
+
totalCount: items.length
|
|
676
|
+
});
|
|
677
|
+
const startTime = performance.now();
|
|
678
|
+
const results = [];
|
|
679
|
+
const concurrency = options.concurrency ?? items.length;
|
|
680
|
+
for (let i = 0; i < items.length; i += concurrency) {
|
|
681
|
+
if (abortController.signal.aborted) {
|
|
682
|
+
break;
|
|
683
|
+
}
|
|
684
|
+
const batch = items.slice(i, i + concurrency);
|
|
685
|
+
const batchPromises = batch.map(async (item, batchIndex) => {
|
|
686
|
+
const index = i + batchIndex;
|
|
687
|
+
const itemStart = performance.now();
|
|
688
|
+
try {
|
|
689
|
+
const data = await kit.run(functionName, item, {
|
|
690
|
+
...options.computeOptions,
|
|
691
|
+
signal: abortController.signal
|
|
692
|
+
});
|
|
693
|
+
const itemResult = {
|
|
694
|
+
index,
|
|
695
|
+
success: true,
|
|
696
|
+
data,
|
|
697
|
+
duration: performance.now() - itemStart
|
|
698
|
+
};
|
|
699
|
+
return itemResult;
|
|
700
|
+
} catch (err) {
|
|
701
|
+
const itemResult = {
|
|
702
|
+
index,
|
|
703
|
+
success: false,
|
|
704
|
+
error: err instanceof Error ? err : new Error(String(err)),
|
|
705
|
+
duration: performance.now() - itemStart
|
|
706
|
+
};
|
|
707
|
+
return itemResult;
|
|
708
|
+
}
|
|
709
|
+
});
|
|
710
|
+
const batchResults = await Promise.all(batchPromises);
|
|
711
|
+
results.push(...batchResults);
|
|
712
|
+
const completed = results.length;
|
|
713
|
+
setState((prev) => ({
|
|
714
|
+
...prev,
|
|
715
|
+
completedCount: completed,
|
|
716
|
+
progress: completed / items.length * 100
|
|
717
|
+
}));
|
|
718
|
+
}
|
|
719
|
+
const totalDuration = performance.now() - startTime;
|
|
720
|
+
const successful = results.filter((r) => r.success && r.data !== void 0).map((r) => r.data);
|
|
721
|
+
const failed = results.filter((r) => !r.success).map((r) => ({ index: r.index, error: r.error }));
|
|
722
|
+
const finalResult = {
|
|
723
|
+
results,
|
|
724
|
+
successful,
|
|
725
|
+
failed,
|
|
726
|
+
totalDuration,
|
|
727
|
+
successRate: successful.length / items.length
|
|
728
|
+
};
|
|
729
|
+
setState({
|
|
730
|
+
result: finalResult,
|
|
731
|
+
loading: false,
|
|
732
|
+
progress: 100,
|
|
733
|
+
completedCount: items.length,
|
|
734
|
+
totalCount: items.length
|
|
735
|
+
});
|
|
736
|
+
return finalResult;
|
|
737
|
+
},
|
|
738
|
+
[kit, functionName, options.concurrency, options.computeOptions]
|
|
739
|
+
);
|
|
740
|
+
const cancel = react.useCallback(() => {
|
|
741
|
+
if (abortControllerRef.current) {
|
|
742
|
+
abortControllerRef.current.abort();
|
|
743
|
+
abortControllerRef.current = null;
|
|
744
|
+
}
|
|
745
|
+
setState((prev) => ({
|
|
746
|
+
...prev,
|
|
747
|
+
loading: false
|
|
748
|
+
}));
|
|
749
|
+
}, []);
|
|
750
|
+
const reset = react.useCallback(() => {
|
|
751
|
+
cancel();
|
|
752
|
+
setState({
|
|
753
|
+
result: null,
|
|
754
|
+
loading: false,
|
|
755
|
+
progress: 0,
|
|
756
|
+
completedCount: 0,
|
|
757
|
+
totalCount: 0
|
|
758
|
+
});
|
|
759
|
+
}, [cancel]);
|
|
760
|
+
react.useEffect(() => {
|
|
761
|
+
return () => {
|
|
762
|
+
cancel();
|
|
763
|
+
};
|
|
764
|
+
}, [cancel]);
|
|
765
|
+
return {
|
|
766
|
+
...state,
|
|
767
|
+
run,
|
|
768
|
+
cancel,
|
|
769
|
+
reset
|
|
770
|
+
};
|
|
771
|
+
}
|
|
171
772
|
|
|
172
773
|
Object.defineProperty(exports, "ComputeKit", {
|
|
173
774
|
enumerable: true,
|
|
@@ -178,6 +779,8 @@ exports.useCompute = useCompute;
|
|
|
178
779
|
exports.useComputeCallback = useComputeCallback;
|
|
179
780
|
exports.useComputeFunction = useComputeFunction;
|
|
180
781
|
exports.useComputeKit = useComputeKit;
|
|
782
|
+
exports.useParallelBatch = useParallelBatch;
|
|
783
|
+
exports.usePipeline = usePipeline;
|
|
181
784
|
exports.usePoolStats = usePoolStats;
|
|
182
785
|
exports.useWasmSupport = useWasmSupport;
|
|
183
786
|
//# sourceMappingURL=index.cjs.map
|