Inertia.
The Architecture: Local-First Sync
The flow shifts from a direct push to a mediated sync:
Rails/ActionCable → DexieCable → IndexedDB (Dexie.js) → Reactive UI (LiveQuery).
This approach removes the need for sync boilerplate. Your components don't need to know about WebSockets; they just watch the local table, and the UI updates instantly because the data is already on the client.
Practical Implementation Guide
Here is a step-by-step look at integrating this into a Rails and Svelte stack.
1. Client-Side Database Config
First, set up your Dexie schema and link it to DexieCable.
// db.js
import Dexie from "dexie";
import DexieCable from "dexiecable";
export const db = new Dexie("MyAppDB");
db.version(1).stores({
todos: "id, title, completed, updated_at"
});
DexieCable.db = db;
DexieCable.subscribe("UserChannel");2. Backend Integration
In your Rails app, use the DexieCable integration within your ActionCable channel and leverage ActiveRecord macros for automatic broadcasting.
# app/channels/user_channel.rb
class UserChannel < ApplicationCable::Channel
include DexieCable::Channel
end
# app/models/todo.rb
class Todo < ApplicationRecord
include DexieCable::Model
syncs_to_dexie :todos
end3. Reactive UI Consumption
In Svelte, use
liveQuery to bind the UI to the local database. This ensures the view stays in sync regardless of how the data entered the store.<script>
import { db } from './db';
import { liveQuery } from 'dexie';
// Observe the local IndexedDB table reactively
let todos = liveQuery(() => db.todos.toArray());
</script>
<h2>Real-Time Todos</h2>
{#if $todos}
<ul>
{#each $todos as todo (todo.id)}
<li>{todo.title}</li>
{/each}
</ul>
{/if}Beyond Basic Sync
One of the most powerful aspects of this AI workflow for developers is the ability to chain arbitrary Dexie operations from the backend. You aren't limited to simple model broadcasts; you can trigger specific database actions from controllers or background jobs:
# Single item insert
UserChannel[current_user].table("todos").add(id: 1, title: "Buy milk")
# Modify matching records
UserChannel[current_user].table("todos").where("completed = 0").modify({ status: "pending" })This setup effectively turns the browser's IndexedDB into a real-time mirror of your server state, eliminating the latency and complexity of traditional WebSocket-to-UI pipelines.