Files
skills/docs/persistence/cache/cache-storage/src/codec/json.md
2026-05-29 08:20:56 +01:00

3.5 KiB

JsonCodec

Purpose

JsonCodec<T> 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

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

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<UserSummary>({
  storage: new MemoryStorage(),
  codec: new JsonCodec<UserSummary>(),
  defaultTtl: 5 * 60_000,
});

async function loadUser(id: string): Promise<UserSummary> {
  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<T>() — 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<T> 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.