Nodis is a schema-free document database for embedded and client/server use. It provides compact binary objects, transactional object pipelines, field-level authorization, a native WAL with durable snapshots, and permission-filtered client synchronization over TCP or TLS 1.3 (including mutual TLS).
Use DB.Checkpoint() for manual WAL compaction or Options.CheckpointInterval for periodic checkpoints. Recovery preserves field IDs, ACLs, pipeline bindings, and object bytes. Checkpoints currently hold the database write lock; live objects remain in memory.
Persistent databases (Options.Dir set) now sync the WAL before acknowledging every commit by default. The legacy Durable flag remains source-compatible; Durable: false no longer disables sync. Only explicit UnsafeNoSync: true (or server -durable=false) skips commit syncs and risks losing recent commits on a machine crash. Flush and Close still sync and report errors. An empty Dir remains memory-only.
Opening storage syncs the WAL and its directory hierarchy and holds an exclusive nodis.lock file lock until close or process exit. Use one database instance per directory, on a local filesystem supporting directory sync and advisory locks (Linux, macOS, FreeBSD, OpenBSD, NetBSD, DragonFly BSD); unsupported platforms refuse persistent opens. Never remove the lock file while a database is running. WAL write/sync failures stop further writes until reopening; a failed commit may be recovered if its complete record reached disk, so reconcile before retrying. Recovery durably removes incomplete final records and rejects checksum corruption. This protects against process crashes within filesystem/device guarantees; backups or replication are still needed for media failure. Disk and wire formats are unchanged.
Queries support generic scalar filters, field projections, and ascending ObjectID keyset pagination through DB.Find / FindInto and client Find / FindAsync. Automatic generic secondary indexes accelerate equality, inequality, range, and string/byte-prefix filters without a schema or index declaration. The planner selects the smallest single-field candidate range or the shorter primary scan, then rechecks all predicates and field permissions. Reusable query buffers, codecs, and server execution allocate zero objects after warmup. Indexes update with commits and rebuild in bulk after recovery, preserving the disk and wire formats. Per-field AVL trees reuse arena nodes, providing O(log n) insertion/removal with prepared capacity and O(k) traversal of a selected range. Arena growth can allocate and copy nodes; tree nodes use more memory than sorted entries. The primary ID index still uses a sorted array, existence predicates still scan, and pages do not share a retained snapshot.
client.Dial automatically synchronizes authorized data. client.DialWithOptions can disable synchronization, and client.DialTLS encrypts requests and synchronization. Successful synchronous results and generic Future[T] completions confirm server execution; local cached views update independently.
Clients invalidate their cache on detected synchronization failure and publish replacement snapshots atomically. SyncStatus() exposes readiness, the received-event watermark, permission revision, and errors. Reconnection rebuilds authorized data, including after permission restoration. Interrupted requests discard the command connection; the next request reconnects without replaying the failed one. Interrupted mutations can return client.ErrOutcomeUnknown: the server may have committed despite the missing response.
Generic storage and network frames reuse buffers. Regression tests enforce zero allocations for prepared scalar/cache lookups, sync status reads, protocol encoding, warmed frame reads/writes (including real TCP), and WAL appends; connection setup and full CRUD operations may allocate.
See README_zh-cn.md and DESIGN.md.