# JsonCodec ## Purpose `JsonCodec` is the default codec for `StorageCache`. It serialises arbitrary JSON-compatible values to UTF-8 bytes via `JSON.stringify` + `TextEncoder.encode`, and parses bytes back through `TextDecoder.decode` (in fatal mode) + `JSON.parse`. Use it for the typical cache payload — profile objects, configuration documents, API responses, denormalised projections — anywhere the value is already JSON-safe. ## Features - Round-trips every JSON-safe TypeScript value: strings, numbers, booleans, `null`, plain objects, arrays, and nested combinations of these. - Full multi-byte UTF-8 support — emoji, CJK, RTL scripts, mathematical operators all survive a round-trip. - Fatal-mode UTF-8 decode — invalid leading sequences fail loud instead of silently producing replacement characters, so corruption surfaces immediately. - Decode failures (malformed UTF-8 OR malformed JSON) are wrapped in `StorageCacheError("CACHE_STORAGE.CODEC_ERROR")` with the original throw on `cause`, so callers see a single structured failure shape. - Stateless — share one instance across many caches; both `TextEncoder` and `TextDecoder` are module-level singletons. ## Usage: Basic Example ```ts import { JsonCodec } from "@suaveplan/cache-storage/codec/json"; const codec = new JsonCodec<{ user: string; roles: string[] }>(); const bytes = codec.encode({ user: "alice", roles: ["admin"] }); const value = codec.decode(bytes); ``` ## Usage: Advanced Example ```ts import { JsonCodec } from "@suaveplan/cache-storage/codec/json"; import { StorageCache } from "@suaveplan/cache-storage"; import { MemoryStorage } from "@suaveplan/storage/memory"; import { CACHE_STORAGE_CODES, StorageCacheError, } from "@suaveplan/cache-storage/errors"; interface UserSummary { id: string; email: string; preferredLocale: string; } const cache = new StorageCache({ storage: new MemoryStorage(), codec: new JsonCodec(), defaultTtl: 5 * 60_000, }); async function loadUser(id: string): Promise { try { return await cache.wrap(`user:${id}`, () => fetchUser(id)); } catch (err) { if ( err instanceof StorageCacheError && err.code === CACHE_STORAGE_CODES.CODEC_ERROR ) { log.warn("Corrupted cache entry — refetching", { id, cause: err.cause }); await cache.delete(`user:${id}`); return fetchUser(id); } throw err; } } ``` ## API Reference | Member | Signature | Description | | --- | --- | --- | | `new JsonCodec()` | — | Constructs a stateless codec for `T`. | | `encode(value)` | `(T) → Uint8Array` | `TextEncoder.encode(JSON.stringify(value))`. | | `decode(bytes)` | `(Uint8Array) → T` | `JSON.parse(TextDecoder.decode(bytes))`. Throws `StorageCacheError("CACHE_STORAGE.CODEC_ERROR")` on malformed input. | Conforms to `Codec` from `@suaveplan/cache-storage/codec`. ## Implementation Notes `TextDecoder` is constructed once at module load with `{ fatal: true }`. The fatal flag is critical — without it, invalid bytes would be replaced with `U+FFFD` and the cache would silently return decoded garbage. The split try/catch separates the two failure modes (`utf8` vs `json`) in the wrapped error's `messageKey` so observability pipelines can disambiguate them. `encode` failures from `JSON.stringify` (circular references, `BigInt`, `Symbol`) propagate as-is — they originate in the application's data, not the cache, and `StorageCache.set` adapts them into `CACHE_STORAGE.CODEC_ERROR` at the cache boundary so the public surface stays uniform.