bind-tanstack-query
TanStack Query invalidation binding for RippleDB
@rippledb/bind-tanstack-query
Wire RippleDB DbEvents to TanStack Query cache invalidation. Works with any TanStack Query adapter (React, Vue, Solid, Svelte).
This package is used internally by @rippledb/client-query to provide a
complete data fetching and caching solution. Consider using
client-query for a more integrated experience.
Installation
pnpm add @rippledb/bind-tanstack-query @tanstack/query-corenpm install @rippledb/bind-tanstack-query @tanstack/query-coreyarn add @rippledb/bind-tanstack-query @tanstack/query-coreIf you're using a framework-specific TanStack Query (e.g.
@tanstack/react-query), you already have @tanstack/query-core as a
dependency.
Basic Usage
import {
wireTanstackInvalidation,
defineListRegistry,
} from "@rippledb/bind-tanstack-query";
import { QueryClient } from "@tanstack/query-core";
// 1. Define which query keys depend on which entities
const registry = defineListRegistry()
.list(["todos"], { deps: ["todos"] })
.list(["todoWithTags"], { deps: ["todos", "tags"] })
.list(["dashboard"], { deps: ["todos", "users", "projects"] });
// 2. Wire it up
const cleanup = wireTanstackInvalidation({
queryClient,
store, // Your RippleDB Store (e.g. MemoryStore)
registry,
debounceMs: 50, // Coalesce rapid-fire events (default: 50ms)
});
// 3. Later: cleanup() to unsubscribeHow It Works
When your Store applies changes (via sync or local writes), it emits DbEvents:
type DbEvent = {
entity: string; // e.g. 'todos'
kind: "insert" | "update" | "delete";
id?: string; // e.g. 'todo-1'
};wireTanstackInvalidation listens to these events and:
- Row queries: Invalidates
[entity, id](e.g.['todos', 'todo-1']) - Entity queries: Invalidates
[entity](e.g.['todos']) - List queries: Invalidates any query key in the registry whose
depsinclude the event's entity
List Registry
The registry defines which query keys depend on which entities:
const registry = defineListRegistry()
// Simple: ['todos'] depends on todos entity
.list(["todos"], { deps: ["todos"] })
// Complex query key with params
.list(["todos", { status: "active" }], { deps: ["todos"] })
// Multiple dependencies: dashboard needs data from 3 entities
.list(["dashboard"], { deps: ["todos", "users", "projects"] });When a DbEvent fires for todos, all query keys whose deps include 'todos' are invalidated.
Configuration
queryClient
Your TanStack QueryClient instance.
store / onEvent
Either provide a Store with onEvent(cb), or pass onEvent directly:
// Option 1: Pass the store
wireTanstackInvalidation({ queryClient, store, registry });
// Option 2: Pass onEvent directly (useful for testing)
wireTanstackInvalidation({
queryClient,
onEvent: (cb) => store.onEvent(cb),
registry,
});registry
The ListRegistry mapping query keys to entity dependencies. Optional if you only need entity/row-level invalidation.
debounceMs
Debounce time in milliseconds to coalesce rapid-fire invalidations. Default: 50.
Set to 0 to disable debouncing (invalidate immediately on each event).
wireTanstackInvalidation({
queryClient,
store,
debounceMs: 0, // No debounce
});invalidateRows
Whether to invalidate row queries ([entity, id]). Default: true.
wireTanstackInvalidation({
queryClient,
store,
invalidateRows: false, // Only invalidate entity/list queries
});React Example
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { useEffect } from "react";
import {
wireTanstackInvalidation,
defineListRegistry,
} from "@rippledb/bind-tanstack-query";
import { store } from "./ripple"; // Your RippleDB store
const registry = defineListRegistry().list(["todos"], { deps: ["todos"] });
function App() {
const queryClient = useQueryClient();
// Wire up invalidation on mount
useEffect(() => {
return wireTanstackInvalidation({
queryClient,
store,
registry,
});
}, [queryClient]);
return <TodoList />;
}
function TodoList() {
// This query will be invalidated when todos change
const { data: todos } = useQuery({
queryKey: ["todos"],
queryFn: () => store.listRows({ entity: "todos" }),
});
return (
<ul>
{todos?.map((todo) => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
);
}Cleanup
wireTanstackInvalidation returns a cleanup function. Call it to unsubscribe:
const cleanup = wireTanstackInvalidation({ ... });
// Later, when unmounting or cleaning up:
cleanup();In React, return it from useEffect for automatic cleanup.