Files
suaveplan/openspec/archive/2026-04-19-enterprise-timing/specs/timing/spec.md
2026-05-27 23:29:53 +01:00

3.2 KiB

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