Overview
Always-on. Cannot be opted out. Source of truth:
packages/hub/src/kernel/{noydb,vault,collection}.ts
What it is
noy-db is an encrypted, offline-first document store: your application holds the keys, and the storage backend only ever sees ciphertext. Before you read or write anything, you need to know where a record lives — that is what this page defines. Everything in noy-db hangs off a three-layer data model, implemented in the kernel (the always-on core of @noy-db/hub that every install ships, as opposed to the opt-in services):
Noydb ← top-level instance from createNoydb()
└── Vault ← tenant / company namespace ("acme", "firm-2026")
└── Collection<T> ← typed record set ("invoices", "clients")
Noydbis one unlocked session. It holds the encryption keys in memory while you work and hands them back to nobody.Vaultis an isolated namespace — one tenant, one company, one user. Each vault has its own keyring, so access to one vault says nothing about another.Collection<T>is a typed set of records inside a vault. The TypeScript generic carries throughget/put/query/scan, so records come back typed.
Every record is addressed by a (vault, collection, id) triple.
Lifecycle
The full round trip — create a session, open a vault, write and read a record, close:
import { createNoydb } from '@noy-db/hub'
type Invoice = { amount: number; status: string }
// Without `store`, noy-db uses its built-in in-memory store — nothing
// persists past the session. Pass `store: …` to persist (see Stores below).
const db = await createNoydb({ user: 'alice', secret: 'a-strong-secret' })
const vault = await db.openVault('acme')
const invoices = vault.collection<Invoice>('invoices')
await invoices.put('inv-1', { amount: 100, status: 'paid' })
const inv = await invoices.get('inv-1')
// → { amount: 100, status: 'paid' }
db.close() // wipes the in-memory encryption keys (KEK + per-collection DEKs)
{ "amount": 100, "status": "paid" }
get returns the decrypted record, or null if the id does not exist. The KEK (key-encryption key, derived from your secret) and the per-collection DEKs (data-encryption keys) never leave memory — Enclave covers the key hierarchy.
Handles
Each handle has its own page with its main methods:
- Client — the
Noydbsession - Vault — an isolated namespace
- Collection — a typed set of records
Reserved collection names
noy-db keeps its own bookkeeping in internal collections whose names start with _:
_keyring— wrapped DEKs per user_history— version snapshots (withwithHistory())_ledger/_ledger_deltas— audit ledger (withwithHistory())_dict_<name>— shared dictionaries (withwithI18n())_consent_audit— consent log (withwithConsent())_periods— period anchors (withwithPeriods())_idx/<field>/<id>— persisted index side-cars (withwithIndexing())_blob_*— blob slot metadata + chunks (withwithBlobs())_presence_<collection>— presence storage-poll fallback (withwithSync())
Reserved names cannot be opened through the generic handle: vault.collection() throws ReservedCollectionNameError for _dict_*, _sequences, _links_*, and secret-bearing internals such as _sync_credentials — use their dedicated APIs instead (vault.dictionary(), vault.sequence(), vault.link()). Plaintext exports skip all underscore-prefixed collections, so internal bookkeeping never leaks into an export; vault.dump() includes them, because a full backup needs them.
Edge cases & limits
- Memory-first: an eager collection (the default) decrypts its full record set into memory on first access. The target scale is 1K–50K records per collection; beyond that, open heavy collections in lazy mode (
prefetch: falseplus acachebound) so reads go through a size-bounded LRU cache — see lazy, and indexing for querying lazy collections. - Concurrent writers: every stored envelope carries a version counter
_v, and the store contract supports compare-and-swap on it — a write that loses a version race fails withConflictError. Multi-recordtransaction()batches pre-flight these version checks and commit all-or-nothing. - Heavy fields: a record is one monolithic encrypted envelope — reading any field decrypts the whole record. For a collection mixing small hot fields with a large rarely-read one, satellite collections (experimental) offer an off-row modeling option.
See also
- Enclave — the key hierarchy behind KEK / DEK
- Stores — the pluggable ciphertext storage layer
- Permissions & Keyring — multi-user access
- Services catalog — everything the kernel gates behind
with*()