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