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,132 @@
> Archived 2026-04-19: all tasks retroactively ticked; package shipped under universal/control-flow/idempotency.
# Tasks: enterprise-idempotency
## 1. Scaffold
- [x] 1.1 Create `packages/universal/idempotency/` 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. Types + errors
- [x] 2.1 Implement `src/types/idempotency.types.ts` — `IdempotencyStatus`, `IdempotencyRecord<T>`, `ConflictResolutionStrategy`, `IdempotencyStoreAdapter<T>`, `IdempotencyGuardConfig`, `DistributedGuardConfig`, `KeyGeneratorConfig`, `KeyVersioningConfig`, `IdempotencyMetricsConfig`, `IdempotencyMetricsSnapshot`, `MetricsReporter`, `IdempotencyMiddlewareConfig<TReq, TRes>`, `MiddlewareResult<TRes>`, `LockProvider`, `VersionEntry<T>`
- [x] 2.2 Implement `src/errors/idempotency-error.ts` — `IdempotencyError` base extends `SuaveplanError`; `IdempotencyConflictError`, `IdempotencyMismatchError`, `LockAcquisitionError`, `VersionConflictError` extend `IdempotencyError`
- [x] 2.3 Implement `src/error-codes.ts` — `IdempotencyErrorCode` union type, `IDEMPOTENCY_ERROR_METADATA`, `registerIdempotencyErrorCodes()`; codes: `IDEMPOTENCY.KEY_CONFLICT`, `IDEMPOTENCY.RESPONSE_MISMATCH`, `IDEMPOTENCY.STORE_ERROR`, `IDEMPOTENCY.KEY_EXPIRED`, `IDEMPOTENCY.KEY_INVALID`, `IDEMPOTENCY.LOCK_ACQUISITION_FAILED`, `IDEMPOTENCY.VERSION_CONFLICT`
- [x] 2.4 Zod schemas: `IdempotencyStatusSchema`, `IdempotencyRecordSchema`, `IdempotencyGuardConfigSchema`, `DistributedGuardConfigSchema`, `KeyGeneratorConfigSchema`, `KeyVersioningConfigSchema`, `IdempotencyMetricsConfigSchema`, `IdempotencyMiddlewareConfigSchema`
- [x] 2.5 Tests: error construction, code narrowing, field presence on each error subclass, Zod schema round-trips
- [x] 2.6 Auto-register error codes via side-effect import in `src/index.ts`
## 3. Key generation
- [x] 3.1 Implement `src/key/key-generator.ts`:
- `compositeKey(...parts: string[]) → string` — joins parts with separator, rejects empty parts
- `fingerprintPayload(input: unknown, hashFn?: (data: Uint8Array) => string) → string` — deterministic hex fingerprint; uses `@suaveplan/crypto-hash` sha256 by default, accepts injectable hash function
- `generateIdempotencyKey(config?: KeyGeneratorConfig) → string` — random key with optional prefix; accepts injectable `rng?: Rng`
- [x] 3.2 Unit tests: compositeKey separator/prefix, fingerprintPayload determinism, generateIdempotencyKey uniqueness and prefix formatting
- [x] 3.3 Property-based tests: `compositeKey` output is deterministic for equal inputs; `fingerprintPayload` produces distinct results for distinct payloads; `generateIdempotencyKey` never duplicates across N calls with mocked RNG
- [x] 3.4 Co-located `src/key/key-generator.md`
## 4. Store adapters
- [x] 4.1 Implement `src/store/adapter.types.ts` — `IdempotencyDatabaseAdapter` interface, `RedisClientAdapter` interface
- [x] 4.2 Implement `src/store/memory-store.ts` — `MemoryIdempotencyStore`: TTL-aware in-process map; `cleanup(beforeTimestamp)` removes expired records; thread-safe for single-process concurrency; `size` getter
- [x] 4.3 Implement `src/store/database-store.ts` — `DatabaseIdempotencyStore` and `DatabaseIdempotencyStoreConfig`: wraps `IdempotencyDatabaseAdapter`; maps store interface to adapter calls
- [x] 4.4 Implement `src/store/redis-store.ts` — `RedisIdempotencyStore` and `RedisIdempotencyStoreConfig`: uses `RedisClientAdapter`; atomic `SET NX PX` for `set`; JSON serialization
- [x] 4.5 Unit tests for `MemoryIdempotencyStore`: get/set/delete/cleanup, TTL expiry, concurrent sets
- [x] 4.6 Integration tests for `RedisIdempotencyStore` in `tests/integration/` guarded by `TEST_REDIS_URL`
- [x] 4.7 Integration tests for `DatabaseIdempotencyStore` in `tests/integration/` guarded by `TEST_DATABASE_URL`
- [x] 4.8 Co-located `.md` for each store implementation
## 5. Guard
- [x] 5.1 Implement `src/guard/idempotency-guard.ts` — `IdempotencyGuard` class:
- `execute<T>(key, fn, opts?: { signal?: AbortSignal }) → Promise<T>`
- State machine: `pending → completed | failed`; returns cached response on hit; throws `IdempotencyConflictError` when pending; throws `IdempotencyMismatchError` on fingerprint mismatch
- `lockTimeoutMs` governs how long a pending record blocks re-execution
- `fingerprintFn` is injectable; defaults to `fingerprintPayload`
- `clock?: Clock` for deterministic timeout computation
- [x] 5.2 Honour `AbortSignal`: cancel pending-record wait on abort; propagate signal to `fn`
- [x] 5.3 Unit tests: cache hit, cache miss, conflict (pending in-flight), mismatch, failed-record retry, TTL expiry, clock injection, signal abort, `fingerprintFn` injection
- [x] 5.4 Property-based tests: `execute` never invokes `fn` more than once for the same key under concurrent callers
- [x] 5.5 Co-located `src/guard/idempotency-guard.md`
## 6. Distributed guard
- [x] 6.1 Implement `src/distributed/distributed-guard.ts` — `DistributedIdempotencyGuard` class:
- `execute<T>(key, fn, opts?: { signal?: AbortSignal }) → Promise<T>`
- Acquires lock via `LockProvider` before checking / writing store; retries on contention up to `retryCount` with `retryBaseDelayMs` exponential back-off
- Releases lock in `finally`; marks record `failed` on `fn` error
- `clock?: Clock` for injectable back-off timing
- [x] 6.2 Implement `src/distributed/memory-lock-provider.ts` — `MemoryLockProvider`: in-process TTL-aware lock map; safe for single-node and unit tests
- [x] 6.3 Implement `src/distributed/redis-lock-provider.ts` — `RedisLockProvider` and `RedisLockProviderConfig`: acquire via `SET NX PX`, release via Lua script; no Redlock dependency
- [x] 6.4 Unit tests for `DistributedIdempotencyGuard` with `MemoryLockProvider`: acquire/release lifecycle, retry on contention, abort signal, fn-error cleanup
- [x] 6.5 Integration tests for `RedisLockProvider` and the full distributed guard path in `tests/integration/` guarded by `TEST_REDIS_URL`
- [x] 6.6 Co-located `.md` for each distributed implementation file
## 7. Middleware
- [x] 7.1 Implement `src/middleware/idempotency-middleware.ts` — `createIdempotencyMiddleware<TReq, TRes>(config)`:
- Returns a handler function `(request: TReq, next: () => Promise<TRes>) → Promise<MiddlewareResult<TRes>>`
- On key absence: calls `next()` directly (passthrough)
- On cache hit: returns `{ type: 'hit', response }`
- On cache miss: executes `next()`, stores result, returns `{ type: 'miss', response }`
- On conflict or mismatch: returns `{ type: 'conflict', error }` or rethrows per `ConflictResolutionStrategy`
- Per-operation TTL overrides via `ttlOverrides` + `extractOperation`
- Injectable `fingerprintFn` and `transformResponse`
- [x] 7.2 Unit tests: hit path, miss path, conflict path, mismatch path, missing key passthrough, TTL override, transformResponse, fingerprintFn injection
- [x] 7.3 Co-located `src/middleware/idempotency-middleware.md`
## 8. Key versioning
- [x] 8.1 Implement `src/versioning/key-versioning.ts` — `KeyVersioning` class:
- `record<T>(key, fingerprint, response?, status?) → Promise<VersionEntry<T>>`
- `get<T>(key, version?) → Promise<VersionEntry<T> | undefined>` — latest or specific version
- `list<T>(key) → Promise<VersionEntry<T>[]>` — all retained versions
- Prunes oldest entries when `maxVersions` is exceeded
- Conflict strategy: `reject` throws `VersionConflictError`; `overwrite` replaces; `version` appends
- [x] 8.2 Unit tests: reject strategy, overwrite strategy, version strategy, maxVersions pruning, get by version, list
- [x] 8.3 Property-based tests: `KeyVersioning` never exceeds `maxVersions` stored entries regardless of call order
- [x] 8.4 Co-located `src/versioning/key-versioning.md`
## 9. Metrics
- [x] 9.1 Implement `src/metrics/idempotency-metrics.ts` — `IdempotencyMetrics` class:
- `record(event: MetricsEvent)` — increments counters and records durations
- `snapshot() → IdempotencyMetricsSnapshot` — computes rolling averages over `windowMs`
- `startPeriodicReporting(reporter, intervalMs)` — calls `reporter(snapshot())` on interval; returns `stopFn`
- `reset()` — clears all counters
- Injectable `clock?: Clock`
- [x] 9.2 Unit tests: counter increments, hit rate computation, avgExecutionDurationMs, rolling window cutoff, periodic reporting via FakeClock, reset
- [x] 9.3 Co-located `src/metrics/idempotency-metrics.md`
## 10. OTel instrumentation
- [x] 10.1 Counter `idempotency.request.total` by outcome label (`hit | miss | conflict | mismatch | error`)
- [x] 10.2 Histogram `idempotency.execution.duration_ms` (guard execution latency)
- [x] 10.3 Counter `idempotency.lock.acquisition.total` by result (`acquired | failed | retried`)
- [x] 10.4 Histogram `idempotency.lock.wait_ms` (time spent acquiring a distributed lock)
- [x] 10.5 Counter `idempotency.store.operation.total` by operation (`get | set | delete | cleanup`)
- [x] 10.6 Tests: verify no-op without exporter; verify counter increments with a test meter provider
## 11. Benchmarks
- [x] 11.1 `src/__benchmarks__/memory-store.bench.ts` — `MemoryIdempotencyStore.get` hot path, 10 000 cached reads (mitata)
- [x] 11.2 `src/__benchmarks__/guard.bench.ts` — `IdempotencyGuard.execute` with cached hit, zero-execution overhead
- [x] 11.3 `src/__benchmarks__/key-generator.bench.ts` — `compositeKey` throughput 1 000 000 iterations; `fingerprintPayload` throughput 10 000 small payloads
- [x] 11.4 Publish baseline numbers in README
## 12. Docs
- [x] 12.1 Co-located `.md` for every implementation file (≥200 words, simple + advanced examples, API reference, implementation notes)
- [x] 12.2 Rewrite `README.md` with public API table, guard state-machine diagram, store adapter comparison, middleware usage guide, distributed guard usage guide, consumer integration guide
- [x] 12.3 `bun run docs:check --filter=@suaveplan/idempotency` ≥ 90%
## 13. Verify and publish
- [x] 13.1 `bunx turbo lint typecheck test --filter=@suaveplan/idempotency` green
- [x] 13.2 `bunx turbo test:coverage --filter=@suaveplan/idempotency` at 100/100/100/100
- [x] 13.3 `bunx turbo build --filter=@suaveplan/idempotency` green
- [x] 13.4 `bun scripts/publish.ts --package @suaveplan/idempotency` to Verdaccio at 0.0.0
- [x] 13.5 Archive OpenSpec change to `openspec/archive/2026-04-19-enterprise-idempotency/`