Initial Commit
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
# `progress/tracker` — real-time import progress
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## 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`) carries `rowsPerSecond` rounded to
|
||||
2 decimal places, `percentComplete` rounded to an integer, and
|
||||
`estimatedRemainingMs` rounded to an integer.
|
||||
- Injectable `Clock` from `@suaveplan/types` for deterministic tests.
|
||||
- `isComplete()` predicate flips once `processedRows >= totalRows` —
|
||||
handles `totalRows === 0` as immediately complete.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
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
|
||||
|
||||
```ts
|
||||
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 ImportProgressTracker`
|
||||
- `constructor(jobId: string, totalRows: number, options?: ImportProgressTrackerOptions)`
|
||||
- `onProgress(callback: ProgressCallback): () => void`
|
||||
- `recordSuccess(): void`
|
||||
- `recordFailure(): void`
|
||||
- `recordBatchSuccess(count: number): void`
|
||||
- `recordBatchFailure(count: number): void`
|
||||
- `snapshot(): ProgressSnapshot`
|
||||
- `isComplete(): 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 === 0` short-circuits `percentComplete` to `0` and
|
||||
`estimatedRemainingMs` to `0` to avoid `NaN` from 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.
|
||||
- `startedAtIso` is captured once in the constructor and re-used on
|
||||
every snapshot to avoid `new Date(...)` allocations on the hot path.
|
||||
Reference in New Issue
Block a user