dripnex.docs
Architecture

Storage

SQLite locally, optional E2E sync, migrations, backup

Storage

Storage is split so native dependencies stay in one package.

Packages

@dripnex/storage-core

No native dependencies:

  • DatabaseAdapter interface
  • ExtendedNoteRepository (the query port the app actually uses)
  • Migration types and runner
  • DataPaths
  • Backup / export / import

@dripnex/storage-sqlite

better-sqlite3 adapter:

  • SQLiteNoteRepository implements ExtendedNoteRepository
  • Notebooks, chunks, embeddings
  • Forward-only migrations (currently through 019_embeddings)
  • Only package with native deps

@dripnex/sync-core

Not a sync engine. Shared TypeScript contracts and validateNotebookTree.

Live sync is apps/desktop/src/main/services/sync/SyncService.ts. It talks to packages/api (Cloudflare). The server stores ciphertext.

Why Split?

better-sqlite3 must be rebuilt per Electron ABI. Isolating it lets storage-core and core tests run in plain Node.

The note port

Core's NoteRepository is the write port used by domain operations:

interface NoteRepository {
  get(id: NoteId): Promise<Note | null>;
  save(note: Note): Promise<void>;
  delete(id: NoteId): Promise<void>;
}

The desktop (and anything that lists, searches, or draws a graph) depends on ExtendedNoteRepository in storage-core: list/search/count, tags, backlinks, graph. Sync bookkeeping stays on the SQLite class.

Sync

  1. Every write goes to SQLite first.
  2. Triggers mark needs_sync on content changes.
  3. SyncService encrypts and pushes blobs to api.dripnex.app.
  4. Pull decrypts locally. Conflicts are last-write-wins with a local CEK.

There is no Supabase in this stack.

Backup

createBackup({
  backupDir: paths.backups,
  databasePath: paths.database,
});

On this page