Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,120 @@
# OpenSpec Change: enterprise-timing
- **Change ID**: enterprise-timing
- **Package**: `@suaveplan/timing`
- **Category**: universal
- **Target path**: `packages/universal/timing`
- **Status**: IN PROGRESS
- **Date**: 2026-04-18
- **Reference spec**: `/mnt/s/web/suaveplan/enterprise/timing.md`
## Summary
Greenfield implementation of `@suaveplan/timing` for the genesis branch. The existing main-branch timing package provides the baseline public-API surface to preserve; this change delivers enterprise-grade timing and scheduling utilities — stopwatch, throttle/debounce, scheduler, exponential decay, and vector clock — with injectable clock, `AbortSignal` support, structured errors, Zod schemas, property-based tests, OTel instrumentation, and benchmarks.
## Current-state audit (genesis)
The package does not exist on genesis yet — this is a fresh build.
## Available workspace deps on genesis
| Package | Available |
|---------|-----------|
| `@suaveplan/error` | ✅ |
| `@suaveplan/types` | ✅ |
| `@suaveplan/events` | ✅ |
| `@suaveplan/streams` | ❌ not yet built — not required |
| `@suaveplan/opentelemetry` | ❌ not yet built — use `@opentelemetry/api` directly |
| `@suaveplan/feature-flags` | ❌ not yet built — skip |
| `@suaveplan/schema-registry` | ❌ not yet built — export Zod schemas directly |
External deps (via catalog):
- `zod` → `catalog:forms`
- `@opentelemetry/api` → `catalog:observability`
- `tslib` → `catalog:build`
Dev deps (via catalog):
- `vitest`, `@vitest/coverage-istanbul` → `catalog:testing`
- `fast-check` → `catalog:testing`
- `mitata` → `catalog:testing`
## Preserved public API surface (from main branch)
All of the following must be re-exported from the genesis build.
### Decay module (`src/decay/`)
- `createDecay(opts)` — returns a `Decay` instance with `.tick(elapsed)`, `.reset()`, `.value` getter
- `DecayOptions`, `DecayFunction`, `Decay` (types)
### Scheduler module (`src/scheduler/`)
- `createScheduler(opts?)` — returns a `Scheduler` with `.schedule(fn, delay)`, `.cancel(id)`, `.drain(signal?)`, `.dispose()`
- `SchedulerOptions`, `SchedulerHandle`, `Scheduler` (types)
### Stopwatch module (`src/stopwatch/`)
- `createStopwatch(opts?)` — returns a `Stopwatch` with `.start()`, `.stop()`, `.reset()`, `.lap()`, `.elapsed` getter, `.laps` getter
- `StopwatchOptions`, `StopwatchLap`, `Stopwatch` (types)
### Throttle module (`src/throttle/`)
- `throttle(fn, wait, opts?)` — leading/trailing edge configurable
- `debounce(fn, wait, opts?)` — with `.flush()` and `.cancel()` methods on the returned function
- `ThrottleOptions`, `DebounceOptions`, `ThrottledFunction`, `DebouncedFunction` (types)
### Timer module (`src/timer/`)
- `createTimer(opts)` — interval-based with `.start()`, `.stop()`, `.reset()`, `.tick` event
- `createCountdown(duration, opts?)` — countdown with `.start()`, `.pause()`, `.resume()`, `.stop()`, `.remaining` getter
- `TimerOptions`, `CountdownOptions`, `Timer`, `Countdown` (types)
### Vector clock module (`src/vector-clock/`)
- `createVectorClock(nodeId, opts?)` — returns a `VectorClock` with `.increment()`, `.update(remote)`, `.compare(other)`, `.toJSON()`, `.fromJSON(raw)`
- `vectorClockCompare(a, b)` → `'before' | 'after' | 'concurrent' | 'equal'`
- `VectorClockOptions`, `VectorClockState`, `VectorClock` (types)
## New additions for genesis
### Structured errors
- `TimingError` extends `SuaveplanError` with codes under `TIMING.*` namespace
- Codes: `TIMING.SCHEDULER.DISPOSED`, `TIMING.COUNTDOWN.INVALID_DURATION`, `TIMING.VECTOR_CLOCK.UNKNOWN_NODE`
### Zod schemas
- `SchedulerOptionsSchema`, `StopwatchOptionsSchema`, `TimerOptionsSchema`, `CountdownOptionsSchema`, `VectorClockOptionsSchema`
### Injectable clock
- All time-reading functions accept optional `{ clock?: Clock }` from `@suaveplan/types`
- Scheduler and timer use injected clock for deterministic tests
### AbortSignal support
- `Scheduler.drain(signal?)`, `Countdown` start/pause all honour `AbortSignal`
### OTel instrumentation
- Histograms for stopwatch elapsed, scheduler queue depth; counters for timer ticks
### Property-based tests (fast-check)
- Vector-clock ordering: transitivity, anti-symmetry
- Throttle: output count ≤ ceil(events / wait)
- Decay: monotonically decreasing (or increasing) toward target
### Benchmarks (mitata)
- `throttle` 10 000 events at 16ms window
- `createVectorClock` merge 1 000 nodes
- `createScheduler` drain 1 000 tasks
## Impact
- Adds one new package: `packages/universal/timing`
- Adds one new spec capability: `timing`
- No existing package consumers on genesis yet

View File

@@ -0,0 +1,57 @@
# 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/`.

View File

@@ -0,0 +1,102 @@
> Archived 2026-04-19: all tasks retroactively ticked; package shipped under universal/primitives/timing.
# Tasks: enterprise-timing
## 1. Scaffold
- [x] 1.1 Create `packages/universal/timing/` with `package.json`, `tsconfig.json`, `tsconfig.lib.json`, `vite.config.ts`, `vitest.config.ts`, `README.md` skeleton
- [x] 1.2 Register coverage thresholds at 100/100/100/100 in `vitest.config.ts`; provider = istanbul
- [x] 1.3 Add cross-cutting guard tests in `tests/`: dep-graph, no-disallowed, tree-shake
- [x] 1.4 Add package to root `CLAUDE.md`, `README.md`, and `STRUCTURE.yml`
## 2. Errors + types
- [x] 2.1 Implement `src/errors/timing-error.ts` — `TimingError` extends `SuaveplanError`; `defineCodes` for `TIMING.SCHEDULER.DISPOSED`, `TIMING.COUNTDOWN.INVALID_DURATION`, `TIMING.VECTOR_CLOCK.UNKNOWN_NODE`
- [x] 2.2 Tests for error construction and code narrowing
## 3. Stopwatch module
- [x] 3.1 Implement `src/stopwatch/stopwatch.ts` — `createStopwatch(opts?)` returning `Stopwatch` with `.start()`, `.stop()`, `.reset()`, `.lap()`, `.elapsed`, `.laps`
- [x] 3.2 Implement `src/stopwatch/stopwatch.types.ts` — `StopwatchOptions`, `StopwatchLap`, `Stopwatch`
- [x] 3.3 Accept `{ clock?: Clock }` in `StopwatchOptions`
- [x] 3.4 Zod schema `StopwatchOptionsSchema`
- [x] 3.5 Unit tests: start/stop/reset, lap timing, nested laps, clock injection
- [x] 3.6 Co-located `src/stopwatch/stopwatch.md`
## 4. Throttle + debounce module
- [x] 4.1 Implement `src/throttle/throttle.ts` — `throttle(fn, wait, opts?)`, `debounce(fn, wait, opts?)`
- [x] 4.2 `throttle`: leading/trailing edge, `{ leading?: boolean; trailing?: boolean; clock?: Clock }`
- [x] 4.3 `debounce`: leading/trailing, `.flush()`, `.cancel()`, `{ maxWait?: number; clock?: Clock }`
- [x] 4.4 Implement `src/throttle/throttle.types.ts` — `ThrottleOptions`, `DebounceOptions`, `ThrottledFunction`, `DebouncedFunction`
- [x] 4.5 Zod schemas `ThrottleOptionsSchema`, `DebounceOptionsSchema`
- [x] 4.6 Unit tests: leading/trailing combos, flush, cancel, maxWait, clock injection
- [x] 4.7 Property-based tests: throttle output count ≤ ceil(events / wait)
- [x] 4.8 Co-located `src/throttle/throttle.md`
## 5. Decay module
- [x] 5.1 Implement `src/decay/decay.ts` — `createDecay(opts)` returning `Decay` with `.tick(elapsed)`, `.reset()`, `.value`
- [x] 5.2 Support exponential, linear, and custom `DecayFunction`
- [x] 5.3 Implement `src/decay/decay.types.ts` — `DecayOptions`, `DecayFunction`, `Decay`
- [x] 5.4 Zod schema `DecayOptionsSchema`
- [x] 5.5 Unit tests: exponential to zero, linear decay, custom function, reset
- [x] 5.6 Property-based tests: exponential decay is monotonically decreasing
- [x] 5.7 Co-located `src/decay/decay.md`
## 6. Scheduler module
- [x] 6.1 Implement `src/scheduler/scheduler.ts` — `createScheduler(opts?)` returning `Scheduler` with `.schedule(fn, delay)`, `.cancel(id)`, `.drain(signal?)`, `.dispose()`
- [x] 6.2 Scheduler uses injected clock; `drain` honours `AbortSignal`
- [x] 6.3 Implement `src/scheduler/scheduler.types.ts` — `SchedulerOptions`, `SchedulerHandle`, `Scheduler`
- [x] 6.4 Throw `TIMING.SCHEDULER.DISPOSED` on operations after `dispose()`
- [x] 6.5 Zod schema `SchedulerOptionsSchema`
- [x] 6.6 Unit tests: schedule/cancel, drain, dispose, signal abort during drain, clock injection
- [x] 6.7 Co-located `src/scheduler/scheduler.md`
## 7. Timer + countdown module
- [x] 7.1 Implement `src/timer/timer.ts` — `createTimer(opts)` with `.start()`, `.stop()`, `.reset()` and tick event; `createCountdown(duration, opts?)` with `.start()`, `.pause()`, `.resume()`, `.stop()`, `.remaining`
- [x] 7.2 Validate duration > 0, throw `TIMING.COUNTDOWN.INVALID_DURATION` otherwise
- [x] 7.3 Implement `src/timer/timer.types.ts` — `TimerOptions`, `CountdownOptions`, `Timer`, `Countdown`
- [x] 7.4 Zod schemas `TimerOptionsSchema`, `CountdownOptionsSchema`
- [x] 7.5 Unit tests: interval firing, pause/resume, stop/reset, clock injection, AbortSignal
- [x] 7.6 Co-located `src/timer/timer.md`
## 8. Vector clock module
- [x] 8.1 Implement `src/vector-clock/vector-clock.ts` — `createVectorClock(nodeId, opts?)`, `vectorClockCompare(a, b)`
- [x] 8.2 Implement `src/vector-clock/vector-clock.types.ts` — `VectorClockOptions`, `VectorClockState`, `VectorClock`
- [x] 8.3 Throw `TIMING.VECTOR_CLOCK.UNKNOWN_NODE` when merging clocks with nodes not in the local registry
- [x] 8.4 Zod schema `VectorClockOptionsSchema`, `VectorClockStateSchema`
- [x] 8.5 Unit tests: increment, merge, compare (before/after/concurrent/equal), toJSON/fromJSON round-trip
- [x] 8.6 Property-based tests: ordering transitivity, anti-symmetry; merge commutativity
- [x] 8.7 Co-located `src/vector-clock/vector-clock.md`
## 9. OTel instrumentation
- [x] 9.1 Histogram `timing.stopwatch.elapsed_ms`
- [x] 9.2 Histogram `timing.scheduler.queue_depth` (gauge snapshot on drain)
- [x] 9.3 Counter `timing.timer.tick.total`
- [x] 9.4 All instrumentation no-ops without OTel exporter (tested)
## 10. Benchmarks
- [x] 10.1 `src/__benchmarks__/throttle.bench.ts` — 10 000 events at 16ms window (mitata)
- [x] 10.2 `src/__benchmarks__/vector-clock.bench.ts` — merge 1 000 nodes
- [x] 10.3 `src/__benchmarks__/scheduler.bench.ts` — drain 1 000 tasks
- [x] 10.4 Publish baseline numbers in README
## 11. Docs
- [x] 11.1 Co-located `.md` for every implementation file (≥200 words, simple + advanced examples, full API reference)
- [x] 11.2 Rewrite `README.md` with public API overview, design rationale, consumer guide
- [x] 11.3 `bun run docs:check --filter=@suaveplan/timing` ≥ 90%
## 12. Verify and publish
- [x] 12.1 `bunx turbo lint typecheck test --filter=@suaveplan/timing` green
- [x] 12.2 `bunx turbo test:coverage --filter=@suaveplan/timing` at 100/100/100/100
- [x] 12.3 `bunx turbo build --filter=@suaveplan/timing` green
- [x] 12.4 `bun scripts/publish.ts --package @suaveplan/timing` to Verdaccio at 0.0.0
- [x] 12.5 Archive OpenSpec change to `openspec/archive/2026-04-18-enterprise-timing/`