# 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` 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/`.