Events & Subscriptions
React to data changes with events and subscriptions
Prisma IDB provides an event system to notify your app when data changes, enabling reactive UI updates and automatic synchronization.
Events
Only mutation operations emit events:
create- Record insertedupdate- Record modifieddelete- Record removed
Read operations (findMany, findUnique, count, etc.) do not emit events.
Each event contains:
- Event type:
create,update, ordelete - Record data: The changed record
- Operation context: Key path, old data (for updates), metadata
Events are emitted after the operation completes and the data is persisted to IndexedDB.
Event Lifecycle
Subscribe
Listen for changes on a specific model:
// Subscribe to all events on a model
client.user.subscribe(["create", "update", "delete"], (event) => {
console.log("Change detected:", event.detail);
});Subscribe returns an unsubscribe function:
const unsubscribe = client.user.subscribe(["create"], (event) => {
console.log("User created:", event.detail);
});
// Later, stop listening
unsubscribe();Selective Subscription
Listen to specific event types:
// Only create events
client.todo.subscribe(["create"], (event) => {
console.log("New todo:", event.detail);
});
// Only updates
client.todo.subscribe(["update"], (event) => {
console.log("Todo changed:", event.detail);
});
// Create and delete, but not update
client.user.subscribe(["create", "delete"], (event) => {
console.log("User lifecycle change:", event.detail);
});Event Handler Pattern
Use async handlers to respond to changes:
client.user.subscribe(["create", "update"], async (event) => {
const user = event.detail;
// Validate
if (!user.email) {
console.warn("Invalid user event");
return;
}
// Sync to external service
try {
await api.users.upsert(user);
} catch (error) {
console.error("Sync failed:", error);
}
});Silent Operations
Prevent events from firing with the silent option:
// This operation won't emit an event
await client.user.create(
{
data: { name: "Alice" },
},
{
silent: true, // Skip event emission
}
);Use cases:
- Bulk operations without notification overhead
- Loading initial data from server
- Avoiding infinite loops with bidirectional sync
Default Behavior
By default, all operations emit events (equivalent to silent: false).
Outbox Integration
When sync is enabled, mutations are automatically captured for server synchronization via the outbox pattern.
Controlling Outbox Capture
// Add to outbox (default with sync enabled)
await client.user.create({ data: { name: "Alice" } }, { addToOutbox: true });
// Skip outbox capture
await client.user.create({ data: { name: "Alice" } }, { addToOutbox: false });Silent + Outbox
Use both flags together to load data without triggering events or outbox capture:
// Load data from server without affecting local state notifications or sync
await client.user.create(
{ data: { name: "Alice" } },
{
silent: true,
addToOutbox: false, // Loading from server, don't re-sync
}
);Common Patterns
Real-Time UI Updates
Subscribe in a SvelteKit component:
<script lang="ts">
import { onMount, onDestroy } from "svelte";
import { PrismaIDBClient } from "$lib/prisma-idb";
let todos = $state<Todo[]>([]);
let client: PrismaIDBClient;
let unsubscribe = $state<() => void>();
onMount(async () => {
client = await PrismaIDBClient.createClient();
// Load initial data
todos = await client.todo.findMany();
// Subscribe to changes
unsubscribe = client.todo.subscribe(["create", "update", "delete"], async (event) => {
// Refresh list
todos = await client.todo.findMany();
});
});
onDestroy(() => {
unsubscribe?.();
});
</script>
{#each todos as todo (todo.id)}
<div>{todo.title}</div>
{/each}Centralized Event Hub
Create a custom event bus:
export class EventBus {
private handlers = new Map<string, Set<Function>>();
subscribe(event: string, handler: Function) {
if (!this.handlers.has(event)) {
this.handlers.set(event, new Set());
}
this.handlers.get(event)!.add(handler);
return () => {
this.handlers.get(event)!.delete(handler);
};
}
emit(event: string, data: any) {
this.handlers.get(event)?.forEach((handler) => handler(data));
}
}
// Bridge Prisma IDB events to your event bus
const bus = new EventBus();
client.user.subscribe(["create"], (event) => {
bus.emit("user:created", event.detail);
});
// Now other parts of your app can listen
bus.subscribe("user:created", (user) => {
console.log("User created globally:", user);
});Type Safety
Event details are fully typed based on your Prisma schema:
client.user.subscribe(["create"], (event) => {
// event.detail has type User
const user = event.detail;
// Full TypeScript support
console.log(user.email); // ✅ Valid
console.log(user.unknown); // ❌ TypeScript error
});