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

9.6 KiB
Raw Permalink Blame History

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

  • Create directory universal/database/cache/ with src/, tests/, src/__benchmarks__/, and all feature subdirectories per the layout in proposal.md.
  • 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:^
  • Write tsconfig.json extending @suaveplan/typescript-config/library.json.
  • Write vitest.config.ts using createTestConfig() from @suaveplan/testing/config (pool: forks).
  • 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)

  • Define all public TS interfaces: CacheEntry, CacheStats, CacheEvictionPolicy, CacheOptions, CacheSetOptions, CacheWrapOptions, CacheTransformHook, CacheMultiGetResult, CacheMultiSetEntry, CacheProviderType, CacheProviderConfig, CacheProviderConfigFor, CacheProviderImplementation, CacheFactoryConfig, CacheProviderRegistry, CacheEvents.
  • Add genesis-only optional fields to CacheOptions: clock?: Clock (from @suaveplan/types), events?: EventBusLike (from @suaveplan/types).
  • Add genesis-only optional field to CacheWrapOptions: signal?: AbortSignal.
  • Write src/types/common.md (≥200 words, both simple + advanced examples, API reference for every type).

2b. Error codes (src/error-codes.ts)

  • Import CodeMetadata, ErrorCodes, SuaveplanError from @suaveplan/error.
  • Define CacheErrorCode union — six codes: CACHE_KEY_INVALID, CACHE_CAPACITY_EXCEEDED, CACHE_PROVIDER_NOT_FOUND, CACHE_SET_FAILED, CACHE_GET_FAILED, CACHE_SERIALIZATION_FAILED.
  • Define CACHE_ERROR_METADATA with httpStatus, severity, retryable, userMessageKey, category, description for each code.
  • Export registerCacheErrorCodes() — calls ErrorCodes.registerMany(CACHE_ERROR_METADATA). Idempotent.
  • Call registerCacheErrorCodes() as an eager side-effect at module scope.
  • Export CacheError extending SuaveplanError carrying CacheErrorCode discriminant.

2c. Zod schemas (src/types/schemas.ts + src/types/schemas.md)

  • Write CacheOptionsSchema, CacheSetOptionsSchema, CacheWrapOptionsSchema, CacheFactoryConfigSchema, CacheTransformHookSchema.
  • Export inferred TypeScript types as companions where they differ from hand-written types.
  • Write src/types/schemas.md (≥200 words).

3. Constants (src/constants/index.ts)

  • 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/)

  • 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.
  • Write abstract-cache.test.ts — test wrap logic via a concrete in-memory subclass stub (no vi.mock).
  • Write abstract-cache.md (≥200 words).

4b. NamespacedCache (src/abstract/namespaced-cache/)

  • 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.
  • Write namespaced-cache.test.ts: key isolation between namespaces, clear only clears own namespace.
  • Write namespaced-cache.md (≥200 words).

5. Memory cache (src/memory/memory-cache/)

  • 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).
  • 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.
  • Write memory-cache.md (≥200 words, both basic + advanced example with injectable clock).

6. Factory (src/factory/cache-factory/)

  • Implement CacheFactory class with static methods: create, register, unregister, has, providers.
  • Implement free functions: createCache, createCacheWithConfig, registerCacheProvider, unregisterCacheProvider, hasCacheProvider, getCacheProviders, clearCacheProviders.
  • CacheFactory.create validates the type against the registry; throws CacheError(CACHE_PROVIDER_NOT_FOUND) for unknown types.
  • Write cache-factory.test.ts: register/unregister, create known + unknown type, clearCacheProviders resets state.
  • Write cache-factory.md (≥200 words).

Registry (src/factory/registry/)

  • registry.ts — auto-registers MemoryCache as the memory provider on import (side-effect).
  • Write registry.md explaining the auto-registration side-effect and tree-shaking implications.

7. Public barrel (src/index.ts)

  • Re-export: AbstractCache, NamespacedCache, MemoryCache, CacheFactory, all free factory functions, all types, error-codes, constants.
  • 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 }.

  • 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.
  • TTL expiry: set items with TTL T (T in 1–100 ms), advance injectable clock by T+1; assert has() returns false.
  • getMultiple completeness: for M items set and K keys queried, assert found.size + missing.length === K.
  • 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.

  • get_hot_1k: 1000 get() on LRU cache of 500 pre-populated items (all hits).
  • set_1k: 1000 set() with TTL = 60s.
  • wrap_cold_100: 100 wrap() all misses, factory returns Promise.resolve(1).
  • lru_eviction_pressure: fill 2x capacity, measure throughput of evictions.
  • Add bench script to package.json.
  • Add placeholder benchmark table to README.md.

10. Docs

  • src/types/common.md
  • src/types/schemas.md
  • src/abstract/abstract-cache/abstract-cache.md
  • src/abstract/namespaced-cache/namespaced-cache.md
  • src/memory/memory-cache/memory-cache.md
  • src/factory/cache-factory/cache-factory.md
  • src/factory/registry/registry.md
  • Root universal/database/cache/README.md — purpose, installation, quick-start, dep table, full API symbol list, benchmarks table.

11. Verify and publish

  • Run bunx turbo lint --filter=@suaveplan/cache — zero errors.
  • Run bunx turbo typecheck --filter=@suaveplan/cache — zero errors.
  • Run bunx turbo test --filter=@suaveplan/cache — all tests pass.
  • Run bunx turbo test:coverage --filter=@suaveplan/cache — 100% on all touched modules.
  • Run bun run docs:check --filter=@suaveplan/cache — zero errors.
  • Confirm no TODO, FIXME, @deprecated, untyped any, ts-ignore, or .skip in src/ or tests/.
  • Confirm "sideEffects": false in package.json.
  • Run bun scripts/publish.ts --package @suaveplan/cache.
  • Update CLAUDE.md package listing to include @suaveplan/cache.
  • Archive: move openspec/changes/enterprise-cache/ to openspec/archive/2026-04-24-enterprise-cache/ and fold spec delta into openspec/specs/database/spec.md.