6.6 KiB
OpenSpec Change: enterprise-browser-cache-browser
- Change ID: enterprise-browser-cache-browser
- Package:
@suaveplan/cache-browser - Category: browser
- Target path:
browser/storage/cache-browser - Status: IN PROGRESS
- Date: 2026-04-24
- Reference spec:
/mnt/s/web/suaveplan/enterprise/cache-browser.md
Summary
Deliver @suaveplan/cache-browser on the genesis branch at enterprise quality. The package provides browser-side cache implementations (LocalStorageCache, SessionStorageCache, IndexedDBCache) plugging into @suaveplan/cache, plus quota utilities, pluggable serializers, cross-tab invalidation via @suaveplan/broadcast-channel, pluggable eviction strategies, and OTel metrics. Auto-registration contract and sideEffects declaration are aligned.
Current-state audit (genesis)
Fresh build — does not exist on genesis yet.
Available workspace deps on genesis
| Package | Role |
|---|---|
@suaveplan/cache (workspace:^) |
Cache interface, CacheFactory |
@suaveplan/events (workspace:^) |
Storage-event multiplexer |
@suaveplan/error (workspace:^) |
Typed cache errors |
@suaveplan/types (workspace:^) |
LoggerLike |
@suaveplan/broadcast-channel (workspace:^) |
Cross-tab invalidation |
@suaveplan/crypto (workspace:^) |
Optional AES-GCM encryption transform |
@suaveplan/schema-registry (workspace:^) |
Register cache config schemas |
zod (catalog:forms) |
Config schemas |
@opentelemetry/api (catalog:observability) |
Cache hit/miss/eviction metrics |
tslib (catalog:build) |
Runtime helpers |
Dev: vitest, @vitest/coverage-istanbul, fake-indexeddb (catalog:testing), @playwright/test, @suaveplan/testing.
Preserved public API surface
// Three cache implementations
class LocalStorageCache implements Cache { constructor(config: LocalStorageCacheConfig) }
class SessionStorageCache implements Cache { constructor(config: SessionStorageCacheConfig) }
class IndexedDBCache implements Cache { constructor(config: IndexedDBCacheConfig) }
// Subpath exports
// @suaveplan/cache-browser/localstorage
// @suaveplan/cache-browser/sessionstorage
// @suaveplan/cache-browser/indexeddb
// @suaveplan/cache-browser/register (explicit auto-registration)
// Quota utilities
function getStorageQuota(): Promise<StorageQuota>
function isStorageNearQuota(threshold?: number): Promise<boolean>
function estimateStorageSize(): Promise<number>
function handleQuotaExceeded(cache: Cache, strategy: EvictionStrategy): Promise<void>
function isStorageAvailable(type: 'localStorage' | 'sessionStorage'): boolean
// Serialization
function defaultSerializer<T>(value: T): string
function defaultDeserializer<T>(raw: string): T
function isSerializable(value: unknown): boolean
function safeDeserialize<T>(raw: string): T | undefined
// Storage events
class StorageEventHandler { ... }
const globalStorageEventHandler: StorageEventHandler
// Config types
interface LocalStorageCacheConfig { namespace: string; ttl?: number; serializer?: Serializer<unknown> }
interface SessionStorageCacheConfig { namespace: string; ttl?: number; serializer?: Serializer<unknown> }
interface IndexedDBCacheConfig { dbName: string; storeName: string; ttl?: number; schemaVersion?: number; serializer?: Serializer<unknown> }
New additions for genesis
1. sideEffects contract
Auto-registration moves to explicit @suaveplan/cache-browser/register subpath; "sideEffects": false is then accurate for all other entry points. The register subpath is a sideEffects: ["./dist/register.js"] entry.
2. Pluggable Serializer<T> interface
interface Serializer<T> { serialize(value: T): string; deserialize(raw: string): T }
Default: JSON. Alternatives: CborSerializer (via @msgpack/msgpack), StructuredCloneSerializer.
3. EvictionStrategy interface
interface EvictionStrategy { selectForEviction(entries: CacheEntry[]): CacheEntry[] }
class LRUEviction implements EvictionStrategy { ... }
class LFUEviction implements EvictionStrategy { ... }
class TTLFirstEviction implements EvictionStrategy { ... }
4. Cross-tab invalidation
LocalStorageCache and IndexedDBCache accept an optional broadcastChannel?: TypedBroadcastChannel — on write/delete/clear, publishes invalidation events so other tabs update their in-memory views.
5. Split oversized files
Each implementation splits into: store.ts (core CRUD), ttl.ts (expiry logic), quota.ts (quota guard), events.ts (storage-event wiring). All ≤300 lines.
6. Public introspection surface
cache.getMetrics(): CacheMetrics replaces private-property reach-through in tests.
7. Error codes
CACHE_QUOTA_EXCEEDED, CACHE_SERIALIZATION_FAILED, INDEXEDDB_NOT_AVAILABLE.
8. OTel metrics
Counter cache.hits_total{backend}, counter cache.misses_total{backend}, counter cache.evictions_total{strategy}, histograms cache.read_ms, cache.write_ms.
File layout
browser/storage/cache-browser/
package.json
tsconfig.json
vitest.config.ts
src/
index.ts
register.ts (explicit CacheFactory.register calls; sideEffects entry)
error-codes.ts
indexeddb/
store/ (store.ts, store.test.ts, store.md)
ttl/ (ttl.ts, ttl.test.ts, ttl.md)
quota/ (quota.ts, quota.test.ts, quota.md)
events/ (events.ts, events.test.ts, events.md)
indexeddb/ (indexeddb.ts — orchestrator, indexeddb.test.ts, indexeddb.md, indexeddb.types.ts)
localstorage/
store/
ttl/
quota/
events/
localstorage/ (localstorage.ts, localstorage.test.ts, localstorage.md, localstorage.types.ts)
sessionstorage/
store/
ttl/
sessionstorage/ (sessionstorage.ts, sessionstorage.test.ts, sessionstorage.md, sessionstorage.types.ts)
utils/
quota/ (getStorageQuota, isStorageNearQuota, estimateStorageSize, handleQuotaExceeded, isStorageAvailable)
serialization/ (defaultSerializer/Deserializer, isSerializable, safeDeserialize, Serializer interface)
storage-events/(StorageEventHandler, globalStorageEventHandler)
eviction/
lru-eviction/
lfu-eviction/
ttl-first-eviction/
types/
types/ (all shared types)
tests/
integration.test.ts
e2e/
cache-browser.spec.ts (Playwright: Chromium/WebKit/Firefox real storage)
Impact
Downstream: @suaveplan/analytics-browser, @suaveplan/pwa, @suaveplan/network-discovery-*, any package using browser caching.
Prerequisites: @suaveplan/cache, @suaveplan/events, @suaveplan/error, @suaveplan/broadcast-channel on Verdaccio.