Core concepts
noy-db is an encrypted, offline-first document store for JavaScript/TypeScript applications: your application holds the encryption keys, and the storage backend — a file, a browser database, a cloud bucket — only ever sees ciphertext. That property is called zero-knowledge: whoever operates the storage can read nothing about your data. This section covers the core (the kernel): the part of the @noy-db/hub package that every install ships and that cannot be opted out of. Everything beyond it — history, sync, transactions, aggregation, and the rest — is a service you opt into with a with*() factory, and that tree-shakes out of your bundle when unused.
Four words you'll see everywhere
- Hub — the npm package
@noy-db/hub, and by extension the core library it ships. The hub is the trust boundary: everything inside it may handle plaintext, everything outside it never does. - Vault — a runtime data space (a tenant, a company, a user) that the hub opens for you. Not a package — the thing your records live in.
- Collection — a typed record set inside a vault (
invoices,clients). - Kernel (and its inner enclave) — the always-on part of the hub: the layer this section documents, which every install ships. The enclave is the kernel's innermost crypto region, the only code that ever touches keys.
So: the hub (a package) opens a vault (a data space) holding collections (typed records), and the kernel is the part of the hub that does this without any opt-ins. Full definitions live in the glossary.
Why a fixed core
The split exists for two reasons:
- Zero-config completeness. Using only what this section documents — no services, no extra packages, no
storeoption — you get a fully working encrypted document store: create a session, open a vault, put/get/query typed records. Persistence, multi-user access, and every advanced capability are additions, never prerequisites. - A small, non-negotiable trust surface. The core's storage guarantees — records are encrypted inside the hub before any store sees them, keys live only in memory, queries run after decryption — hold no matter which services you add, because services sit inside the hub while the store stays outside the encryption boundary.
The mental model
Everything in the core hangs off a three-layer hierarchy; every record is addressed by a (vault, collection, id) triple:
Noydb ← one unlocked session, from createNoydb()
└── Vault ← isolated namespace with its own keys ("acme")
└── Collection<T> ← typed record set ("invoices")
Around that hierarchy, four core mechanisms do the work. When you put() a record, the hub validates it against your schema (if you declared one), encrypts it with a per-collection key from the vault's keyring, and hands the resulting ciphertext envelope to the store — the pluggable backend. Reads reverse the path. Queries filter decrypted records inside the hub; the store never evaluates a predicate — it is a dumb, untrusted ciphertext shelf.
The whole core in one round trip
Every line below exercises a different core concern — session, vault, typed collection, encrypted write, decrypted read, in-hub query, key wipe:
import { createNoydb } from '@noy-db/hub'
type Invoice = { amount: number; status: string }
// No `store` option → the 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' })
await invoices.put('inv-2', { amount: 2500, status: 'open' })
await invoices.get('inv-1')
// → { amount: 100, status: 'paid' }
invoices.query().where('status', '==', 'paid').toArray()
// → [{ amount: 100, status: 'paid' }]
db.close() // wipes the in-memory encryption keys
For a guided version of this snippet with installation steps, start at Getting started.
Reading order
The six pages, in a learning-path order — grasp the data model, then the three handles, then the storage and access layers:
- Overview — where a record lives: the
Noydb→Vault→Collection<T>hierarchy, the session lifecycle, and how the three handles map to their pages. - Client — the
dbhandle: theNoydbsession (open vaults, transactions, keyring management) and the client-side encryption it owns. - Vault — the isolated, encrypted namespace: opening collections, backup/restore, and the methods that services unlock.
- Collection — the typed record set:
get/put/query/scan, the chainable query builder, streamingscan(), and Standard Schema validation. - Stores — the pluggable ciphertext backend: the
NoydbStorecontract, the essential stores, and the extended cloud/SQL/remote-FS family (per-store counts and capabilities: the generated store capabilities matrix). - Permissions & Keyring — how a vault admits users: the per-user keyring file, roles, and per-collection
rw/ro/nonepermissions (assumes the key hierarchy from the Client page above).
Relationships are not in the core. Foreign keys, joins, and link sets are always-on kernel code, but a single-collection document store needs none of them — so they're documented as Foreign refs and its siblings (
joins,cross-join,links) in the query catalog, keeping this section relationship-free.
How it maps to the source tree
Each page names its source of truth in the noy-db repo, under packages/hub/src/:
| Concern | Source | Page |
|---|---|---|
| Session, vault, collection | kernel/noydb.ts, kernel/vault.ts, kernel/collection.ts | Client · Vault · Collection |
| Store contract + built-in memory store | kernel/types.ts (NoydbStore), kernel/memory-store.ts | Stores |
| Schema validation | kernel/schema.ts | Collection → Schema & validation |
| Query + scan builders | kernel/query/ | Collection → Querying |
| Encryption + data format | capsule/enclave-aes/ | Client → Enclave |
| Keyring format + roles | with-party/team/keyring.ts (the single-owner keyring is always-on; multi-user management is the opt-in team service) | Permissions & Keyring |
Invariants
Four consequences of the design that hold for every noy-db application, with or without services:
- Encryption precedes storage. A record is encrypted inside the hub before any store sees it; only ciphertext crosses the store boundary.
- Keys are session-scoped. The key-encryption key and the per-collection data keys exist only in memory while the session is unlocked;
db.close()wipes them. - The store never computes. Filtering, ordering, and pagination all happen inside the hub after decryption — the backend stores and returns envelopes, nothing more.
- You pay only for what you opt into. An unused service tree-shakes out of the bundle; the core documented here is the entire mandatory footprint.
Next
Work through the six pages above in order, or jump into the Orientation map to pick capabilities and land on the exact pages you need. For the deeper structural view — how the core, the services, and the satellite packages compose — see Architecture.