Files
2026-05-27 23:29:53 +01:00

170 lines
9.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Tasks: enterprise-cache
**Change ID**: enterprise-cache
**Package**: `@suaveplan/cache`
**Target path**: `universal/database/cache`
**Prerequisite packages on Verdaccio**: `@suaveplan/error`, `@suaveplan/types`, `@suaveplan/events`
---
## 1. Scaffold
- [x] Create directory `universal/database/cache/` with `src/`, `tests/`, `src/__benchmarks__/`, and all feature subdirectories per the layout in `proposal.md`.
- [x] Write `package.json` at version `0.1.0` with:
- `"type": "module"`, `"sideEffects": false`
- `exports` map: top-level `"."` barrel plus subpaths `./abstract/abstract-cache`, `./abstract/namespaced-cache`, `./memory/memory-cache`, `./factory/cache-factory`, `./types/common`, `./types/schemas`
- `dependencies`: `@suaveplan/error workspace:^`, `@suaveplan/types workspace:^`, `@suaveplan/events workspace:^`, `zod catalog:forms`, `@opentelemetry/api catalog:observability`, `tslib catalog:build`
- `devDependencies`: `vitest catalog:testing`, `@vitest/coverage-istanbul catalog:testing`, `fast-check catalog:testing`, `mitata catalog:testing`, `@suaveplan/testing workspace:^`
- [x] Write `tsconfig.json` extending `@suaveplan/typescript-config/library.json`.
- [x] Write `vitest.config.ts` using `createTestConfig()` from `@suaveplan/testing/config` (pool: `forks`).
- [x] Add `lint`, `typecheck`, `test`, `test:coverage`, `build`, `docs:check`, `bench` scripts to `package.json`.
---
## 2. Types + errors
### 2a. Common types (`src/types/common.ts` + `src/types/common.md`)
- [x] Define all public TS interfaces: `CacheEntry`, `CacheStats`, `CacheEvictionPolicy`, `CacheOptions`, `CacheSetOptions`, `CacheWrapOptions`, `CacheTransformHook`, `CacheMultiGetResult`, `CacheMultiSetEntry`, `CacheProviderType`, `CacheProviderConfig`, `CacheProviderConfigFor`, `CacheProviderImplementation`, `CacheFactoryConfig`, `CacheProviderRegistry`, `CacheEvents`.
- [x] Add genesis-only optional fields to `CacheOptions`: `clock?: Clock` (from `@suaveplan/types`), `events?: EventBusLike` (from `@suaveplan/types`).
- [x] Add genesis-only optional field to `CacheWrapOptions`: `signal?: AbortSignal`.
- [x] Write `src/types/common.md` (≥200 words, both simple + advanced examples, API reference for every type).
### 2b. Error codes (`src/error-codes.ts`)
- [x] Import `CodeMetadata`, `ErrorCodes`, `SuaveplanError` from `@suaveplan/error`.
- [x] Define `CacheErrorCode` union — six codes: `CACHE_KEY_INVALID`, `CACHE_CAPACITY_EXCEEDED`, `CACHE_PROVIDER_NOT_FOUND`, `CACHE_SET_FAILED`, `CACHE_GET_FAILED`, `CACHE_SERIALIZATION_FAILED`.
- [x] Define `CACHE_ERROR_METADATA` with `httpStatus`, `severity`, `retryable`, `userMessageKey`, `category`, `description` for each code.
- [x] Export `registerCacheErrorCodes()` — calls `ErrorCodes.registerMany(CACHE_ERROR_METADATA)`. Idempotent.
- [x] Call `registerCacheErrorCodes()` as an eager side-effect at module scope.
- [x] Export `CacheError` extending `SuaveplanError` carrying `CacheErrorCode` discriminant.
### 2c. Zod schemas (`src/types/schemas.ts` + `src/types/schemas.md`)
- [x] Write `CacheOptionsSchema`, `CacheSetOptionsSchema`, `CacheWrapOptionsSchema`, `CacheFactoryConfigSchema`, `CacheTransformHookSchema`.
- [x] Export inferred TypeScript types as companions where they differ from hand-written types.
- [x] Write `src/types/schemas.md` (≥200 words).
---
## 3. Constants (`src/constants/index.ts`)
- [x] Export `DEFAULT_MAX_SIZE = 1000`, `DEFAULT_TTL_SECONDS = 3600`, `DEFAULT_EVICTION_POLICY: CacheEvictionPolicy = 'lru'`, `DEFAULT_EXPIRATION_INTERVAL_MS = 60_000`.
---
## 4. Abstract base (`src/abstract/abstract-cache/`)
- [x] Implement `AbstractCache<T>` abstract class:
- Abstract methods: `get`, `set`, `delete`, `clear`, `has`, `getStats`, `getMultiple`, `setMultiple`.
- Concrete `wrap(key, fn, options?)`:
- Check cache; if hit and not stale, return cached value.
- If stale-while-revalidate: return stale value, trigger background refresh (respects `options.signal`).
- If miss or expired: call `fn()`, store result if `shouldCache(value)` returns true.
- OTel: span `cache.wrap`, histogram `cache.wrap_duration_ms`.
- All methods protected by try/catch; on internal error throw `CacheError` with appropriate code.
- [x] Write `abstract-cache.test.ts` — test `wrap` logic via a concrete in-memory subclass stub (no vi.mock).
- [x] Write `abstract-cache.md` (≥200 words).
### 4b. NamespacedCache (`src/abstract/namespaced-cache/`)
- [x] Implement `NamespacedCache<T>` wrapping an `AbstractCache<T>`:
- Prefixes all keys with `"${namespace}:"`.
- Delegates all methods to the wrapped cache.
- `getStats()` returns stats scoped to the namespace where possible.
- [x] Write `namespaced-cache.test.ts`: key isolation between namespaces, clear only clears own namespace.
- [x] Write `namespaced-cache.md` (≥200 words).
---
## 5. Memory cache (`src/memory/memory-cache/`)
- [x] Implement `MemoryCache<T>` extending `AbstractCache<T>`:
- Internal store: `Map<string, CacheEntry<T>>`.
- Injectable `clock?: Clock` — default `{ now: () => Date.now() }`.
- Injectable `events?: EventBusLike` — emit `CacheEvents` on every operation.
- LRU eviction: doubly-linked list + map for O(1) evict-on-capacity.
- LFU eviction: frequency map + bucket list for O(1) evict-on-capacity.
- FIFO eviction: insertion-order queue.
- TTL eviction: periodic sweep via `setInterval` using injectable clock.
- `setInterval` is cleared on `clear()` and optionally on explicit `dispose()`.
- `getStats()` returns live counters (no `Date.now()` — use `clock.now()`).
- Stale-while-revalidate: tracked in `CacheEntry` metadata.
- OTel: counter `cache.get_total` (hit/miss), `cache.set_total`, `cache.delete_total`, `cache.clear_total`, gauge `cache.size`, gauge `cache.hit_rate`, counter `cache.evict_total` (with `reason` attribute).
- [x] Write `memory-cache.test.ts` (≤500 lines): get/set/delete/clear/has, TTL expiry (advance injectable clock), LRU eviction, LFU eviction, stats accuracy, multi-get/set, namespace isolation, event emission.
- [x] Write `memory-cache.md` (≥200 words, both basic + advanced example with injectable clock).
---
## 6. Factory (`src/factory/cache-factory/`)
- [x] Implement `CacheFactory` class with static methods: `create`, `register`, `unregister`, `has`, `providers`.
- [x] Implement free functions: `createCache`, `createCacheWithConfig`, `registerCacheProvider`, `unregisterCacheProvider`, `hasCacheProvider`, `getCacheProviders`, `clearCacheProviders`.
- [x] `CacheFactory.create` validates the type against the registry; throws `CacheError(CACHE_PROVIDER_NOT_FOUND)` for unknown types.
- [x] Write `cache-factory.test.ts`: register/unregister, create known + unknown type, `clearCacheProviders` resets state.
- [x] Write `cache-factory.md` (≥200 words).
### Registry (`src/factory/registry/`)
- [x] `registry.ts` — auto-registers `MemoryCache` as the `memory` provider on import (side-effect).
- [x] Write `registry.md` explaining the auto-registration side-effect and tree-shaking implications.
---
## 7. Public barrel (`src/index.ts`)
- [x] Re-export: `AbstractCache`, `NamespacedCache`, `MemoryCache`, `CacheFactory`, all free factory functions, all types, error-codes, constants.
- [x] Import `./factory/registry` to trigger built-in provider registration on package load.
---
## 8. Property-based tests (`tests/property.test.ts`)
Use `fast-check`. All timeouts: `{ timeout: 30000 }`.
- [x] **LRU eviction invariant**: fill cache of capacity N (N in 5–20), set N+1 items with sequential access order; assert LRU item is evicted.
- [x] **TTL expiry**: set items with TTL T (T in 1–100 ms), advance injectable clock by T+1; assert `has()` returns false.
- [x] **`getMultiple` completeness**: for M items set and K keys queried, assert `found.size + missing.length === K`.
- [x] **Stats accuracy**: for N random get/set sequences, assert `stats.hits + stats.misses === total get calls`.
---
## 9. Benchmarks (`src/__benchmarks__/cache.bench.ts`)
Use `mitata`.
- [x] `get_hot_1k`: 1000 `get()` on LRU cache of 500 pre-populated items (all hits).
- [x] `set_1k`: 1000 `set()` with TTL = 60s.
- [x] `wrap_cold_100`: 100 `wrap()` all misses, factory returns `Promise.resolve(1)`.
- [x] `lru_eviction_pressure`: fill 2x capacity, measure throughput of evictions.
- [x] Add `bench` script to `package.json`.
- [x] Add placeholder benchmark table to `README.md`.
---
## 10. Docs
- [x] `src/types/common.md`
- [x] `src/types/schemas.md`
- [x] `src/abstract/abstract-cache/abstract-cache.md`
- [x] `src/abstract/namespaced-cache/namespaced-cache.md`
- [x] `src/memory/memory-cache/memory-cache.md`
- [x] `src/factory/cache-factory/cache-factory.md`
- [x] `src/factory/registry/registry.md`
- [x] Root `universal/database/cache/README.md` — purpose, installation, quick-start, dep table, full API symbol list, benchmarks table.
---
## 11. Verify and publish
- [x] Run `bunx turbo lint --filter=@suaveplan/cache` — zero errors.
- [x] Run `bunx turbo typecheck --filter=@suaveplan/cache` — zero errors.
- [x] Run `bunx turbo test --filter=@suaveplan/cache` — all tests pass.
- [x] Run `bunx turbo test:coverage --filter=@suaveplan/cache` — 100% on all touched modules.
- [x] Run `bun run docs:check --filter=@suaveplan/cache` — zero errors.
- [x] Confirm no `TODO`, `FIXME`, `@deprecated`, untyped `any`, `ts-ignore`, or `.skip` in `src/` or `tests/`.
- [x] Confirm `"sideEffects": false` in `package.json`.
- [x] Run `bun scripts/publish.ts --package @suaveplan/cache`.
- [x] Update `CLAUDE.md` package listing to include `@suaveplan/cache`.
- [x] Archive: move `openspec/changes/enterprise-cache/` to `openspec/archive/2026-04-24-enterprise-cache/` and fold spec delta into `openspec/specs/database/spec.md`.