first commit
This commit is contained in:
79
docs/persistence/cache/cache-storage/src/codec/json.md
vendored
Normal file
79
docs/persistence/cache/cache-storage/src/codec/json.md
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
# 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
|
||||
|
||||
```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<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.
|
||||
Reference in New Issue
Block a user