Real-Time Normalized Cache with TanStack Query
TheuseNormalizedQuery hook provides instant, event-driven cache updates with server-side filtering. Built with 2025 best practices including runtime validation (Valibot), type-safe merging (ts-deepmerge), and proper subscription cleanup.
Overview
useNormalizedQuery wraps TanStack Query to add real-time capabilities:
- Instant updates across all devices via WebSocket events
- Server-side filtering reduces network traffic by 90%+
- Client-side safety ensures correctness even with unrelated events
- Proper cleanup prevents connection leaks
- Runtime validation catches malformed events
- Type-safe merging preserves data integrity
Architecture
Basic Usage
Directory Listing
- Initial query fetches directory listing
- Hook subscribes to file events for this path (exact mode)
- When files are created/updated, events arrive instantly
- Cache updates atomically
- UI re-renders with new data
Media View (Recursive)
Global Resources
Single Resource Queries
API Reference
Options
Path Filtering Modes
Exact Mode (Default)
Only events for files directly in the specified directory:- File in
/Photos/image.jpg→ ✓ Included - File in
/Photos/Vacation/beach.jpg→ ✗ Excluded - Directory
/Photos/Vacation→ ✗ Excluded
Recursive Mode
All events for files anywhere under the specified directory:- File in
/Photos/image.jpg→ ✓ Included - File in
/Photos/Vacation/beach.jpg→ ✓ Included - File in
/Photos/Vacation/Cruise/pic.jpg→ ✓ Included
Server-Side Filtering
How It Works
Each hook creates a filtered subscription on the backend:- ✓
resource_typematches? - ✓
library_idmatches? - ✓
path_scopematches? (withinclude_descendantsmode) - ✓
resourceIdmatches? (if specified)
Filter Logic
Exact Mode:Client-Side Safety Filtering
Even with server-side filtering, the client applies a safety filter to batch events:Event Types
ResourceChanged (Single)
ResourceChangedBatch (Multiple)
ResourceDeleted
Refresh (Invalidate All)
queryClient.invalidateQueries() to refetch all data.
Deep Merge Behavior
Usests-deepmerge for type-safe, configurable merging:
No-Merge Fields
Some fields should be replaced entirely, not merged:Runtime Validation
All events are validated with Valibot before processing:Subscription Multiplexing
Multiple hooks with identical filters automatically share a single backend subscription:- First hook creates subscription with filter
{resource_type: "location", library_id: "abc"} - Subscription manager generates key from filter:
{"resource_type":"location","library_id":"abc"} - Second hook with same filter reuses existing subscription
- Events broadcast to all listeners
- When both unmount, subscription cleaned up automatically
- Eliminates duplicate subscriptions during render cycles
- Reduces backend load (fewer Unix socket connections)
- Faster subscription setup (reuses existing connection)
- Automatic reference counting prevents premature cleanup
Subscription Cleanup
Subscriptions are properly cleaned up when components unmount:- React calls cleanup function
- Frontend stops listening to events
- Tauri sends
Unsubscriberequest to daemon - Daemon closes subscription
- Unix socket connection closed
Performance
Event Reduction
Connection Management
- Multiplexing: Multiple hooks with identical filters share one backend subscription
- Reference counting: Subscriptions cleaned up when last hook unmounts
- Deduplication: Eliminates duplicate subscriptions during render cycles
- Monitoring: Check
client.getSubscriptionStats()for active subscriptions
Testing
Test Coverage
Rust (Backend):- 9/9 event filtering tests passing
- Validates exact vs recursive modes
- Tests all path types (Physical, Content, Cloud, Sidecar)
- 5/5 integration tests passing
- Uses real backend event fixtures
- Validates filtering and cache updates
- Proves correctness with actual production code
Run Tests
Best Practices
Always Scope File Queries
Use Correct Mode for View Type
Combine with TanStack Query Options
Advanced Usage
Content-Addressed Files
Files use Content-basedsd_path but have Physical paths in alternate_paths:
Multiple Instances
Multiple files with same content have different IDs:Debugging
Enable Logging
Monitor Subscriptions
Inspect Cache
Migration
From useLibraryQuery
Backward Compatibility
The olduseNormalizedCache name is aliased:
Technical Details
Exported Functions
Core logic is exported for testing:Runtime Dependencies
- ts-deepmerge - Type-safe deep merging
- valibot - Runtime event validation
- tiny-invariant - Assertion helpers
- type-fest - TypeScript utilities
- @tanstack/react-query - Core caching
Subscription Lifecycle
Common Patterns
List with Real-Time Updates
Directory with Instant File Appearance
Inspector with Sidecar Updates
Summary
useNormalizedQuery provides production-grade real-time caching:
- Server-side filtering (90%+ event reduction)
- Client-side safety (validates and filters)
- Proper cleanup (no connection leaks)
- Runtime validation (catches bad events)
- Type-safe merging (preserves data)
- Comprehensive tests (9 Rust + 5 TypeScript)
- TanStack Query compatible (all features work)
- Cross-device sync (instant updates everywhere)
