58 lines
3.2 KiB
Markdown
58 lines
3.2 KiB
Markdown
# Capability Spec: Timing
|
|
|
|
**Package**: `@suaveplan/timing`
|
|
**Version**: 0.0.0 (genesis)
|
|
**Status**: ADDED
|
|
|
|
---
|
|
|
|
## ADDED Requirements
|
|
|
|
### TM-01 — Injectable clock
|
|
|
|
Every module that reads wall-clock time MUST accept `{ clock?: Clock }` from `@suaveplan/types`. No `Date.now()` or `setTimeout` at module scope. Default clock uses `Date.now()`.
|
|
|
|
### TM-02 — AbortSignal support
|
|
|
|
`Scheduler.drain(signal?)` and streaming/iterative helpers MUST honour `AbortSignal`. Abort resolves the operation cleanly without throwing unless the signal fires mid-operation, in which case an `AbortError`-compatible rejection is emitted.
|
|
|
|
### TM-03 — Stopwatch
|
|
|
|
`createStopwatch` MUST return a `Stopwatch` whose `.elapsed` is monotonically non-decreasing between `.start()` and `.stop()`. `.lap()` MUST record the split time relative to the previous lap start (or the stopwatch start). `.reset()` MUST clear all laps and reset elapsed to 0.
|
|
|
|
### TM-04 — Throttle / debounce
|
|
|
|
`throttle` MUST emit at most one invocation per `wait` window (leading or trailing edge, configurable). `debounce` MUST delay invocation until `wait` ms after the last call; `.flush()` MUST invoke immediately; `.cancel()` MUST suppress the pending invocation. `maxWait` option on `debounce` caps total delay.
|
|
|
|
### TM-05 — Decay
|
|
|
|
`createDecay` MUST accept a `DecayFunction` (or preset name `'exponential' | 'linear'`). `.tick(elapsed)` advances the decay by `elapsed` milliseconds. The returned `.value` MUST be bounded in `[target, initial]` (or `[initial, target]` for growth). `.reset()` MUST restore `initial`.
|
|
|
|
### TM-06 — Scheduler
|
|
|
|
`createScheduler` MUST execute scheduled callbacks at or after their requested delay using the injected clock. `.cancel(id)` MUST prevent the callback from firing. `.drain(signal?)` MUST flush all pending callbacks in delay order. After `.dispose()`, any operation MUST throw `TimingError` with code `TIMING.SCHEDULER.DISPOSED`.
|
|
|
|
### TM-07 — Timer and countdown
|
|
|
|
`createTimer` MUST fire its tick event at each `interval`. `createCountdown` MUST validate that `duration > 0`; otherwise throw `TimingError` with code `TIMING.COUNTDOWN.INVALID_DURATION`. `.pause()` / `.resume()` MUST preserve `.remaining` across pause/resume cycles.
|
|
|
|
### TM-08 — Vector clock
|
|
|
|
`createVectorClock` MUST maintain a `Map<nodeId, number>` counter. `.increment()` MUST increment the local node's counter. `.update(remote)` MUST take the component-wise maximum. `vectorClockCompare` MUST return `'before' | 'after' | 'concurrent' | 'equal'` correctly for all pairs. `.toJSON()` / `.fromJSON()` MUST produce a lossless round-trip.
|
|
|
|
### TM-09 — Structured errors
|
|
|
|
All thrown errors MUST extend `TimingError` which extends `SuaveplanError`. Codes under `TIMING.*` namespace.
|
|
|
|
### TM-10 — Zod schemas
|
|
|
|
Every public options type MUST have a corresponding Zod schema in a sibling `*.schemas.ts` file.
|
|
|
|
### TM-11 — OTel instrumentation
|
|
|
|
Stopwatch elapsed, scheduler queue depth, and timer tick counter MUST emit via `@opentelemetry/api`. All instruments are no-ops when no exporter is registered.
|
|
|
|
### TM-12 — sideEffects: false + coverage 100%
|
|
|
|
`"sideEffects": false` with tree-shake test. Vitest thresholds at 100/100/100/100. No `.skip` / `.only` in `src/`.
|