mono/packages/ui/docs/cache.md
2026-03-21 20:18:25 +01:00

61 lines
4.0 KiB
Markdown

# Polymech Cache Invalidation & Mirroring
This document outlines the architecture for caching data on the Polymech platform, specifically how mutations invalidate Server/Client state and mirror those invalidations to secondary servers (e.g. from local to production instances).
## Core Mechanisms
Cache logic splits into:
1. **Server-Side Cache** (`redis` or `memory` via `getCache()`): Used in API routes to hold heavy queries and `pages`.
2. **Client-Side Cache** (`@tanstack/react-query`): The frontend data store holding currently loaded application state.
3. **Server-Sent Events (SSE)**: Event streams connecting the frontend clients to the backend to get real-time cache mutations.
4. **Cache Mirroring**: HTTP Broadcasts mapping local actions outwards to production domains using Service Accounts.
## Flow of a Cache Invalidation
When a user modifies data (e.g. Updating a Post) locally:
1. **Mutation**: A request is sent to an API endpoint (e.g. `PATCH /api/posts/:id`).
2. **Local Invalidation**:
- The handler calls `appCache.invalidate('post')` inside `[server/src/cache.ts](../server/src/cache.ts)`.
- The Cache clears the specific entities and walks the dependency tree (e.g., `post` depends on `pictures` and `categories`).
3. **SSE Broadcast**:
- The handler issues `appCache.notify('post', id, 'update')`.
- The backend pushes an SSE `app-update` down the wire.
4. **Client-Side Reset**:
- The React frontend listens to the stream in `[src/components/StreamInvalidator.tsx](../src/components/StreamInvalidator.tsx)` via `[src/contexts/StreamContext.tsx](../src/contexts/StreamContext.tsx)`.
- Depending on the entity, `StreamInvalidator` resets the specific `post` ID in React Query, and forcefully refetches the overarching `posts`, `pages`, and `feed` caches.
5. **Mirror Broadcast**:
- During `appCache.invalidate()`, if mirroring isn't explicitly disabled, `[server/src/mirror.ts](../server/src/mirror.ts)` takes the `type` and broadcasts it to the domains listed in `.env` under `CACHE_MIRRORS`.
- It acquires a JWT token utilizing `SERVICE_EMAIL` and `SERVICE_PASSWORD` (or `TEST_EMAIL`).
- A `POST` goes out to `https://service.polymech.info/api/cache/invalidate` including `mirror: false` in the payload (to prevent an infinite broadcast loop).
6. **Remote Revalidation**:
- The remote (e.g., Production) instance receives the `invalidate` request at `[server/src/products/serving/index.ts](../server/src/products/serving/index.ts)`.
- It internally invalidates its own Redis/Memory cache utilizing `appCache.invalidate(type, true /* preventMirror */)`.
- It issues an SSE to all *its* connected clients `appCache.notify(type, null, 'update')`, which triggers Step 4 for public visitors!
## Dependency Graph Configuration
In `[server/src/cache.ts](../server/src/cache.ts)`, dependencies orchestrate cascade invalidation:
```typescript
private static DEPENDENCIES: Record<string, string[]> = {
'posts': ['categories', 'pictures'], // Changing a picture/category invalidates posts
'pages': ['categories', 'pictures', 'translations'],
'categories': ['types'],
'translations': [],
'feed': ['posts', 'pages', 'categories'],
'auth': []
};
```
## Adding a New Mirrored Cache Endpoint
1. If it represents a new domain model, add it to `DEPENDENCIES` in `AppCache`.
2. Add its alias/hook inside `[src/components/StreamInvalidator.tsx](../src/components/StreamInvalidator.tsx)`.
Note: The mirror sends pluralized definitions (`pages`, `types`, `posts`) whereas standard CRUD routes may notify singularly (`page`, `type`, `post`). Map both aliases to the same React Query keys.
3. Utilize `appCache.invalidate('entityName')` inside your API logic. Mirroring happens automatically!
## Debugging
Mirroring successes and errors are not logged to `stdout`. All `mirror.ts` and `cache.ts` activities are strictly written to:
`[server/logs/cache.json](../server/logs/cache.json)` using the internal pino `cacheLogger`.