decentralised-db
v0.7.1
Published
Offline-first, P2P database with zero-knowledge encryption
Maintainers
Readme
DecentralisedDB
DecentralisedDB is a local-first encrypted database with optional cloud sync, realtime collaboration, and integrated AI routing. The core product boundary is simple:
- private data stays local unless the app explicitly enables cloud sync
- identities are based on a passphrase and a recovery phrase
- collaboration and cloud features are opt-in
- AI is attached as a provider layer, not the storage model
Production development currently uses this private source-of-truth; public releases are generated through the allowlisted open-source export so production ops, secrets, evidence, and Git history stay private. The current public source export is published at https://github.com/kevinseabourne/decentralised-db.
Current Product Surface
Supported authentication model
The supported user authentication and recovery model is:
createIdentity(passphrase)loadIdentity(passphrase)recoverIdentity(recoveryPhrase, newPassphrase)
Passkeys are not part of the supported product surface.
For Expo or React Native auth/session flows, use the dedicated auth client export:
decentralised-db/auth/expo-auth-session
For native-facing local-first database clients, use the runtime-backed entrypoints:
decentralised-db/expodecentralised-db/desktop
Supported sync model
- local-first writes through IndexedDB
- optional encrypted cloud sync through the Cloudflare Worker API
- optional realtime collaboration when cloud sync is enabled
- authenticated media reads; anonymous/public media delivery is not part of the reviewed contract
Browser auth/session state should stay in IndexedDB-backed storage. The reviewed SDK path does not require localStorage for auth/session persistence.
The current SDK passphrase KDF for local unlock is Argon2id for newly created identities. Legacy PBKDF2-wrapped local keys remain readable so existing users can still unlock older accounts and migrate forward safely.
High-sensitivity deployment hardening
For sensitive deployments, the reviewed local auth/config path now supports external secret-manager supplied sealing keys:
JWST_AUTH_SESSION_SEALING_KEY_B64injects the 32-byte base64 session seal key forjwst-authJWST_AUTH_REQUIRE_EXTERNAL_SESSION_SEALING_KEY=truemakesjwst-authfail closed if that key is not suppliedJWST_CLOUD_CONFIG_KEY_B64injects the 32-byte base64 config seal key forjwst-cloudJWST_CLOUD_REQUIRE_EXTERNAL_CONFIG_KEY=truemakesjwst-cloudfail closed if that key is not supplied- when
ENVIRONMENT=productionorNODE_ENV=production,jwst-authnow requires an external session sealing key by default unlessJWST_AUTH_ALLOW_LOCAL_SESSION_SEALING_KEY_IN_PRODUCTION=trueis set explicitly - when
ENVIRONMENT=productionorNODE_ENV=production,jwst-cloudnow requires an external config sealing key by default unlessJWST_CLOUD_ALLOW_LOCAL_CONFIG_KEY_IN_PRODUCTION=trueis set explicitly
This is an external-key path, not OS keychain / HSM integration.
The reviewed lookup-table path that removes legacy ALLOW FILTERING from cloud identity and owner reads also requires the runtime lookup schema in:
libs/jwst-cloud/scripts/create_runtime_lookup_tables.cql
Current AI runtime
The current reviewed runtime topology is:
- default lane:
deepinfra_qwenwithQwen/Qwen3.5-35B-A3B - supported lane:
chutes_glmwithzai-org/GLM-5.1-TEE
For sensitive deployments, the worker now keeps AI model selection fail-closed by default:
- apps can discover the configured lanes through
GET /api/ai/providersand let users choose among them explicitly - clients can use only the configured provider/model lanes unless operators explicitly allow additional model overrides
- in multi-lane deployments, additional model overrides must be allowlisted per lane with
AI_CHAT_PRIMARY_ALLOWED_MODEL_OVERRIDES,AI_CHAT_SECONDARY_ALLOWED_MODEL_OVERRIDES, orAI_CHAT_CHUTES_KIMI_ALLOWED_MODEL_OVERRIDES - legacy global model override env vars are honored only for single-lane deployments to avoid cross-provider routing surprises
- Chutes Kimi can be enabled as an approved opt-in lane with
AI_CHAT_CHUTES_KIMI_MODEL/AI_RUNTIME_CHUTES_KIMI_MODEL - if you need the thinking variant instead of the default K2.6 lane, set the Chutes Kimi model env var to the approved upstream model slug you want reviewed
- the worker does not automatically fail over across providers during chat requests; if the selected lane is unavailable, the request fails closed on that lane
- the upstream
userfield is pinned to the authenticated session subject by default; client-side overrides require explicit operator opt-in
Current notification layer
The Cloudflare Worker exposes a reviewed internal notification route for email delivery:
POST /internal/email/send
That route is intended for internal services and operator-controlled notification flows. It is not part of end-user auth.
Installation
pnpm installSDK Quick Start
Local-only usage
import { DecentralizedDB } from 'decentralised-db';
const db = new DecentralizedDB({
name: 'my-app',
encryption: true,
cloudSync: false,
});
await db.connect();
const identity = await db.createIdentity('your-long-passphrase');
// Present identity.recoveryPhrase once in a secure UI and require the user to
// store it offline. Do not log it or send it to analytics.
await db.create('notes', {
title: 'Hello',
body: 'Private local data',
});
const notes = await db.find('notes');
console.log(notes);Local-first with cloud sync
import { DecentralizedDB } from 'decentralised-db';
const db = new DecentralizedDB({
name: 'my-app',
encryption: true,
cloudSync: true,
cloudEndpoint: 'https://api.decentralised-db.com',
});
await db.connect();
const session = await db.loadIdentity('your-passphrase');
await db.create('projects', {
name: 'Launch',
status: 'go',
});
db.subscribe('projects', (doc) => {
console.log('Realtime change', doc);
});Recovery flow
import { DecentralizedDB } from 'decentralised-db';
const db = new DecentralizedDB({
name: 'my-app',
encryption: true,
cloudSync: true,
cloudEndpoint: 'https://api.decentralised-db.com',
});
await db.connect();
await db.recoverIdentity(
'your 24 word recovery phrase goes here',
'your-new-passphrase',
);Expo auth/session usage
import * as SecureStore from 'expo-secure-store';
import * as Crypto from 'expo-crypto';
import { createExpoAuthSessionClient } from 'decentralised-db/expo';
const auth = createExpoAuthSessionClient({
secureStore: SecureStore,
cryptoModule: Crypto,
name: 'my-app',
cloudEndpoint: 'https://api.decentralised-db.com',
});
await auth.connect();
const user = await auth.createIdentity('correct horse battery staple galaxy lantern');
const session = await auth.loadIdentity('correct horse battery staple galaxy lantern', user.id);Expo local-first database usage
import * as SecureStore from 'expo-secure-store';
import * as SQLite from 'expo-sqlite';
import * as Crypto from 'expo-crypto';
import { createExpoDatabaseAsync } from 'decentralised-db/expo';
const db = await createExpoDatabaseAsync({
name: 'my-app',
encryption: true,
cloudSync: true,
cloudEndpoint: 'https://api.decentralised-db.com',
sqliteModule: SQLite,
secureStore: SecureStore,
cryptoModule: Crypto,
});
await db.connect();
await db.loadIdentity('correct horse battery staple galaxy lantern');
await db.create('notes', { title: 'Hello from Expo' });
await db.uploadFile({
uri: 'file:///data/user/0/app/cache/photo.jpg',
name: 'photo.jpg',
type: 'image/jpeg',
});
const accounts = await db.accounts.listAccounts();Desktop local-first database usage
import keytar from 'keytar';
import { DatabaseSync } from 'node:sqlite';
import {
DesktopDatabase,
createDesktopCredentialStoreAdapter,
createDesktopSqliteRuntime,
createNodeSqliteSyncDriver,
} from 'decentralised-db/desktop';
const runtime = createDesktopSqliteRuntime({
database: createNodeSqliteSyncDriver(new DatabaseSync('decentralised-db.sqlite')),
secretStorage: createDesktopCredentialStoreAdapter(keytar, {
service: 'decentralised-db',
accountPrefix: 'desktop:',
}),
});
const db = new DesktopDatabase({
name: 'desktop-app',
encryption: true,
cloudSync: true,
cloudEndpoint: 'https://api.decentralised-db.com',
runtime,
isOnline: () => true,
});
await db.connect();
await db.loadIdentity('correct horse battery staple galaxy lantern');
await db.create('documents', { title: 'Desktop local-first record' });
const accounts = await db.accounts.listAccounts();Account Management
The current account-switching model is intentionally conservative:
- account switching requires the account passphrase
- deletion risk is surfaced before local account deletion
- recovery depends on the recovery phrase, not biometric credentials
Repo Structure
/client-sdk— TypeScript SDK and browser-facing local/cloud logic/cloudflare-workers— public API, sync, AI, and notification runtime/libs/jwst-*— Rust core, auth, storage, codec, and related infrastructure/scripts/ci— launch, signoff, audit, and CI automation/scripts/ops— operational tooling and health/evidence helpers/docs— current product and operator documentation
Key Docs
Read these first
- Start Here
- Current Product Surface
- Architecture Map
- Mobile And Desktop Architecture Plan
- Documentation Trust Map
Current operational and strategy docs
- GitLab Automation Setup
- AI Operating Model
- Multi-Device Login Guide
- Migration Guide
- Open Source Strategy
- Commercial Strategy
Supporting background docs
Development
Common commands:
pnpm install
pnpm run pack:sdk
pnpm run typecheck:launch
pnpm run typecheck:signaling
pnpm run test
bash scripts/enterprise-launch-gates.shRelease and Safety Rules
- never patch production directly
- work through branches, merge requests, CI, canary, and rollback
- do not commit secrets, backups, generated launch evidence, or local tool state
- preserve the local-first and privacy boundary by default
GitLab
Canonical repository:
Issues:
License
MIT
