feat: hash-based metadata staleness detection (#18649)
## Summary Replace the single `metadataVersion` integer with per-entity-type **collection hashes** for granular metadata staleness detection. The backend already generates a UUID per flat entity map on each cache recompute (`crypto.randomUUID()` in `WorkspaceCacheService`); we now expose these via the minimal metadata endpoint and SSE events so the frontend can compare and know exactly which entity types are stale. ### Key changes **Backend:** - `WorkspaceCacheService.getCacheHashes()` — new public method that reads only `:hash` keys from Redis without fetching full data - `MinimalMetadataDTO` — added `collectionHashes: Record<string, string>` (JSON scalar mapping `AllMetadataName` → collection hash), removed `metadataVersion` - `MetadataEventDTO` — added optional `updatedCollectionHash` field to SSE events - `MetadataEventsToDbListener` — reads the collection hash for the affected entity type after cache invalidation and attaches it to the SSE event before publishing - `MinimalMetadataService` — no longer queries the workspace table; uses `getCacheHashes()` for all flat entity maps and maps cache keys to `AllMetadataName` locally **Frontend:** - `metadataCollectionHashesState` — new Jotai atom with `atomWithStorage` + `getOnInit: true` storing `Partial<Record<MetadataEntityKey, string>>` - `mapAllMetadataNameToEntityKey()` — explicit mapping from backend `AllMetadataName` to frontend `MetadataEntityKey` (23 entries) - `useLoadMinimalMetadata` — stores `collectionHashes` from server, computes `staleEntityKeys` by comparing local vs server hashes - `patchMetadataStoreFromSSEEvent()` — accepts optional `updatedCollectionHash` and updates `metadataCollectionHashesState` - All 11 SSE effect components — pass `eventDetail.updatedCollectionHash` through to the patch function - `useStaleMetadataEntities` — new hook returning entity keys missing from collection hashes (not yet loaded/synced) - `resetMetadataStore()` — also clears collection hashes - Deleted `metadataVersionState` (superseded by collection hashes) ### Design decisions - **No change to hash generation** — existing `crypto.randomUUID()` is sufficient. Hashes are persisted in Redis, survive server restarts, and change only on `invalidateAndRecompute`. - **"Collection hash" naming** — used consistently to clarify the hash represents an entire entity collection (e.g., all views), not a single record. - **Mapping localized** — backend `WorkspaceCacheKeyName` → `AllMetadataName` mapping lives in the minimal metadata service. Frontend `AllMetadataName` → `MetadataEntityKey` mapping lives in a local utility. Nothing in `twenty-shared`. - **Backward compatible** — `collectionHashes` is additive; `updatedCollectionHash` is nullable.
This commit is contained in:
+32
@@ -0,0 +1,32 @@
|
||||
import { type MetadataEntityKey } from '@/metadata-store/states/metadataStoreState';
|
||||
|
||||
const METADATA_NAME_TO_ENTITY_KEY: Record<string, MetadataEntityKey> = {
|
||||
objectMetadata: 'objectMetadataItems',
|
||||
fieldMetadata: 'fieldMetadataItems',
|
||||
index: 'indexMetadataItems',
|
||||
view: 'views',
|
||||
viewField: 'viewFields',
|
||||
viewFieldGroup: 'viewFieldGroups',
|
||||
viewGroup: 'viewGroups',
|
||||
viewSort: 'viewSorts',
|
||||
viewFilter: 'viewFilters',
|
||||
viewFilterGroup: 'viewFilterGroups',
|
||||
rowLevelPermissionPredicate: 'rowLevelPermissionPredicates',
|
||||
rowLevelPermissionPredicateGroup: 'rowLevelPermissionPredicateGroups',
|
||||
logicFunction: 'logicFunctions',
|
||||
role: 'roles',
|
||||
roleTarget: 'roleTargets',
|
||||
agent: 'agents',
|
||||
skill: 'skills',
|
||||
pageLayout: 'pageLayouts',
|
||||
pageLayoutWidget: 'pageLayoutWidgets',
|
||||
pageLayoutTab: 'pageLayoutTabs',
|
||||
commandMenuItem: 'commandMenuItems',
|
||||
navigationMenuItem: 'navigationMenuItems',
|
||||
frontComponent: 'frontComponents',
|
||||
webhook: 'webhooks',
|
||||
};
|
||||
|
||||
export const mapAllMetadataNameToEntityKey = (
|
||||
metadataName: string,
|
||||
): MetadataEntityKey | undefined => METADATA_NAME_TO_ENTITY_KEY[metadataName];
|
||||
Reference in New Issue
Block a user