Local persistence
A pulse lives in memory. A local connector stores its history on the device, so documents open without a network, offline edits survive reloads and restarts, and everything uploads once a remote connector is reachable again.
const note = await loadPulse(Note, id, {
connectors: [local, remote], // local first: ready from disk, then synced with the server
});
- Local first. Local connectors are opened before remote ones and
readyresolves from disk. - Everything is stored. Local edits and rows received from the server are written, acknowledged only after the transaction commits.
- Same rules as the server. Each store keeps immutable operation rows with the semantics of the PostgreSQL and SQLite stores: duplicates are idempotent, conflicts roll back, rows wait for their dependencies.
- Self-healing. A local store that fails (blocked storage, closed database, restarted main process) is retried like any connector, with a full resynchronization.
Pick a store
| Package | Runtime | Storage | Several windows |
|---|---|---|---|
pulsify-indexeddb | Browsers, PWAs | IndexedDB, one database per name | Tabs updated through a BroadcastChannel |
pulsify-electron | Electron | SQLite (better-sqlite3) in the main process | Every window updated by the main process |
pulsify-tauri | Tauri 2 | SQLite (rusqlite) in the Rust core | Every webview updated by the Rust core |
Browsers: IndexedDB
// bun add pulsify-indexeddb
import { createWebSocketConnector, loadPulse } from "pulsify-core";
import { createIndexedDbConnector } from "pulsify-indexeddb";
const local = createIndexedDbConnector({ name: `my-app:${user.id}` }); // one database per account
const remote = createWebSocketConnector("wss://example.com/sync?doc={docId}");
const note = await loadPulse(Note, noteId, { connectors: [local, remote] });
// On logout: stop the pulses, then wipe and close.
destroyPulse(note);
await local.clear();
local.close(); // pulses using it stop instead of reconnecting
A blocked or full storage fails with CONNECTION_FAILED or LIMIT_EXCEEDED; the document keeps working from memory and the remote meanwhile.
Electron: SQLite in the main process
better-sqlite3 runs in the main process; windows talk to it over IPC with binary frames, through a bridge that exposes nothing else.
// main.ts (bun add pulsify-electron better-sqlite3, then rebuild it for Electron)
import { app, ipcMain } from "electron";
import { join } from "node:path";
import { servePulsifyStore } from "pulsify-electron/main";
const store = servePulsifyStore({
ipcMain,
database: join(app.getPath("userData"), "pulsify.sqlite"), // WAL, one file for every window
});
app.on("will-quit", () => void store.close());
// preload.ts
import { contextBridge, ipcRenderer } from "electron";
import { exposePulsifyBridge } from "pulsify-electron";
exposePulsifyBridge({ contextBridge, ipcRenderer }); // window.pulsify, frames only
The SQLite file uses the same schema as SqlitePatchStore, so any Pulsify SQLite tool can read it.
Tauri: SQLite in the Rust core
A Tauri 2 plugin stores documents with rusqlite and streams frames to each webview as raw bytes over a Tauri channel. The file format is the same as SqlitePatchStore: TypeScript and Rust read each other's databases.
# src-tauri/Cargo.toml
[dependencies]
tauri-plugin-pulsify = "6"
// src-tauri/src/lib.rs
tauri::Builder::default()
.plugin(tauri_plugin_pulsify::init()) // pulsify.sqlite in the app data directory
.run(tauri::generate_context!())
.expect("error while running tauri application");
// src-tauri/capabilities/default.json
{ "identifier": "default", "windows": ["main"], "permissions": ["core:default", "pulsify:default"] }
The database opens on first use: if it cannot be opened, the app still starts and connected pulses retry. Without the pulsify:default permission, loading fails with FORBIDDEN.
With Pulsify Cloud
Local connectors combine with any remote, including the cloud: the notebook opens offline, and the cloud socket catches up in the background.
import { createCloudConnector } from "pulsify-cloud";
import { createIndexedDbConnector } from "pulsify-indexeddb";
const local = createIndexedDbConnector({ name: `notes:${user.id}` });
const cloud = createCloudConnector({ url: PULSIFY_URL, token: fetchToken });
const notebook = await loadPulse(Notebook, user.id, {
connectors: [local, cloud],
include: true,
});