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:
DatabaseAdapterinterfaceExtendedNoteRepository(the query port the app actually uses)Migrationtypes and runnerDataPaths- Backup / export / import
@dripnex/storage-sqlite
better-sqlite3 adapter:
SQLiteNoteRepositoryimplementsExtendedNoteRepository- 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
- Every write goes to SQLite first.
- Triggers mark
needs_syncon content changes. SyncServiceencrypts and pushes blobs toapi.dripnex.app.- 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,
});