3.2 KiB
3.2 KiB
progress/tracker — real-time import progress
(no submodules)
Purpose
Provides the ImportProgressTracker class consumed by the canonical
ImportService (and exported for direct use by CLI tools / batch
workers) to track import jobs in real time. The tracker counts
successes, failures, and overall progress, and exposes derived metrics
(rows per second, percent complete, estimated remaining time) without
requiring the caller to keep track of the wall clock themselves.
The clock is injectable so test suites can pin deterministic ETA /
rows-per-sec assertions without sleeping. Without a clock override the
tracker uses Date.now, so the production hot path is allocation-free
after construction.
Features
- Single-row (
recordSuccess,recordFailure) and batched (recordBatchSuccess(count),recordBatchFailure(count)) updates. - Listener fan-out via
onProgress(callback)— returns an unsubscribe function so consumers can detach cleanly. - Snapshot shape (
ProgressSnapshot) carriesrowsPerSecondrounded to 2 decimal places,percentCompleterounded to an integer, andestimatedRemainingMsrounded to an integer. - Injectable
Clockfrom@suaveplan/typesfor deterministic tests. isComplete()predicate flips onceprocessedRows >= totalRows— handlestotalRows === 0as immediately complete.
Usage: Basic Example
import { ImportProgressTracker } from "@suaveplan/api-plugin-import";
const tracker = new ImportProgressTracker("job-1", 100);
tracker.onProgress((snap) => console.log(snap.percentComplete));
tracker.recordBatchSuccess(50); // logs 50
tracker.recordBatchFailure(10); // logs 60
Usage: Advanced Example
import { ImportProgressTracker } from "@suaveplan/api-plugin-import";
// Deterministic test with an injected clock — advance time by a known delta
let now = 1_000;
const tracker = new ImportProgressTracker("j", 100, { clock: () => now });
tracker.recordBatchSuccess(50);
now += 1_000;
const snap = tracker.snapshot();
// snap.rowsPerSecond === 50, snap.estimatedRemainingMs === 1_000
API Reference
class ImportProgressTrackerconstructor(jobId: string, totalRows: number, options?: ImportProgressTrackerOptions)onProgress(callback: ProgressCallback): () => voidrecordSuccess(): voidrecordFailure(): voidrecordBatchSuccess(count: number): voidrecordBatchFailure(count: number): voidsnapshot(): ProgressSnapshotisComplete(): boolean
interface ImportProgressTrackerOptions { clock?: Clock }interface ProgressSnapshot { jobId; totalRows; processedRows; successCount; failedCount; percentComplete; rowsPerSecond; estimatedRemainingMs; elapsedMs; startedAt }type ProgressCallback = (snap: ProgressSnapshot) => void
Implementation Notes
totalRows === 0short-circuitspercentCompleteto0andestimatedRemainingMsto0to avoidNaNfrom a zero divisor.- The listener array is replaced (not spliced) on
off()so a listener removing itself from inside its own callback does not invalidate the in-flight iteration. startedAtIsois captured once in the constructor and re-used on every snapshot to avoidnew Date(...)allocations on the hot path.