# ADR 0056 — Entity cache shapes are declared

> Source: https://github.com/max-listov/stitchkit/blob/master/docs/decisions/0056-entity-cache-shapes-are-declared.md

---

# ADR 0056 — Entity cache shapes are declared

- **Status:** Accepted — extends the optional cache bridge without turning it
  into a domain cache framework
- **Date:** 2026-08-07

## Context

The original entity helper understood only `Paginated<T>` and
`InfiniteData<Paginated<T>>`, assumed list rows were full entities, used one
static list-key prefix and prepended every create. Real query caches also use
plain arrays and array-valued infinite pages. Scoped keys, projected list rows
and backend ordering therefore required a second hand-written CRUD updater.

Those differences are application policy. Inferring a workspace from a key,
deriving sort order from fields or rewriting totals would make the helper
domain-aware and silently corrupt cache metadata.

## Decision

Keep one `createEntityCacheHandlers<TData, TListItem>` API. Its list config
declares one of four shapes: array, paginated, infinite array pages or infinite
paginated pages. A built-in discriminant is sufficient; a separate public list
adapter would expose more machinery without expressing another behavior.

The config explicitly provides full-entity identity, list-item identity and
projection. Static keys remain the short path. Dynamic list/detail keys receive
one discriminated created/updated/deleted event, including the canonical id and
the original entity or deleted payload.

`createAt` selects the insertion edge/page. `updateMissing` explicitly chooses
skip or insert. An optional comparator is application-owned and applied after
create/update; Stitchkit never guesses backend ordering.

All operations preserve outer objects, `nextCursor`, page-specific metadata
and `pageParams`. Infinite insertion changes only the selected edge page.
Create deduplication and update/delete matching inspect every cached page.
Runtime shape checks leave other query values under an intentionally partial
list-key prefix untouched.

## Alternatives rejected

- **A generic read/write list adapter callback.** It exposes TanStack internals
  in every consumer config and becomes callback soup for four fixed shapes.
- **Keep the old helper beside a new one.** Two CRUD semantics would drift and
  make migrations ambiguous.
- **Infer shape from cached values.** Empty/missing caches are ambiguous and a
  malformed neighbouring query under a prefix would be patched incorrectly.
- **Update totals or cursor metadata.** Their meaning is endpoint-specific and
  cannot be derived from a socket event safely.

## Consequences

- One small declaration covers the representative real cache shapes.
- Projection and sorting remain typed and owned by the application.
- The config is intentionally more explicit; this is a pre-1.0 breaking change
  with one clean path and no compatibility shim.
- Arbitrary cache transformations continue to use TanStack Query directly.
