Raven ScottandCursor 1c33a21602 Upsert user-prefs rows by userId primary key.
Prevent duplicate @pearcord/user-prefs documents on insert so partial
writes can be repaired in place during startup recovery.

Co-authored-by: Cursor <[email protected]>
2026-06-02 11:43:50 -04:00

pearcord-db

Local persistence for Pearcord: HyperDB (RocksDB) when the schema is built, with automatic fallback to a JSON file store.

Mission

Offer one LocalDatabase class that serializes all reads/writes, picks the best engine at runtime, and exposes a minimal insert / get / find / delete API aligned with @pearcord/* collections from pearcord-shared.

When to use / not

Use when:

  • Any module needs durable guild, user, message, or invite rows on disk.
  • You want engine-agnostic storage (hyperdb vs json) without branching in callers.

Do not use when:

  • You only need in-memory structures or P2P gossip without local materialization.
  • You need Autobase multi-writer replication — planned in pearcord-sync (not wired yet).

Public API

Export Role
LocalDatabase Main facade (storagePathdb/ directory)
LocalDatabase#ready() Opens HyperDB or JsonStore backend
LocalDatabase#insert(collection, record) Upsert by collection primary key
LocalDatabase#get(collection, query) Single-row lookup
LocalDatabase#find(collection, query, opts?) Query; supports { limit }; normalizes async iterators
LocalDatabase#delete(collection, query) Remove matching row
LocalDatabase#close() Close backend
LocalDatabase#getEngine() 'hyperdb' or 'json'
hasHyperDB true if hyperdb + generated spec/hyperdb loaded
jsonStoreHasUsers(storagePath) Migration probe: legacy JSON has users
JsonStore Subpath export pearcord-db/store-json — standalone JSON backend

LocalDatabase uses an internal promise chain (_serialize) so concurrent mutations do not interleave.

P2P surface

None. This module is local-only. Peers replicate via gossip into the same collections on other devices; pearcord-guild writes inbound RPC payloads through platform handlers that call db.insert.

Storage

Engine Path When
HyperDB (Rocks) {storagePath}/ (RocksDB files via HyperDB.rocks) hyperdb installed and spec/hyperdb/index.js exists after npm run build:schemaprimary engine (P0-3 complete)
JSON {storagePath}/pearcord-db.json Fallback if HyperDB unavailable, or HyperDB empty while JSON already has users

Platform default path: {PEARCORD_STORAGE or ~/.config/pearcord}/db.

DM metadata uses a separate JsonStore at {storagePath}/dm-meta (platform-only).

Platform integration

const { LocalDatabase } = require('pearcord-db')
// PearcordPlatform constructor:
this.dbPath = path.join(this.storagePath, 'db')
this.db = new LocalDatabase(this.dbPath)

Shared by pearcord-identity, pearcord-guild, pearcord-message, pearcord-invite, and direct platform queries (listGuilds, search, audit). Platform checks this.db.getEngine() for HyperDB-optimized message scans.

UI / IPC

No direct UI dependency. All IPC flows go through pearcord-platform, which owns the single LocalDatabase instance per session.

Tests

From apps/pearcord:

  • npm run test:hyperdbsmoke-hyperdb.cjs
  • npm run test:schema-v9smoke-schema-v9.cjs

Both use LocalDatabase and hasHyperDB from this package.

Code example

const path = require('bare-path')
const { LocalDatabase } = require('pearcord-db')
const { COLLECTIONS, id, now } = require('pearcord-shared')

const db = new LocalDatabase(path.join('/tmp/pearcord-demo', 'db'))
await db.ready()
console.log('engine:', db.getEngine())

await db.insert(COLLECTIONS.USERS, {
  id: id(),
  username: 'alice',
  createdAt: now()
})

const users = await db.find(COLLECTIONS.USERS, {})
await db.close()

Build HyperDB schema:

cd modules/pearcord-db && npm install && npm run build:schema

Repository

Part of Pearcord.

  • Org: pearcord
  • Clone: git clone https://git.ssh.surf/pearcord/pearcord-db.git
  • Install: npm install git+https://git.ssh.surf/pearcord/pearcord-db.git#main
S
Description
Pearcord module: pearcord-db
Readme
46 MiB
Languages
JavaScript 100%