2026-10-24 19:53:25 +00:00
2026-11-13 18:04:09 +00:00
2026-10-24 19:53:25 +00:00
2026-10-24 19:53:25 +00:00
2026-10-24 19:53:25 +00:00
2026-11-13 18:04:09 +00:00
2026-10-24 19:53:25 +00:00
2026-10-24 19:53:25 +00:00
2026-11-13 18:04:09 +00:00
2026-10-24 19:53:25 +00:00
2026-10-24 19:53:25 +00:00
2026-10-24 19:53:25 +00:00

@lossless.org/nosqldb

A MongoDB-wire-compatible database server powered by its own native Rust MVCC engine, engineered from scratch in a clean room with a public API surface derived from the Apache-2.0-licensed official MongoDB Node.js driver, and built for every scale: from a test fixture or a database embedded in a single application to large enterprise deployments. Its goal is to beat MongoDB on performance and on features. It supports the documented command surface through the official mongodb driver without an external MongoDB server. Memory and file storage use the same format-3 store, including the operation log, point-in-time revert and debug dashboard. Matching executables are bundled with the package.

Issue Reporting and Security

For reporting bugs, issues, or security vulnerabilities, please visit community.foss.global/. This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a code.foss.global/ account to submit Pull Requests directly.

Install

pnpm add @lossless.org/nosqldb

What It Does

@lossless.org/nosqldb is a real database server that speaks the wire protocol used by MongoDB drivers. The core engine is written in Rust for high performance, with a thin TypeScript orchestration layer. Connect with the standard mongodb Node.js driver — no mocks, no stubs, no external binaries required.

From one process to enterprise scale

NoSQLDB brings its own clean-room engineered, MongoDB-compatible engine to every stage of an application's life:

  • Development and tests: start a server from code, in memory or on disk, with nothing else to install.
  • Embedded: ship the database inside your application and reach it over TCP or a Unix socket.
  • Enterprise scale: large datasets, many collections and indexes, and many concurrent clients on its own durable MVCC engine.

The target is to beat MongoDB on performance and on features: more throughput, lower latency, and less memory and disk per operation, with MongoDB-compatible behaviour wherever applications rely on it, and capabilities beyond it where they help, such as the built-in operation log, point-in-time revert and debug dashboard.

MongoDB compatibility

NoSQLDB is an independent implementation, written from scratch to be MongoDB compatible. Its public API surface is derived from the client side: the official MongoDB Node.js driver (mongodb, Apache-2.0 licensed) and the commands and replies it exchanges. Compatibility is established by runtime testing against MongoDB through its wire protocol and the official drivers. NoSQLDB contains no MongoDB server code. It is built with the clean-room setup of AGL: the sessions that write its code work in confined containers that hold nothing but this repository. Where this readme says a behaviour, code or message is MongoDB compatible, it was implemented to match what such testing observes.

Contributions follow the same clean-room rules: describe behaviour as implemented to be MongoDB compatible, never as the same as a MongoDB version; record provenance as observed by runtime testing against MongoDB; and never read, quote, cite or link MongoDB server source code or its test suites.

MongoDB is a trademark of MongoDB, Inc. NoSQLDB is not affiliated with or endorsed by MongoDB, Inc.

Why NoSQLDB?

NoSQLDB External DB Server
Startup Managed by the application Managed separately
Binary distribution Matching executables bundled in the package Separate installation
Install pnpm add System package / Docker
Persistence Memory or file-based Full disk engine
Debug UI Built in External tooling
Point-in-time revert Built in Depends on the database
Built for Unit tests, CI/CD, local dev, embedded applications and large-scale production Production

Three Ways to Use It

  • 🎯 LocalNoSqlDb — Zero-config convenience. Give it a folder path, get a persistent database over a Unix socket. Done.
  • 🏗️ NoSqlDbServer — Full control. Configure port, host, storage backend, Unix sockets. Great for test fixtures or custom setups.
  • 🖥️ NoSqlDbDebugServer — Launch a web dashboard to visually browse collections, inspect the operation log, and revert to any point in time.

Architecture: TypeScript + Rust 🦀

NoSQLDB uses a sidecar binary pattern — TypeScript handles lifecycle, Rust handles all database operations:

┌──────────────────────────────────────────────────────────────┐
│                   Your Application                           │
│                  (TypeScript / Node.js)                      │
│  ┌──────────────────┐      ┌───────────────────────────┐     │
│  │  NoSqlDbServer   │─────▶│  RustDbBridge (IPC)       │     │
│  │  or LocalNoSqlDb │      │  @push.rocks/smartrust    │     │
│  └──────────────────┘      └───────────┬───────────────┘     │
└────────────────────────────────────────┼─────────────────────┘
                                         │ spawn + JSON IPC
                                         ▼
┌──────────────────────────────────────────────────────────────┐
│                    rustdb binary                             │
│                                                              │
│  ┌──────────────┐  ┌──────────────┐  ┌───────────────┐       │
│  │ Wire Protocol│→ │Command Router│→ │   Handlers    │       │
│  │  (OP_MSG)    │  │  (40+ cmds)  │  │ Find,Insert.. │       │
│  └──────────────┘  └──────────────┘  └───────┬───────┘       │
│                                              │               │
│  ┌─────────┐ ┌────────┐ ┌───────────┐ ┌──────┴──────┐        │
│  │  Query  │ │ Update │ │Aggregation│ │   Index     │        │
│  │ Matcher │ │ Engine │ │  Engine   │ │   Engine    │        │
│  └─────────┘ └────────┘ └───────────┘ └─────────────┘        │
│                                                              │
│  ┌──────────────────┐  ┌──────────────────┐  ┌──────────┐    │
│  │ Native MVCC pages│  │ Memory/file I/O  │  │  OpLog   │    │
│  └──────────────────┘  └──────────────────┘  └──────────┘    │
└──────────────────────────────────────────────────────────────┘
              ▲
              │ TCP / Unix Socket (wire protocol)
              │
┌─────────────┴────────────────────────────────────────────────┐
│              MongoClient (mongodb npm driver)                │
│              Connects directly to Rust binary                │
└──────────────────────────────────────────────────────────────┘

The TypeScript layer handles lifecycle only (start/stop/configure via IPC). All database operations flow directly from the MongoClient to the Rust binary over TCP or Unix sockets — zero per-query IPC overhead.


Quick Start

Option 1: LocalNoSqlDb (Zero Config) 🎯

The fastest way to get a persistent local database:

import { LocalNoSqlDb } from '@lossless.org/nosqldb';
import { MongoClient } from 'mongodb';

// Point it at a folder — that's it
const db = new LocalNoSqlDb({ folderPath: './my-data' });
const { connectionUri } = await db.start();

// Connect with the standard driver
const client = new MongoClient(connectionUri, { directConnection: true });
await client.connect();

// Use it like any wire-protocol-compatible database
const users = client.db('myapp').collection('users');
await users.insertOne({ name: 'Alice', email: 'alice@example.com' });
const user = await users.findOne({ name: 'Alice' });
console.log(user); // { _id: ObjectId(...), name: 'Alice', email: 'alice@example.com' }

// Data persists to disk automatically — survives restarts!
await client.close();
await db.stop();

Option 2: NoSqlDbServer (Full Control) 🏗️

import { NoSqlDbServer } from '@lossless.org/nosqldb';
import { MongoClient } from 'mongodb';

// TCP mode
const server = new NoSqlDbServer({ port: 27017 });
await server.start();

const client = new MongoClient('mongodb://127.0.0.1:27017');
await client.connect();

const db = client.db('myapp');
await db.collection('users').insertOne({ name: 'Alice', age: 30 });
const user = await db.collection('users').findOne({ name: 'Alice' });

await client.close();
await server.stop();

Option 3: Debug Server (Visual Dashboard) 🖥️

Launch a web-based dashboard to inspect your database in real time:

debugserver and debugui are optional subpath exports. Install their debug dependencies only when you use them:

pnpm add '@api.global/typedserver@^8' '@design.estate/dees-element@^2'
import { NoSqlDbServer } from '@lossless.org/nosqldb';
import { NoSqlDbDebugServer } from '@lossless.org/nosqldb/debugserver';

const server = new NoSqlDbServer({ storage: 'memory' });
await server.start();

const debugServer = new NoSqlDbDebugServer(server, { port: 4000 });
await debugServer.start();
// Open http://localhost:4000 in your browser 🚀

The debug dashboard gives you:

  • 📊 Dashboard — server status, uptime, database/collection counts, operation breakdown
  • 📁 Collection Browser — browse databases, collections, and documents interactively
  • 📝 OpLog Timeline — every insert, update, and delete with expandable field-level diffs
  • ⏪ Point-in-Time Revert — select any oplog sequence, preview what will be undone, and execute

📝 Operation Log & Point-in-Time Revert

The native engine provides these APIs for both memory and file storage. Revert publishes one atomic indexed data change and preserves authentication, fencing, publication holds and durable retry history.

Every write operation (insert, update, delete) is automatically recorded in an in-memory operation log (OpLog) with full before/after document snapshots. The OpLog lives in RAM and resets on restart — it covers the current session only, and retention is bounded (default: 10,000 entries or 8 MiB, whichever is hit first; configurable via the oplog server option). The byte limit covers the retained documents and the table that holds them. Once a limit is exceeded, the oldest entries are evicted. oplog: { enabled: false } detaches it: writes then skip the history copy of each changed document, getOpLog, getOpLogStats and revertToSeq reject with oplog is disabled, and getMetrics() reports oplogEnabled: false. This enables:

  • Change tracking — see exactly what changed, when, and in which collection
  • Field-level diffs — compare previous and new document states
  • Point-in-time revert — undo operations back to any retained sequence number
  • Dry-run preview — see what would be reverted before executing

Programmatic OpLog API

import { NoSqlDbServer } from '@lossless.org/nosqldb';

const server = new NoSqlDbServer({ port: 27017 });
await server.start();

// ... perform some CRUD operations via MongoClient ...

// Get oplog entries
const oplog = await server.getOpLog({ limit: 50 });
console.log(oplog.entries);
// [{ seq: 1, op: 'insert', db: 'myapp', collection: 'users', document: {...}, previousDocument: null }, ...]

// Get aggregate stats
const stats = await server.getOpLogStats();
console.log(stats);
// { currentSeq: 42, totalEntries: 42, oldestSeq: 1, approxBytes: 18342, entriesByOp: { insert: 20, update: 15, delete: 7 } }

// Preview a revert (dry run)
const preview = await server.revertToSeq(30, true);
console.log(`Would undo ${preview.reverted} operations`);

// Execute the revert — undoes all operations after seq 30
const result = await server.revertToSeq(30, false);
console.log(`Reverted ${result.reverted} operations`);

// Reverts can only reach back as far as retained oplog history —
// revertToSeq returns an error if the target sequence is older than the
// oldest retained entry (evicted by the retention limits).

// Browse collections programmatically
const collections = await server.getCollections();
const docs = await server.getDocuments('myapp', 'users', 50, 0);

OpLog Entry Structure

Each entry contains:

Field Type Description
seq number Monotonically increasing sequence number
timestampMs number Unix timestamp in milliseconds
op 'insert' | 'update' | 'delete' Operation type
db string Database name
collection string Collection name
documentId string Document _id as hex string
document object | null New document state (null for deletes)
previousDocument object | null Previous document state (null for inserts)

🔢 Document Field Order

Every stored document leads with _id, implemented to be MongoDB compatible. Inserts, upserts, replacements, findAndModify, $out and $merge move a client-supplied _id to the front (the Node.js driver appends its generated _id after the caller's fields) and generate a missing one there. Every other field keeps its written order: update operators change existing fields in place and append the fields they create, and a replacement keeps its own field order after _id. Documents stored with a trailing _id by 10.7.3 and earlier keep it until an update, replacement or $merge changes them.


✋ No-Op Write Detection

The engine detects document rewrites that change nothing and skips them entirely — no storage write, no WAL append, no index update, and no oplog entry. A skipped rewrite still counts as matched (matchedCount: 1) but reports modifiedCount: 0.

To be MongoDB compatible, a rewrite is skipped only when the document stays identical: the same fields in the same order with the same values and BSON types, at every depth. Only the position of _id is ignored, so a document stored with a trailing _id by 10.7.3 and earlier is not rewritten by a write that changes nothing. A replacement or $set that only reorders fields is written. Explicit changes to _updatedAt are persisted and returned just like changes to any other field. Applications decide when to advance their timestamps.

Counters are exposed through serverStatus:

const status = await db.command({ serverStatus: 1 });
console.log(status.writes);
// {
//   updatesWritten: 42,        // rewrites that reached storage, index, and oplog
//   noopSkipped: {
//     identical: 1337,         // post-image equal to the stored document
//     volatileOnly: 0,         // retained metric; timestamp changes now write
//   },
// }

API Reference

Find execution plans and pooled cursors

collection.find(filter).hint(index).sort(order).limit(count).explain('executionStats') uses the serving query planner and reports actual document and index-entry reads. queryPlanner inspects the plan without executing it; allPlansExecution also executes the selected plan, with an empty trial-execution list because selection is deterministic. Explain currently supports find outside transactions. Queries can use a compound index's sort prefix after equality-constrained fields, and scalar $in predicates on single-field indexes use bounded, deduplicated point lookups. Unsupported point shapes retain ordinary predicate evaluation.

Driver cursors belong to their logical session and authenticated principal generations, so getMore can use another connection in the pool. Session retirement, expiry, and shutdown release their cursors. Reads made without a logical session retain connection ownership and disconnect cleanup.

NoSqlDbServer

The core server class. Manages the Rust database engine and exposes connection details.

Constructor Options (INoSqlDbServerOptions)

binaryPath?: string selects one exact engine executable for this server. When supplied, Smartrust validates a regular executable file and never searches NOSQLDB_RUST_BINARY, npm packages, development directories or PATH, even if the selected path is unusable. Missing, empty, malformed, non-file and non-executable paths reject startup with RustBinaryLocatorError, code ERR_RUST_BINARY_EXPLICIT_PATH_INVALID. Validation never changes the target's permissions. Executable launch or readiness failures also fail startup without selecting another engine. Omitting the option preserves normal discovery.

Use an absolute path to the caller-installed, verified artifact. Smartrust anchors relative paths when the underlying bridge is constructed, without normalizing away filesystem symlink traversal. A bare name is a relative file path, not a PATH lookup. Options are instance-local; no environment variables are changed. Installer verification and protected installation paths remain the caller's responsibility.

import { NoSqlDbServer } from '@lossless.org/nosqldb';

const server = new NoSqlDbServer({
  binaryPath: '/opt/spark/engines/nosqldb/rustdb_linux_amd64',
  socketPath: '/run/spark/private/nosqldb.sock',
  storage: 'file',
  storagePath: '/var/lib/spark/nosqldb',
});
await server.start({ timeoutMs: 30_000 });
try {
  const connectionUri = server.getConnectionUri();
  // Pass connectionUri to SmartData for application persistence.
} finally {
  await server.stop(); // Resolves after the owned engine has terminated.
}

The socket parent directory must already exist with the caller's desired access permissions. RustDbBridge also accepts { binaryPath } for direct lifecycle use. When its spawn() returns false, spawnFailureMessage says what that means as far as the bridge knows: with a binaryPath, that the engine there did not report ready within the bridge's limit, with the path as given and the time waited; without one, that no engine binary was found or that the engine found did not report ready, as nothing tells which. If ending that engine fails, spawn() throws instead of returning false, and spawnFailureMessage keeps the message. After any other spawn(), including one that throws on its own timeoutMs or signal, it is undefined. A binaryPath that does not exist is refused before spawn() returns, with RustBinaryLocatorError.

Engine output

The engine writes its log to its standard error. None of it reaches the host process's standard error: by default the server keeps only the engine's last lines, for errors. To receive the log, pass engineLog, which is called once for every line, in order, without the line break. onEngineExit receives a NoSqlDbEngineExitError for each unexpected exit of the engine: an exit with a non-zero status or by a signal while the server runs and no stop is in progress. An exit with status 0, which the engine makes after a graceful shutdown it was signalled to make, is not reported; the server counts as stopped either way.

import { NoSqlDbServer } from '@lossless.org/nosqldb';

const server = new NoSqlDbServer({
  storage: 'file',
  storagePath: '/var/lib/app/nosqldb',
  engineLog: (line) => logger.info(`nosqldb: ${line}`),
  onEngineExit: (error) => logger.error(error.message, error.engineStderrTail),
});

Whatever is set, these carry the engine's last 64 standard-error lines in their field engineStderrTail, a frozen array of strings, without any change to their message:

  • every rejection of start() of NoSqlDbServer and LocalNoSqlDb, with an empty array when the start failed before it spawned an engine;
  • the NoSqlDbEngineExitError, whose message is nosqldb Rust process exited unexpectedly (code=<code>, signal=<signal>) and which has exitCode and signal;
  • the rejection of a management call that fails because the engine no longer runs.

A line longer than 1,024 characters (UTF-16 code units, as a string's length counts them) keeps its first 1,024, or its first 1,023 when the 1,024th is the first half of a surrogate pair, so that no line holds half of a pair; [<n> more characters] follows, counting the characters cut. engineLog receives the line whole. noSqlDbEngineStderrTailLimits exports both bounds. LocalNoSqlDb accepts both options and passes them to its server. RustDbBridge accepts engineLog as well, and its engineStderrTail getter returns the last lines of the running or most recently spawned engine. Each function runs in its own microtask, after the server has recorded the line or the exit; an exception it throws is not caught and reaches the process as an uncaught exception.

Native memory and file storage

The native MVCC engine is the sole serving engine on Linux and macOS. storage: 'memory' (the default) uses ephemeral native storage; storage: 'file' persists the same record model under storagePath. binaryPath selects the executable; it is independent of storage selection.

fileStorageEngine, persistPath, persistIntervalMs and auth.usersPath have been removed. Supplying them rejects at construction, before filesystem or process side effects. Authentication belongs to the native database and persists with file storage. Memory storage is ephemeral.

import { NoSqlDbServer } from '@lossless.org/nosqldb';

const server = new NoSqlDbServer({
  binaryPath: '/opt/spark/engines/nosqldb/rustdb_linux_amd64',
  socketPath: '/run/spark/private/nosqldb.sock',
  storage: 'file',
  storagePath: '/var/lib/spark/nosqldb',
  auth: {
    enabled: true,
    scramIterations: 15000,
    users: [{ database: 'app', username: 'app-owner',
      password: bootstrapPassword, roles: ['readWrite'] }],
  },
});
await server.start({ timeoutMs: 30_000 });
try {
  const connectionUri = server.getConnectionUri();
  // Connect SmartData with the app credentials after native ownership is acquired.
} finally {
  await server.stop();
}

Supply bootstrapPassword from protected caller configuration. The work factor above is an example, not a universal default. Reopen with the stored SCRAM work factor; bootstrap users may be omitted after initialization. LocalNoSqlDb accepts folderPath, socketPath, binaryPath and the same auth options.

The engine allocates its memory with jemalloc, under settings built into the engine: two arenas, freed memory returned to the operating system over 5 seconds (dirty decay 5,000 ms, muzzy decay 0), and on Linux the allocator's background threads. At start the engine checks that its allocator runs these settings and raises its soft open-files limit to its hard limit (on macOS to at most kern.maxfilesperproc). When either fails, as the check does when _RJEM_MALLOC_CONF in its environment changes a setting, the engine does not start: it exits with status 71, and start() rejects with the reason in engineStderrTail. The MIMALLOC_ARENA_EAGER_COMMIT and MIMALLOC_ALLOW_LARGE_OS_PAGES variables that earlier releases set for the engine are no longer set or read.

The performance figures this release states are measured with file storage on XFS. ext4 was measured only on a loop-backed substitute, so this release makes no latency claim for ext4. Recovery after a crash relies on a filesystem that never exposes stale blocks after a crash: XFS, or ext4 with ordered data (data=ordered, its default mode). ext4 mounted with data=writeback is not supported. On macOS, file storage runs on APFS; the durability gates and the timed gates of this release run on Linux only.

Native query and maintenance efficiency

Bounded synchronous reads, including principal resolution, publication authority and query setup, share one retained snapshot of the root. No such snapshot or resolved authorization is reused across requests or asynchronous suspensions.

Filesystem ownership and root bindings are validated once per command for reads: the reads of each wire command or background maintenance turn reuse one full validation of the enclosing owner, the root and its bindings that began after the command started, run by its own first storage read or by a concurrent command. Reads go through retained descriptors, so a path swap cannot change what they return. A failed validation revokes every earlier one, so commands still running stop at their next read. A validation covers reads for at most one second after it began, measured on a monotonic clock, so a long-running command such as a large aggregation or sort validates again at its first read after that and fails at that read once its root or a binding has been replaced. Every write, whether a commit's, a group's, a maintenance operation's or an index build's, validates the enclosing owner and every binding before it writes, and a commit's conflict checks validate in full before and after themselves, before the commit takes its place in the log. After a write, when its outcome can be unknown, the root's bindings and each file the write wrote, by its binding and its length, are checked through the retained descriptors alone, without opening the owner's path; a failure there fences the root, and the next command's first access validates the enclosing owner again. Startup, recovery and native management calls, which run outside wire commands, validate every access. A root or container renamed or replaced between commands fails the next command's first storage access, and a data or log file replaced underneath the root fails the next write that writes it. Durability, cancellation and close checks remain enforced.

For a query whose filter references only _id and whose projection is exactly { _id: 1 }, the native primary index can supply the original BSON identity without loading the document payload. Range endpoints, order, skip, limit and cursor pagination retain their normal semantics. The engine verifies the index and unique-owner values, the document record's existence, and the storage-owned SHA-256 certificate of the exact BSON _id that every document record holds. Updates that keep the same identity leave the primary owner and the certificate unchanged, avoiding an extra index rewrite. Numeric BSON width changes update the certificate and primary identity atomically with the body. A covered read does not checksum an unrequested document payload. An entry whose owner names another identity than the certificate uses the normal bounded body read. Secondary-index projections and predicates requiring other fields retain normal document reads. No application cache or configuration flag is required.

Concurrent wire insert, update, delete and findAndModify commands can share one durability flush. Each command keeps its own commit marker and retry outcome. Only the private write group sees provisional roots; readers and replies see results after the final flush. The queue holds at most 16 jobs, adds no deliberate delay, and ends each group after 16 jobs or 64 MiB of appended data (one admitted command can cross that byte threshold). Authority, session and input-resource lifetimes remain held through durability. History publication also waits for the flush. DDL, transactions, index builds, imports and maintenance retain their existing independently durable publication paths.

The Rust engine registers SIGTERM and SIGINT before reporting readiness. The first signal closes request admission, drains admitted commands and native workers, and closes storage only after their durable publications finish. An admitted transaction commit finishes atomically or aborts; idle transactions discard their uncommitted changes. An admitted command that reaches the closing session owner fails with ShutdownInProgress (code 91), and one still waiting for a database or collection gate fails with InterruptedAtShutdown (code 11600); a transaction statement carries TransientTransactionError with either, implemented to be MongoDB compatible. Successful shutdown releases storage-root ownership and exits with code 0 even when the parent's management pipe remains open. A second shutdown signal, a drain failure, or the overall 40-second deadline forces exit with code 70; forced exit does not promise drain completion. NoSqlDbServer.stop() also succeeds after a confirmed engine exit without sending another management request to the dead child.

Maintenance batches catalog work and parks completed reclamation and no-TTL sweeps until the committed position changes. Session cleanup skips its metadata scan until a session can expire. The store maintains itself on its own thread: it merges its write buffer into the tree, writes checkpoints, prunes the tombstones no snapshot needs and moves the live data out of data segments that hold much garbage. No pass copies the whole root, and there is no compaction to run, resume or restart.

File startup admission

INoSqlDbServerOptions.fileStorageStartup and ILocalNoSqlDbOptions.fileStorageStartup accept 'current-format-only' (the default) or 'existing-only' for file storage. The exported type is TNoSqlDbFileStorageStartup. Invalid values, including 'migrate', reject at construction. Selecting the policy with memory storage also rejects.

Default startup accepts a fresh root or a current native root, and converts a root that a 10.x release wrote, or a folder that LocalSmartDb of @push.rocks/smartdb wrote, before it serves it (see Storage format migration below). Use fileStorageStartup: 'existing-only' when the database must already exist: missing paths, empty directories and identity-only roots without committed initialization fail instead of creating an empty database, and a 10.x root that holds a committed database counts as existing and is converted as under the default. So does a smartdb folder that holds at least one database folder; one that holds none is refused as an empty directory is, before anything below its top-level names is read. The native owner enforces this policy; no TypeScript existence check or fallback can create a new root. Older binaries reject the new option rather than silently ignoring it. Rehearsal startup retains its separate current-format-only configuration.

Foreign and mixed layouts, a 10.x name beside entries of this release without the record of a conversion in progress, and a 10.x root that the conversion cannot take are rejected before anything is written. TypeScript performs no storage writes before starting the engine. Native format admission uses the owning detector; the exclusive native file-root lease is acquired before initialization, recovery, authentication or index preparation.

A root that carries a format or a feature that this release does not know, such as a root that a later release wrote, is refused before anything is written, and no root is created in its place or beside it. The start's rejection then carries responseErrorCode: 'UNKNOWN_ROOT', which no other start failure carries, and its message names the cause. So is a conversion in progress whose record (.nosqldb-conversion) a later release wrote. A missing storage path is created together with every directory missing above it, each accessible to its owner alone; 'existing-only' creates none of them. An existing storage path must be a directory of the starting user with mode 0700. A start refuses any other and changes nothing. A service that created its smartdb folder itself with a wider mode sets it to 0700 before its first start of this release.

A start that refuses a root that an earlier release wrote, or a conversion of one in progress, tells the cause by a code on the rejection's responseErrorCode where the refusal has one, as a value to read without parsing the message, and leaves the path as it found it:

responseErrorCode The start refused
BUSY a root whose lock a running process holds, a running 10.x process or smartdb engine included
STATE_CORRUPT a 10.x name beside this release's entries without the record of a conversion in progress; a collection whose recorded count its documents do not hold; a collection's metadata that holds no count, next natural ordinal or dropped flag of the type 10.x stores; a place in a collection's natural order that is missing, holds no document identity or another document's, or lies at or past the collection's next ordinal; a collection that is not dropped holding another number of places in its natural order than documents; a document without its ordinal record, or whose ordinal record holds no ordinal or certifies another document; an index definition that does not decode; in a smartdb folder, a record whose checksum fails, or a log record that does so or is incomplete before the log's end, a record of an unknown tag, log operation records whose sequence numbers do not ascend, a log commit record that names no earlier operation record of its log or one that an earlier commit record already names, a value that is not one BSON document or a document without _id, a log delete that carries a value, a file shorter than its 64-byte header or without its header's magic, a file whose header gives another file's kind, a provider-identity.json that does not parse or lacks a member, and an indexes.json that does not parse, holds a member whose JSON value is not of the kind smartdb writes for it, defines no _id_ index, an _id_ index on another key than {_id: 1} or two indexes of one name
UNSUPPORTED a unique index that would hold two documents under one key once each array member is indexed on its own; an index definition that the conversion cannot carry; an index name bound to an index that the conversion does not carry; in a smartdb folder, a header version, file kind, log operation or non-zero reserved header byte that this release does not read, a provider-identity.json version or member and an index definition member that it does not read, a provider-identity.json longer than 4 KiB and an indexes.json longer than the largest document the engine stores, neither of which it reads, a database or collection name it refuses, a system.views collection that this release does not admit as its database's view catalog, an index definition it does not create, a plain number in an index's key that is not an integer inside the int32 range, a document that a collection keeps over the engine's limits, by its size or by its nesting depth as an insert counts it, and a record key or value longer than twice the largest document, which it does not read
CAPACITY a file system that does not hold the space the conversion declares
SOURCE_CHANGED a resumed conversion whose 10.x root or smartdb folder changed since the conversion began, and a smartdb folder that changed while the conversion read it

start() of NoSqlDbServer and of LocalNoSqlDb rejects with the engine's error and the code on it; there is no error class for these codes. None of them is UNKNOWN_ROOT, nothing that the conversion meets while it builds, verifies or removes carries one, and every other refusal of such a root, such as an entry beside it that neither release creates, carries no code. In 11.1.0, other damage in a root that 10.x wrote, among it a key or record that does not decode and a checksum failure or torn frame of its engine, is refused without a code; a refusal without a code says nothing about whether the root is damaged.

Only after await server.start() succeeds should consumers prepare SmartData models or run application migrations. Start cancellation and deadlines cover the native operation; failed-start cleanup confirms child exit, and stop() remains retryable if termination cannot yet be confirmed. A second owner of the same root is rejected without disturbing the winner. Callers must also exclude manual edits and lifecycle changes outside NoSQLDB's ownership protocol.

Storage format migration. Every root this release creates is a format-3 root under .nosqldb-control and .nosqldb-store. A start, under either fileStorageStartup policy, converts a root that a 10.x release wrote, which it recognizes by .__rustdb_internal and .__rustdb_mvcc, and then serves the result. The conversion reads the 10.x root without writing into it, builds the format-3 root beside it under this release's names, verifies it record for record against the 10.x content, runs on it the checks that a start makes of a root's content before it serves it, and only then removes the 10.x entries. In 11.1.0 those checks are three. The first is the check of the root's allocation ownership records: it refuses a root that holds a database allocation left allocating or deprovisioning, an active one without its auth binding, its user or its database, a deprovisioned one that still has its auth binding or its user, or an auth binding without its active allocation, and, because it reads those records through the root's fencing profile and auth profile, a root that lacks either profile or holds one that does not decode. The second is the check of every database's view catalog. The third is the check that no database of the root holds an allocation that is not active, unless a publication holds that database: it refuses a root in which a database still exists beside its deprovisioned allocation. A built root that does not pass those checks is not switched over: the start is refused with no code and a message that says the earlier release's files are intact, nothing of the 10.x root is removed, and every later start is refused the same way. The same holds for a smartdb folder. A smartdb folder that would build such a root is refused before anything is written (see smartdb folders below), so on one this refusal is a defect of this release, unless the cause the message names is a failed read of the storage: report it. The way out of this refusal is by hand. With no process running on the path, remove this release's three entries from it, .nosqldb-conversion, .nosqldb-control and .nosqldb-store: all three, and nothing else. The path then holds only the earlier release's entries, as that release left them. Repair the root with the earlier release and start this release on it again; the conversion begins anew. Do this only when the start's message says "Nothing of the earlier release's was removed: its files are intact": after any other refusal, and on a root that was converted, .nosqldb-store holds the only copy of the data. A root changed with the earlier release while the three entries are still there is refused by this release's next start with SOURCE_CHANGED until they are removed. Where the earlier release refuses the root as well, this release has no way forward for it yet. 10.8.1 does not start on the path while the three entries are there. A conversion that 11.0.0 left interrupted after its removal had begun is finished by this release without the check of the converted root: part of the earlier release's files is already gone, and the converted root is the only complete copy. If this release's start refuses that root, it is refused at every start, as a root that 11.0.0 converted completely is; its data stays in the converted root, and this release does not repair it. It records its progress in .nosqldb-conversion, so a start that is stopped or interrupted during the conversion resumes it at the next start. Before it writes anything it declares the space it needs beside the 10.x root, and the start is refused when the file system does not hold it. It carries the database as of the root's last completed commit, with no history of writes committed before the conversion and no part of an import that had not finished. No earlier release reads a root that this release has created, converted or aligned: keep a stopped copy of the 10.x root if you may need to go back. That copy is a copied root: restored by copying its files, into another directory, onto another machine or back onto its own path, it keeps the binding of its old place, and 10.8.1, like this release, refuses to start on it until it is aligned. Alignment refuses a root that holds auth users, so for such a root a restored copy is no way back. A start refuses, and leaves the path as it found it, a 10.x root that holds no committed database or only part of one, a 10.x name beside entries of this release without the conversion's record, a 10.x root that a running 10.x process holds or that a rehearsal created, a collection whose recorded count its documents do not hold, and a unique index that would hold two documents under one key once each array member is indexed on its own; the codes those refusals carry are listed under File startup admission above. This release also refuses a 10.x root that holds a database which importDatabase wrote, with STATE_CORRUPT and the message NoSQLDB refuses to convert this storage root: an index definition does not decode; such a root is not damaged, the start leaves it as it found it, and 10.8.1 still serves it. The start decides by the top-level names which kind of root the path holds, then takes the 10.x root's own lock, .__rustdb_internal/storage-owner.lock, which a running 10.x process holds, before it checks anything else of the root or reads its content, and it holds the lock until the conversion has removed that file: a 10.x root that a running process holds is refused with BUSY whatever else it holds. The lock file is checked alone first: it must be a regular file of the starting user with one link, which its owner reads and may write and others may only read; it is opened without following a link and never created, so a missing lock file refuses the start. A start that resumes a conversion takes the lock too, at any stage, while the file exists. The conversion's removal takes .__rustdb_mvcc, then the lock file, then .__rustdb_internal, then its record. A 10.x root that was copied, moved or restored converts too but keeps the binding of its old place; like every copied root, it must be aligned (alignStoppedStorageRoot) before a start serves it. Alignment refuses a converted root that holds auth users or authority history. A start on a copied 10.x root that holds them therefore converts it, removes its 10.x entries and is refused, and no start serves the result: do not start this release on such a root. The root audit shows it before a start, as bindingState: 'foreign' with AUTH_USERS_PRESENT or RESOURCE_FENCES_PRESENT among its reasons. No authentication or permission migration runs during startup, and this release contains no authentication-schema conversion.

smartdb folders. A start also converts a folder that LocalSmartDb of @push.rocks/smartdb wrote; the 2.18.1 and 5.7.1 releases were observed, and both write one format. A start tells it from a 10.x root by its top-level names and kinds alone: .__rustdb_internal is a directory that holds a regular file provider-identity.json, there is no .__rustdb_mvcc, and every other top-level entry is a directory whose name does not begin with a dot, one database folder each. It then takes the folder's own lock, .__rustdb_internal/storage-owner.lock, which a running smartdb engine holds, before it checks or reads anything else, so a folder that a running engine holds is refused with BUSY. The folder must hold exactly the entries that smartdb writes, each where smartdb writes it: every database folder one or more collection folders, perhaps smartdb's .__rustdb_transaction_publication.json, and nothing else, every collection folder its data.rdb, wal.rdb and indexes.json and perhaps a keydir.hint, and .__rustdb_internal the provider's files and fence locks and perhaps transaction-commits, which holds only directories named by 64 lowercase hexadecimal digits. A missing entry, or any other, is refused with no code. What lies below such a directory, and the publication files, are the bookkeeping of smartdb's transactions: a start takes its directories and regular files as they are, whatever their names, never reads them, and refuses no combination of them. A transaction smartdb acknowledged is converted. An aborted one left nothing. One interrupted during its commit is converted whole or not at all, exactly as smartdb's own next start would show it. smartdb's leftover commit files are removed with the rest of its folder. A folder that only smartdb 2.10.x wrote has no .__rustdb_internal, so no lock that would show a running engine, and is no such folder: a start refuses it with no code and changes nothing. Start it once with smartdb 2.18.1, stop it cleanly, and then start this release. Under the default policy a folder without database folders converts into a new, empty database; 'existing-only' refuses it, as it refuses an empty directory, before it takes the folder's lock. Every entry must be a directory or a regular file of the starting user with one link and on the storage path's file system, which its owner reads and, a directory, writes and searches; group and other read and search bits, which a service under umask 0022 leaves, are accepted, and anything else, a link, a group or other write bit, setuid, setgid or sticky, is refused with no code. An existing storage path must be a directory of the starting user with mode 0700. A start refuses any other and changes nothing. A service that created its smartdb folder itself with a wider mode sets it to 0700 before its first start of this release. Nothing is ever chmodded: the conversion reads the folder without changing its data or its modes, and its entries disappear once the converted root is verified. The converted root holds what smartdb served on its own reopen: each collection's documents byte for byte, field order and _id as stored, its indexes as indexes.json defines them, rebuilt from the documents. indexes.json is read in the canonical Extended JSON that smartdb 2.18.1 and later write and in the plain form of 2.10.x and 2.16.0, where a plain integer inside the int32 range stands for that int32; any other plain number is refused with UNSUPPORTED. keydir.hint is never read, whatever its size. A collection's natural order is the order of its records in the folder, each document at its first record after its last delete: an update keeps a document's place, and a document deleted and inserted again comes last. A committed transaction rewrote every collection of its database, so there the order is the one smartdb wrote the records in, not the order of the inserts. A record that a crash left incomplete at the very end of data.rdb or wal.rdb, and a log record at the log's end whose checksum fails, are dropped, as smartdb drops them; a record whose length runs past the end of its file cannot be told from such a torn tail, so it is dropped too, with everything after it. Log operations apply whether or not their commit records follow them; a commit record names an earlier operation of its log that no earlier commit record names, or the folder is refused with STATE_CORRUPT. After the conversion this release's own _id equality applies: smartdb gave the string "42" and the number 42 one key and refused the second insert, and this release takes both. One collection name has a meaning of its own: this release, like smartdb 5.7.1, keeps a database's views as the documents of its system.views collection (see Restricted views). A system.views that this release admits converts with its documents byte for byte and is served as the database's view catalog, whichever smartdb release wrote the folder: each document is a view, and the collection itself is reserved from direct reads and writes, as on any root. It is admitted when every document holds exactly _id, viewOn and pipeline; _id is the string <database>.<view> of its own database; the view's name and viewOn are collection names that neither begin with system. nor hold $; pipeline is exactly [{ $match: { $expr: { $eq: [1, 0] } } }] with the int32 values 1 and 0; no view has the name of a collection of the database; the collection holds at most 1,024 documents of at most 1 MiB together; and its only index is _id_. An empty system.views is admitted, and so is a view whose source does not exist. smartdb 2.18.1 served system.views as an ordinary collection, so after the conversion its documents are no longer read or written under that name. A folder that holds a system.views this release does not admit, judged by the documents the collection keeps, is refused with UNSUPPORTED before anything is written and left as it was: start the folder with smartdb 2.18.1, rename or drop that collection, stop it cleanly, and then start this release. A folder at the place that its provider-identity.json records keeps its provider and is served at once; a copied, moved or restored folder converts too but keeps the binding of its old place, and like every copied root must be aligned (alignStoppedStorageRoot) before a start serves it. In 11.1.0 the root audit (inspectStoppedStorageRoot) answers a smartdb folder as unavailable with UNSUPPORTED, whether or not a running engine holds its lock, which it does not take, and the string and namespace inspections, alignment, relocation and rehearsal refuse it with UNSUPPORTED; none of them changes anything. Start a server on the folder to convert it.

A stopped root that a 10.x release wrote can be read before a start converts it. The stopped operations that only read open it read-only, under its own names and its own lock as a 10.x release's stopped operations did, and change nothing in it: the root audit (inspectStoppedStorageRoot), the string and namespace inspections (inspectOfflineStringValue, inspectOfflinePhysicalNamespaces) and data validation (--validate-data), which runs the namespace inspection. Alignment, relocation and rehearsal refuse a 10.x root with UNSUPPORTED: start a server on it to convert it, then copy and align the converted root. Every stopped operation refuses a path on which a conversion is in progress, one that holds .nosqldb-conversion; the root audit answers it as unavailable with UNSUPPORTED, and the string and namespace inspections and data validation reject it with UNSUPPORTED. A start on that path resumes the conversion and finishes it. A path that holds a 10.x name beside this release's entries without .nosqldb-conversion is no 10.x root: as a start refuses it with STATE_CORRUPT, the root audit answers it as unavailable with STATE_CORRUPT, and the string and namespace inspections, data validation, alignment, relocation and rehearsal reject it with STATE_CORRUPT. A path that holds a 10.x name beside a top-level entry of a later release, or beside this release's control directory holding a feature of a later release, which a start refuses with UNKNOWN_ROOT, every stopped operation refuses with UNSUPPORTED: the root audit answers it as unavailable with UNSUPPORTED.

import { NoSqlDbServer } from '@lossless.org/nosqldb';

// TCP mode (default)
const server = new NoSqlDbServer({
  port: 0,                  // Default: 27017; 0 requests an OS-assigned port
  host: '127.0.0.1',        // Default: 127.0.0.1
  storage: 'memory',        // 'memory' or 'file' (default: 'memory')
  storagePath: './data',     // Required when storage is 'file'
});
const startupController = new AbortController();
await server.start({
  signal: startupController.signal,
  timeoutMs: 30_000,
});
console.log(server.port); // Actual bound port while running
console.log(server.getConnectionUri()); // mongodb://127.0.0.1:<actual-port>

// Unix socket mode — no port conflicts!
const server = new NoSqlDbServer({
  socketPath: '/tmp/nosqldb.sock',
  storage: 'file',
  storagePath: './data',
});

// Bounded in-memory oplog retention
const server = new NoSqlDbServer({
  port: 27017,
  oplog: {
    maxEntries: 50000,            // Default: 10000
    maxBytes: 128 * 1024 * 1024,  // Default: 8 MiB
  },
});

// No oplog: writes skip the history copy, and the oplog methods reject
const lean = new NoSqlDbServer({
  socketPath: '/tmp/nosqldb.sock',
  oplog: { enabled: false },
});

// The store's memory budget, which its node cache and write buffer share:
// 4 MiB to 1 TiB, refused outside that range and never clamped
// (default: computed from the memory the process may use)
const budgeted = new NoSqlDbServer({
  storage: 'file',
  storagePath: './data',
  storageBudgetBytes: 256 * 1024 * 1024,
});

// TLS transport for TCP mode
const tlsServer = new NoSqlDbServer({
  port: 27017,
  tls: {
    enabled: true,
    certPath: './certs/server.pem',
    keyPath: './certs/server.key',
    // caPath: './certs/client-ca.pem',
    // requireClientCert: true, // Enables mTLS client certificate checks
  },
});

// SCRAM-SHA-256 authentication
const secureServer = new NoSqlDbServer({
  port: 27017,
  auth: {
    enabled: true,
    users: [
      {
        username: 'root',
        password: 'change-me',
        database: 'admin',
        roles: ['root'],
      },
    ],
  },
});

When auth.enabled is true, protected commands require successful SCRAM-SHA-256 authentication through the official MongoDB driver:

const client = new MongoClient('mongodb://root:change-me@127.0.0.1:27017/admin?authSource=admin', {
  directConnection: true,
});
await client.connect();

TLS is available for TCP listeners. getConnectionUri() includes ?tls=true when TLS is enabled; pass the trusted CA to the MongoDB driver with tlsCAFile, ca, or secureContext.

Authentication verifies SCRAM credentials, denies unauthenticated commands, and enforces command-level built-in roles for supported operations. connectionStatus reports the authenticated users and roles for the current socket.

Supported built-in role names are root, read, readWrite, dbAdmin, userAdmin, clusterMonitor, readAnyDatabase, readWriteAnyDatabase, dbAdminAnyDatabase and userAdminAnyDatabase. Native auth records contain derived SCRAM credentials, protected decoy material, the immutable work factor, principal identities, generations and bounded grant replay state. Plaintext passwords are not persisted. auth.scramIterations must match the stored profile on every reopen. Historical or malformed auth records fail closed.

Every authenticated command resolves the current principal and generation. Password and role changes invalidate old sockets. Deleting and recreating a username creates a different principal; an old connection cannot inherit its authority. Auth changes and the data/control records they protect share native atomic publications.

Single-node transactions pin one committed root across namespaces and retain changed documents, rather than copying a database. The snapshot is pinned by the transaction's first statement. Supported reads and writes see the transaction snapshot plus its staged changes. Commit publishes atomically; abort discards staged changes.

Write conflicts are implemented to be MongoDB compatible: the first statement to write a document or a unique index key wins, and the later writer is told at its own statement.

  • A transaction statement that inserts, updates, replaces, deletes or upserts a document, or claims or releases a unique index key, that another open transaction has already written fails with WriteConflict (112) and TransientTransactionError. So does a write to a document or key that was published after the transaction's snapshot. The failed statement aborts the transaction at once and releases everything it wrote: later statements and commitTransaction report NoSuchTransaction (251), also labelled TransientTransactionError. The transaction that wrote first keeps its documents and commits. The driver's withTransaction retries the loser on a fresh snapshot, and each attempt can lose again: no priority, ageing or wait rule favours a retrying transaction. So a transaction that writes a hot document and then does more work makes progress against clients looping short transactions on it only by its odds of winning each attempt, with no bound on its attempts or its time. Those per-attempt odds are lower than MongoDB's, as observed by runtime testing against MongoDB (mongo:7.0, 7.0.39): MongoDB releases a committing transaction's documents before it acknowledges the commit, while NoSQLDB acknowledges a commit as it releases them, after the commit is durable. On a host whose durable sync is slower, the odds fall further.
  • Reads never conflict: they see the snapshot, not another transaction's uncommitted writes. A statement that writes nothing (a filter that matches nothing in the snapshot, an update that leaves the document unchanged) does not conflict either.
  • A write outside a transaction that meets an open transaction's uncommitted write waits until that transaction commits or aborts, then runs on the new state: after a commit it applies on top of the transaction's write, and a unique key the transaction claimed becomes DuplicateKey (11000). The wait is bounded by maxTimeMS, which ends it with MaxTimeMSExpired (50) and nothing written, and holds no write worker, so the transaction can always finish. Writes to other documents are not delayed. A multi-document write outside a transaction stays all-or-nothing: it publishes nothing until it can publish every document.
  • TTL expiry is not a contending writer. It leaves out the documents an open transaction has written, deletes the other expired documents of the index and moves past the held ones. A later pass over the index expires those still expired once the transaction has ended. It never waits for a transaction and never aborts one, whatever its age. MongoDB's TTL expiry, as observed by runtime testing, instead retries a conflicting delete until the transaction ends and leaves aborting it to the transaction lifetime limit.
  • A key already visible in the transaction's snapshot is not a conflict: inserting it fails the statement with DuplicateKey (11000), as outside a transaction. A collection created inside a transaction has no stored identity until commit, so two transactions creating the same collection both succeed their statements and the second commit reports WriteConflict (112), implemented to be MongoDB compatible.

Write intents live in memory only; no transaction survives a restart, so there is nothing to recover.

Commit/abort outcomes and successful retryable statement results survive file storage restart. Duplicate commits replay their outcome; abort after commit reports TransactionCommitted (256), commit after abort reports NoSuchTransaction (251), and older transaction numbers report TransactionTooOld (225). Unsupported transactional DDL, auth mutations and $out/$merge reject before transaction admission or mutation.

Outside a transaction, createIndexes for already installed, identical indexes completes without waiting for any transaction or publishing a new storage position. A repeated physical create preserves NamespaceExists (48) under the same shared admission. Both paths validate current authorization and catalog options. A new collection or a changed index catalog waits only for the transactions that named that collection, and revalidates under its exclusive collection gate before any publication (see "Database waits" below).

Durable Committed Writes

NoSqlDbServer.getCommittedWrites() and the database-scoped wire command getCommittedWrites read the same native commit journal on memory and file storage. Each publication receives its stamp at the existing native commit boundary, not when a transaction starts or a document is inserted. The journal record and the publication share one atomic storage batch. A pending or aborted transaction never exposes its staged writes; retrying an acknowledged commit does not emit a second write. File storage retains the stamps and timeline identity across restarts.

const tail = await server.getCommittedWrites({ db: 'books' });

const page = await client.db('books').command({
  getCommittedWrites: 1,
  after: tail.cursor,
  limit: 128,
  maximumBytes: 1024 * 1024,
});

Omitting after initializes at the current head and returns no historical writes. A cursor is { timeline: string, sequence: string }: its sequence is an exact unsigned decimal string, never a JavaScript number or wall-clock time. The public timeline is an opaque cursor epoch derived from the native timeline and the durable provider identity, not the native lineage identifier itself. It remains stable across restarts. A rehearsal copy or independently aligned fork gets a different provider identity and therefore a different cursor epoch; resuming another root's cursor returns an explicit gap even at an equal sequence. The native timeline, retained journal bytes and native stamps are preserved exactly by copying. The single stopped binding publication is not fabricated as an application write; subsequent serving publications start retained coverage after that boundary. A move preserving the provider identity preserves the cursor epoch, but any uncovered native publication still reports a gap.

A start converts a root that a 10.x release wrote, a 10.8.1 root included, into this release's format (see Storage format migration under File startup admission). The converted root keeps the 10.x root's committed position as its head and carries no history of writes committed before the conversion: a cursor before that head gets gap: true, and publications after the conversion are retained from that head on. No earlier release reads the converted root. A deployment pinned to 10.8.1 is unaffected until a start of this release opens its root; keep a stopped copy of the root before that if you may need to go back. For a root that holds auth users, a restored copy is no way back (see Storage format migration under File startup admission).

Pages are exclusive of after and follow commit order. Their cursor advances over examined publications even when none belongs to the requested database; use it for the next request while hasMore is true. Do not infer continuity from an empty writes array.

The page contains writes, cursor, head, oldest, gap and hasMore. oldest identifies the first retained publication, or the head when none remain. Each write contains its stamp, committedAtMs, commit-time namespaces (database and collection) and an optional receipt with the session UUID, exact decimal transaction number and optional retryable statement number. Multi-statement retryable batches use a transaction-scoped receipt with a null statement number. Legacy non-UUID logical session identifiers have no receipt. Only namespaces in the requested database are returned. Document identifiers, document contents, previous values and application routing fields are not recorded. This is commit evidence, not a MongoDB change stream or a replacement for an application's transactional routing log. The older in-memory debug OpLog remains a separate API and is not a durable delivery cursor.

Retention is bounded by 15 minutes and the newest 65,536 native publications. Compaction is incremental and bounded. A cursor older than retained coverage, from a different timeline, beyond the current head or crossing missing history produces gap: true; rebuild the dependent state and explicitly initialize a fresh tail rather than treating the page as continuous history. High publication rates can reach the count boundary before 15 minutes. An exact current-head cursor remains valid while idle, even after retention expires. The durable pruning-floor cursor also remains valid when every later publication is retained, including a tail initialized on a pre-journal root.

limit defaults to 128 and accepts 1–1,024 examined publications. maximumBytes defaults to 1 MiB and accepts 1 KiB–4 MiB; a record too large for the requested page is rejected rather than silently skipped. These reads use the current native publication snapshot and normal database read admission and authorization. Raw source scanning has a separate 16 MiB page-work budget, independent of namespace filtering. Database readers cannot inspect another database; allocation read grants deliberately cannot read the live commit journal. The Node API is trusted parent-process management and retains native database/publication exclusion. Cursor reads are not available inside a transaction.

Durable Database Publication Holds

On supported Linux and macOS filesystems, file-backed NoSQLDB can keep a replaced or deleted database closed after the local mutation is durable and until a downstream coordinator confirms its own fsync. Check the explicit health contract before using this flow:

const health = await server.getHealth();
if (
  health.publicationHoldVersion !== 1 ||
  !health.publicationHoldSupported ||
  health.publicationHoldRequiresExternalDrain
) {
  throw new Error('NoSQLDB publication holds are unavailable');
}

Set holdPublication: true on an exact fenced importDatabase() or deleteDatabaseTenant() operation. The result contains a held resourceFence with a one-time publicationCapability. Treat that capability as a secret: do not log it or persist it outside protected control-plane state. After downstream state is durable, submit the exact receipt to commitDatabasePublication():

import type { INoSqlDbHeldPublicationReceipt } from '@lossless.org/nosqldb';

const held = await server.importDatabase({
  databaseName: 'tenant_a',
  source: snapshot,
  username: 'tenant_a_user',
  fence: {
    version: 1,
    scopeId: 'corestore-node-1.tenant-a',
    token: 42,
    mutationId: 'restore-42',
    payloadSha256: controlPlanePayloadSha256,
  },
  holdPublication: true,
});

const receipt = held.resourceFence as INoSqlDbHeldPublicationReceipt;
const heldExport = await server.exportHeldDatabase({
  databaseName: 'tenant_a',
  resourceFence: receipt,
});
await verifyAndPersistDownstreamSnapshot(heldExport);
await persistAndFsyncDownstreamState(receipt);
const released = await server.commitDatabasePublication({
  databaseName: 'tenant_a',
  resourceFence: receipt,
});

The held barrier survives restart and blocks wire commands, transactions and index restoration for that database while unrelated databases remain available. A blocked wire command fails with ConflictingOperationInProgress (code 117, no error label) naming EFENCE_PUBLICATION_HELD. exportHeldDatabase() is a bounded management-only read of one pinned committed catalog, auth and fence root. It requires the exact still-held receipt and capability, returns the same canonical Extended JSON shape as exportDatabase(), and does not release the barrier. Ordinary export and wire access remain closed; native TTL maintenance cannot expire held documents. A wrong, stale or already released receipt fails closed. Commit is exact and idempotent. A successful commit removes the raw capability from durable state and retains only protected verification material. Provider/root identity and bounded validation of native fencing records make copied, incomplete or corrupt allocation state fail closed. A higher fencing token compacts resolved older receipts, so long-lived coordinators do not exhaust receipt capacity.

Durable database publication holds and allocation fencing use native file storage on the supported Linux and macOS filesystems.

Coordinators that do not yet have their own durable record can call getDatabaseResourceFenceState({ databaseName }) before creating it. File-backed NoSQLDB returns the durable scope, highest token, current publication phase, and allocation lifecycle/identity when the name is allocation-managed, or null for an unfenced name. Startup first validates both directions of every allocation's fencing/auth binding. The inspection never returns mutation IDs, mutation payload digests, publication receipts, or publication capabilities. Treat any active phase, identity mismatch, corrupt state, unsupported storage, or unsafe token as a hard stop rather than creating coordinator state.

Authoritative Database Allocation

allocateDatabaseTenant() is the create-only control-plane API for permanently allocation-managed database names. It is available only when allocationFencingVersion === 1, allocationFencingSupported === true, and allocationFencingRequiresDrain === true. The request requires literal expectedAbsent: true, an INoSqlDbResourceFence, and the tenant database, username, and password. roles is optional and defaults to ['readWrite', 'dbAdmin']. Under native ownership and database/control admission, NoSQLDB proves catalog and principal absence, then commits the catalog, principal, complete auth allocation binding and fencing receipt at one position. It returns an INoSqlDbAllocateDatabaseTenantResult; its .allocation field is the durable INoSqlDbDatabaseAllocationIdentity. Exact fence replay returns the same allocation ID, generation, principal ID, and receipt digests.

Pass the returned allocation identity to every later ensureDatabaseTenant(), importDatabase(), and deleteDatabaseTenant() request for that name. Managed import and delete requests also require the exact allocation username; the identity alone is insufficient. Each distinct managed mutation requires a strictly newer fence token; the current token is accepted only for exact receipt replay or continuation, and older receipts become explicitly stale after advancement. Held publication receipts carry the allocation into commitDatabasePublication(). Managed requests without it and unmanaged requests with it fail closed. Deprovision drains affected runtime authority, then atomically retires the catalog, exact principal, grants and auth binding with a permanent deprovisioned fencing tombstone. A later allocation requires a newer fence token, increments generation, and receives a fresh allocation and principal identity. Wire data access is available only while active and only to the current allocation principal; any other principal is refused with Unauthorized (code 13), and while the allocation is not active every wire command on the database is refused with Unauthorized naming EALLOCATION_DEPROVISIONED. The database/user ownership-changing wire commands dropDatabase, createUser, updateUser, dropUser, grantRolesToUser and revokeRolesFromUser are refused with Unauthorized (code 13), the MongoDB-compatible refusal of a user without the privilege.

Allocation bindings are native records, excluded from logical database exports and never accepted from import payloads. Startup and stopped-storage inspection require the complete matching fence and auth binding even when no read grant was ever issued. Missing bindings are rejected without reconstruction. This includes allocated roots produced by experimental native versions that did not create the complete binding. This release provides no automatic conversion or downgrade path; never remove or edit private metadata to force admission.

Read-Only Tenant Attestation

attestDatabaseTenant() verifies that an existing database is exclusively owned by the exact username, role set, and candidate password. It returns only { matches: boolean }; it never returns a URI, credential, principal identity, or mismatch detail. Missing databases or users, conflicting ownership, role differences, allocation-principal drift, and password mismatch all return false through the same constant-work credential path.

Attestation pins one native snapshot under the database's read-only admission. Its auth, allocation and data observations share that committed root. Attestation does not refresh process caches or call tenant ensure, create, rotate, import, delete, fence repair, publication-epoch observation, or cache invalidation. Corrupt, held, deprovisioned, disabled, or busy provider state fails as an operational error instead of returning a match result.

const result = await server.attestDatabaseTenant({
  databaseName: 'tenant_a',
  username: 'tenant_a_user',
  roles: ['readWrite'],
  password: candidatePassword,
});

if (!result.matches) {
  throw new Error('Existing tenant authority does not match');
}

Allocation-Bound Read Access Grants

Scratch diagnostics can use issueDatabaseAllocationReadAccessGrant() to create one auxiliary read principal for an exact active allocation. The capability is advertised only when databaseAllocationReadAccessGrantVersion === 1 and databaseAllocationReadAccessGrantSupported === true; this requires file storage, enabled authentication and ready allocation fencing. Native MVCC commits grant principals, authentication and allocation bindings atomically in the storage root. The caller supplies databaseName, a bounded grantId, a bounded unique username, password, and the complete INoSqlDbDatabaseAllocationIdentity. Roles and expiry are not caller-controlled. NoSQLDB always assigns exactly ['read'] and a fixed, non-renewing 12-minute expiry.

const grant = await server.issueDatabaseAllocationReadAccessGrant({
  databaseName: allocation.databaseName,
  grantId: 'scratch-diagnostic-2026-08-08',
  username: 'scratch_diagnostic_reader',
  password: generatedSecret,
  allocation,
});

const diagnosticClient = new MongoClient(grant.mongodbUri!, {
  directConnection: true,
});
await diagnosticClient.connect();

Exact issue replay validates the candidate password against the persisted SCRAM credential and returns the original principal generation, issuedAt, and expiresAt only while that grant remains active and unexpired; it never renews the grant. Expired or revoked grantId values are rejected. NoSQLDB permits at most one active read grant per allocation. Revoked and expired grantId values remain in a per-allocation anti-replay set capped at 1,024 entries, with no eviction while the allocation exists. Capacity exhaustion fails closed. Allocation deprovision uses a two-phase fail-closed sequence: any active grant is first durably retired and tombstoned, then the database is durably deleted, and finally the exact owner plus that allocation's replay state are removed in one auth rewrite. Interruption before completion leaves the grant retired rather than usable.

Native deprovision also durably retires an active grant before draining its transaction, then publishes database deletion, exact owner cleanup and its fencing receipt together. Bounded periodic cleanup retires expired grant principals and cancels their sessions and cursors.

Grant-specific rejections from the issue and revoke methods are normalized automatically as NoSqlDbAllocationReadAccessGrantError with stable EGRANT_INVALID_REQUEST, EGRANT_CREDENTIAL_MISMATCH, EGRANT_CONFLICT, EGRANT_REPLAY_CAPACITY, or EGRANT_STATE_UNAVAILABLE codes. The companion exports TNoSqlDbAllocationReadAccessGrantErrorCode, noSqlDbAllocationReadAccessGrantErrorCodes, and normalizeNoSqlDbAllocationReadAccessGrantError(error, code) support type-safe handling and explicit normalization of structured bridge errors. Allocation and resource-fence failures retain their existing NoSqlDbResourceFenceError codes. Callers should classify errors by these exported classes and codes rather than by message text.

Active allocation read grants never survive a NoSQLDB engine restart or a new attachment to the durable auth store. Startup durably retires and tombstones them before accepting work; diagnostics must issue a new grantId, username, and credential after restart.

Grant principals can read only their exact allocation database. Reads, collection/index metadata, and read-only transactions are supported. Writes, DDL, user/admin operations, writing aggregates ($out and $merge), cross-database targets, and allocation ownership changes are denied. Revocation, expiry cleanup, and allocation deprovision abort matching logical sessions and transactions, release retained database permits, and remove matching cursors. Stale sockets fail their next command.

revokeDatabaseAllocationReadAccessGrant() takes databaseName, grantId, and the complete allocation identity, returns { revoked: true } without a secret, and is idempotent. Revoke-before-issue records anti-replay state only while a read permit proves that exact allocation is current. A delayed revoke after deprovision returns success without recreating auth metadata.

Native exportDatabase() pins a committed database root while ordinary writes continue. Catalog, auth and publication checks use the same root; DDL remains excluded by the database gate. Callers can lower maxEncodedBytes, maxCollections, maxDocuments and maxIndexes; the server enforces these bounds while scanning. Results use canonical MongoDB Extended JSON, preserving every BSON type and numeric width.

excludeDocumentsFrom names collections of the database whose documents the export leaves out, for a backup that must not carry them, such as a workload-log ledger. Each named collection is still exported with its options and all its indexes, with an empty documents list. Its documents are never read and count against none of the limits, so a ledger that alone exceeds maxEncodedBytes or maxDocuments does not fail the export. Importing the export gives a database whose excluded collections exist, empty, with their options and indexes. A name that is not a collection of the database is refused with a message that names it, and so is a system collection's name, whether or not the database has that collection: leaving out system.views would drop the database's view definitions, deny-all views included, on restore. A value that is not a list, a list entry that is not a string, an empty name and a name given twice are refused too. Without the field, or with an empty list, the export is unchanged. exportHeldDatabase() takes the field with the same meaning. Both export requests refuse a top-level field they do not take, as the content digest does, so a misspelled excludeDocumentsFrom is refused instead of exporting every document, and exportDatabase() refuses resourceFence, which only exportHeldDatabase() takes.

const backup = await server.exportDatabase({
  databaseName: 'myapp',
  limits: { maxEncodedBytes: 16 * 1024 * 1024 },
  excludeDocumentsFrom: ['workload_logs'],
});
// backup.collections lists workload_logs with its options and indexes
// and documents: [].

importDatabase() validates canonical or relaxed Extended JSON, builds a private native catalog in bounded commits, then atomically publishes the complete catalog with its fencing receipt. Incomplete staging is never visible to data readers. Cancellation or restart retires unpublished staging through the owning native logic. Index aliases, empty namespaces and validated system.views definitions are preserved; view IDs are rebound when the database name changes. Keep exported objects as JSON values rather than coercing BSON numeric wrappers into JavaScript numbers. Imported indexes retain their validated source catalog specification, including BSON field order and an absent v field in legacy exports. These APIs transfer database contents; they do not convert storage files.

getDatabaseContentDigest() computes a bounded, database-name-independent SHA-256 over collection names, exact BSON document bytes, and persisted index specifications. Collection, document, and index enumeration order is normalized; BSON field order and compound-index key order remain significant. The physical system.views catalog is included, with only its already-validated deterministic database prefix removed from _id for hashing so a cross-name import retains the same digest. INoSqlDbGetDatabaseContentDigestInput accepts databaseName and optional INoSqlDbDatabaseContentDigestLimits fields maxScannedBsonBytes, maxCollections, maxDocuments, and maxIndexes. Callers may lower the server ceilings of 96 MiB scanned BSON, 10,000 collections, 1,000,000 documents, and 100,000 indexes. The INoSqlDbDatabaseContentDigest result reports format smartdb.database.content-digest.v1, algorithm sha256, the lowercase digest, and exact scan counters.

NoSqlDbServer.start() and the lifecycle-critical health, fence-state, digest, export, import, publication-commit, tenant-allocation, tenant-attestation, tenant-ensure, tenant-delete, read-grant issue, and read-grant revoke methods accept optional INoSqlDbManagementOperationOptions with signal?: AbortSignal and timeoutMs?: number. timeoutMs, when provided, must be a positive safe integer no greater than 2,147,483,647. Startup applies both cancellation and one deadline across sidecar spawn, native admission, recovery and database readiness. Cancellation and deadlines are fail-stop once the sidecar is owned: NoSQLDB attempts to terminate the Rust engine before rejecting. Once termination is confirmed, the operation cannot continue after the caller releases ownership. If termination itself fails, the rejection retains bridge ownership and the service owner must retry stop() until it succeeds. After cancellation or deadline termination, restart NoSQLDB before accepting more traffic.

const controller = new AbortController();
const exported = await server.exportDatabase(
  { databaseName: 'myapp' },
  { signal: controller.signal, timeoutMs: 5 * 60_000 },
);

Basic user management commands are available for authenticated users with root or userAdmin privileges:

await client.db('admin').command({
  createUser: 'reader',
  pwd: 'readpass',
  roles: [{ role: 'read', db: 'myapp' }],
});

await client.db('admin').command({ usersInfo: 'reader' });

Methods & Properties

Method / Property Type Description
start(options?) Promise<void> Start under one optional cancellation/deadline budget; join partial sidecars before rejection or retain ownership for stop() retry if termination fails
stop() Promise<INoSqlDbServerStopResult> Wait for active startup work, then stop the server and confirm Rust bridge cleanup, including partial startup cleanup; resolves with how the engine ended
getConnectionUri() string Get the active mongodb:// URI; before start and after stop, port 0 remains unresolved
running boolean Whether the server is currently running
port number Actual bound port while running; otherwise the configured port (TCP mode)
host string Configured host (TCP mode)
socketPath string | undefined Socket path (socket mode)
processId number | undefined Operating-system pid of the spawned Rust engine while it runs as a child process (for crash and kill tests)
getMetrics() Promise<INoSqlDbMetrics> Server metrics (db/collection counts, sessions, transactions, auth, uptime)
getCommittedWrites(params) Promise<INoSqlDbCommittedWritesPage> Read database-scoped durable commit evidence, initializing a tail or resuming an exact timeline/sequence cursor
getOpLog(params?) Promise<IOpLogResult> Query oplog entries with optional filters (rejects when oplog.enabled is false, as do the next two)
getOpLogStats() Promise<IOpLogStats> Aggregate oplog statistics
revertToSeq(seq, dryRun?) Promise<IRevertResult> Revert to a specific oplog sequence (must be within retained oplog history)
getCollections(db?) Promise<ICollectionInfo[]> List all collections with counts
getDocuments(db, coll, limit?, skip?) Promise<IDocumentsResult> Browse documents with pagination
getHealth(options?) Promise<INoSqlDbHealth> Read readiness and explicit publication-hold capability fields with optional fail-stop cancellation/deadline ownership
allocateDatabaseTenant(params, options?) Promise<INoSqlDbAllocateDatabaseTenantResult> Authoritatively allocate an absent file-backed tenant and return its durable allocation identity; supports fail-stop cancellation and deadlines
attestDatabaseTenant(params, options?) Promise<INoSqlDbAttestDatabaseTenantResult> Verify exact existing tenant ownership, roles, and candidate credential without mutating provider, auth, fence, or runtime cache state
issueDatabaseAllocationReadAccessGrant(params, options?) Promise<INoSqlDbIssueDatabaseAllocationReadAccessGrantResult> Issue or exactly replay one fixed 12-minute read grant bound to the complete active allocation identity
revokeDatabaseAllocationReadAccessGrant(params, options?) Promise<INoSqlDbRevokeDatabaseAllocationReadAccessGrantResult> Idempotently revoke/tombstone one exact allocation read grant and invalidate its live runtime state
ensureDatabaseTenant(params, options?) Promise<INoSqlDbEnsureDatabaseTenantResult> Idempotently ensure an exact fenced tenant; supports fail-stop cancellation and deadlines
getDatabaseResourceFenceState(params, options?) Promise<INoSqlDbDatabaseResourceFenceState | null> Safely inspect a file-backed database fence high-water mark and publication phase without exposing receipts or capability material; supports fail-stop cancellation and deadlines
getDatabaseContentDigest(params, options?) Promise<INoSqlDbDatabaseContentDigest> Compute a bounded, database-name-independent digest over exact BSON documents and persisted index specifications; supports fail-stop cancellation and deadlines
exportDatabase(params, options?) Promise<INoSqlDbDatabaseExport> Export one database as lossless canonical MongoDB Extended JSON; excludeDocumentsFrom leaves out the documents of named collections, unread; supports fail-stop cancellation and deadlines
exportHeldDatabase(params, options?) Promise<INoSqlDbDatabaseExport> Export one exact still-held database under its publication receipt without opening wire access or releasing the hold; accepts ordinary export limits, excludeDocumentsFrom, cancellation and deadlines
importDatabase(params, options?) Promise<INoSqlDbImportDatabaseResult> Durably replace one database from canonical or relaxed MongoDB Extended JSON, optionally leaving publication held; supports fail-stop cancellation and deadlines
deleteDatabaseTenant(params, options?) Promise<INoSqlDbDeleteDatabaseTenantResult> Durably delete an exact tenant database/user, optionally leaving publication held; supports fail-stop cancellation and deadlines
commitDatabasePublication(params, options?) Promise<TNoSqlDbCommitDatabasePublicationResult> Idempotently release an exact held publication receipt; supports fail-stop cancellation and deadlines

stop() resolves with an INoSqlDbServerStopResult that says how the engine ended: stopRequestSent, whether a stop request was sent to a running engine; stopAcknowledged, whether the engine acknowledged it; forcedByServer, whether the server had to signal or kill the engine to end it; and exitCode and exitSignal, as the engine process's exit reported them, each null where there was none. clean is true exactly when the request was sent and acknowledged, the server neither signalled nor killed the engine, and the engine exited with code 0 and no signal. After an acknowledged request, stop() closes the engine's management input and gives the engine noSqlDbServerStopGraceMs (10,000 ms) to exit by itself; only an engine that has not exited by then, or one that did not acknowledge, is ended by the server. A stop when no engine runs resolves with a result that says no request was sent, and concurrent callers share one result. stop() rejects only when the bridge's termination fails, as before. LocalNoSqlDb.stop() resolves with its server's result.

A start that meets a storage root, or a feature of one, from a later NoSQLDB release than the one starting refuses it before it writes anything there and creates no root in its place. start() of NoSqlDbServer and of LocalNoSqlDb then rejects with a NoSqlDbUnknownRootError, whose code is UNKNOWN_ROOT and whose message names that cause; its cause is the management error that carried the code. A caller tells this refusal apart from every other start failure by the code, never by the message.

NoSqlDbStorageRehearsal

NoSqlDbStorageRehearsal.createDestination(input, options?) creates an isolated, authenticated copy of an initialized, stopped file-storage root. It verifies the source's existing native owner, provider and authentication bindings while holding its existing outer and native file-root leases continuously. The source is read-only throughout: no initialization, recovery, migration, permission repair, journal retirement or identity editing occurs there. Successful creation leaves it independently restartable at its original path.

import { NoSqlDbServer, NoSqlDbStorageRehearsal } from '@lossless.org/nosqldb';

const binaryPath = '/opt/corestore/engines/nosqldb/rustdb_linux_amd64';
const operation = new NoSqlDbStorageRehearsal({ binaryPath });
const request = {
  sourceFolderPath: '/var/lib/corestore/authoritative',
  destinationFolderPath: '/var/lib/corestore/rehearsals/upgrade-1',
  operationId: 'corestore-upgrade-1',
};
const controller = new AbortController();
try {
  // Corestore has stopped the source and retains its maintenance exclusion.
  const receipt = await operation.createDestination(request, {
    timeoutMs: 30_000,
    signal: controller.signal,
  });
  const candidate = new NoSqlDbServer({
    binaryPath,
    storage: 'file',
    storagePath: receipt.destinationFolderPath,
    fileStorageStartup: 'current-format-only',
    storageRehearsalId: receipt.operationId,
    socketPath: '/run/corestore/private/rehearsal.sock',
    auth: {
      enabled: true,
      scramIterations: receipt.scramIterations,
    },
  });
  try {
    await candidate.start({ timeoutMs: 30_000 });
    const uri = candidate.getConnectionUri();
    // Connect SmartData with the preserved credentials. Prepare its models,
    // then run Corestore's versioned ts_migration modules against this URI.
    // Keep external services and application side effects isolated in Corestore.
  } finally {
    await candidate.stop();
  }
  // Retain the typed receipt through Corestore's normal persistence API.
} finally {
  await operation.stop();
}

The destination's parent and socket parent must already exist, be owned by the effective user and have mode 0700. The socket must be absent and outside the storage root. Source and destination paths must be absolute, canonical, distinct, non-nested paths on the same supported local filesystem/device. The destination must be absent on the first attempt. Neither request nor receipt contains a password. binaryPath selects exactly the caller-verified executable through Smartrust; omission retains normal engine discovery without environment changes.

The public types are INoSqlDbStorageRehearsalOptions, INoSqlDbStorageRehearsalInput, INoSqlDbStorageRehearsalReceipt and INoSqlDbStorageRehearsalPreservation. An optional expectedSourceProviderRootId pins a previously attested source provider ID; NoSQLDB always verifies the source's stored bindings even when this is omitted. Consumers do not inspect private identity files. Optional limits: { maximumEntries, maximumBytes } can lower the source scan limits.

The supported storage profile is native-mvcc-authenticated-v1; creation evidence uses smartdb.storage-rehearsal.receipt.v2. Rehearsal accepts current native authentication records only. The removed authMigration option and schema-2 worker conversion are not accepted.

State Rehearsal contract
Contents and catalogs Preserve exact BSON documents, indexes and aliases, empty namespaces, and internal databases including admin, local and config.
Authentication Preserve usernames, roles, salts, password verifiers, work factor, principal identities, generations and retained grant replay history.
Fencing and holds Preserve allocations, retired history, counters and exact receipt records. Held databases remain held; a rehearsal cannot release their production capabilities.
Sessions and outcomes Preserve durable retry and transaction outcomes, including retained and retiring session records.
Native history Preserve the source group/timeline and committed records. The destination adds one committed provider-binding record with isolated purpose; it never resets authority counters.

The isolated binding publication uses the sealed stopped-profile publisher, not a serving write. Its exact native recovery plan is durably recorded before publication and validated on retry. Every other record's bytes and version are preserved, with unchanged complete non-profile record count and fingerprint. Its authority admits no internal relocation or alignment workspace. An unrelated staged workspace rejects before native opening, append or recovery. This release neither rehearses a root that a 10.x release wrote nor finishes a rehearsal that a 10.x release began, or returns the receipt of one that it finished. A request whose source, destination or unfinished copy is such a root, in whole or in part, fails with UNSUPPORTED, and the retry of one whose 10.x roots starts have converted since fails with UNSUPPORTED or SOURCE_CHANGED; no such request changes either path or the operation workspace. Finish a 10.x rehearsal with that release before a start converts its source, or rehearse the converted root into another destination.

Active source owners, rehearsal-as-source, old or mixed layouts, unknown entries, unfinished import or fencing operations, active read grants, unfinished or quarantined indexes, invalid catalogs, corrupt auth/outcomes and storage requiring recovery are rejected before publication. Source symlinks, hardlinks, nested mounts, special objects, unsafe owners/modes and identity changes are rejected. Creation supports the qualified Linux local-filesystem set and writable local macOS APFS with native ownership and atomic publication. Other filesystems and operating systems are rejected before destination initialization.

Defaults and hard limits are 100,000 filesystem entries, 1 TiB of file bytes, depth 16, 4095-byte paths and 64 MiB of retained inventory metadata. The owning native catalog, index, auth, outcome and fencing validators also enforce their record and semantic work budgets. Exhausted bounds reject without truncation or reset. LSNs and fencing high-water marks in the receipt are decimal strings, preserving their complete unsigned 64-bit range. These are operation bounds, not total serving document limits.

Copying and hashing stream 64 KiB chunks. Complete source/destination validation compares the preserved record digest as well as physical identity and contents; this is not a constant-time snapshot or reflink.

storageRehearsalId?: string is supported by both INoSqlDbServerOptions and ILocalNoSqlDbOptions. It requires the exact recorded operation ID, file storage, fileStorageStartup: 'current-format-only', the recorded auth work factor, enabled authentication, no bootstrap users, and a private explicit Unix socket. LocalNoSqlDb also forwards auth?: INoSqlDbAuthOptions. Ordinary startup rejects a rehearsal root. Native startup verifies the completion seal and new physical provider binding and preserved native auth profile before recovery or auth initialization. Health reports storagePurpose: 'rehearsal' and storageRehearsalId; production fencing, allocation and publication capabilities are disabled. Authenticated ordinary SmartData reads, writes and transactions remain available on unheld databases. Corestore owns application/outbound-service isolation. There is no promotion API: changing startup flags cannot turn this destination into production authority.

One instance admits one operation at a time; independent instances can select different executables and sources. Native leases exclude active source owners and conflicting copies. timeoutMs defaults to one hour and is capped at 24 hours; the budget includes engine lookup/spawn and the native operation. Abort, deadline and stop() terminate the owned sidecar; methods settle after confirmed exit. Cleanup can outlast the deadline. If termination cannot be confirmed, the instance retains its bridge and stop() remains retryable. Native work also checks parent process death. Source stability relies on the native lease plus the caller's maintenance exclusion from manual edits and restarts between attempts.

Publication uses a private sibling operation workspace, a bounded durable journal, a distinct native destination lease and an atomic exclusive rename. The renamed root cannot start until its completion seal is durable. After interruption, retry the identical request, including paths, operation ID, expected provider ID and limits, using the same NoSQLDB release or a compatible newer release. A retry may use a different deadline. Keep the source stopped and unchanged until an unfinished copy completes; do not edit, remove or move either root or the operation workspace. Completed retries return the same creation receipt even after legitimate rehearsal writes or source restart. They verify the destination's identity and seal and re-establish publication durability; they do not attest to its later contents.

NoSqlDbStorageRehearsalError has fixed, secret-free messages and no raw nested cause. Codes are INVALID_REQUEST, UNSUPPORTED, BUSY, NOT_FOUND, DESTINATION_CONFLICT, IDENTITY_MISMATCH, STATE_CORRUPT, SOURCE_CHANGED, CAPACITY, CANCELLED, IO, RECOVERY_REQUIRED and BINARY_UNAVAILABLE. Uncertain command completion or cleanup reports RECOVERY_REQUIRED; an error does not imply an absent destination. Exact retry reconciles interrupted copies, renames and receipt writes without replacing unrelated paths. A changed source or tampered destination fails closed.

The receipt identifies both paths/providers, the destination device/inode, request and receipt hashes, storage profile, public auth work factor, preservation counts and explicit productionAuthority: false / promotionSupported: false. It exposes no usernames, principal IDs, per-file auth hashes, verifiers or publication secrets. It is integrity-checked creation evidence, not an installer signature. Source verification compares complete bytes, object identities, owners, link counts, modes, modification times and change times; ordinary read access times may change. Matching Linux and macOS amd64/arm64 engines and their checksum/provenance files ship in the same published NoSQLDB package's dist_rust/ directory; see the engine artifact section below.

LocalNoSqlDb

Zero-config wrapper around NoSqlDbServer. Uses Unix sockets and file-based persistence.

Constructor Options (ILocalNoSqlDbOptions)

import { LocalNoSqlDb } from '@lossless.org/nosqldb';

const db = new LocalNoSqlDb({
  folderPath: './data',                  // Required: data storage directory
  socketPath: '/tmp/custom.sock',        // Optional: custom socket (default: auto-generated)
  binaryPath: '/opt/spark/engines/nosqldb/rustdb_linux_amd64', // Optional: exact engine
  fileStorageStartup: 'current-format-only', // Optional; also the default
});

binaryPath has the same strict selection contract as NoSqlDbServer and is passed through unchanged on every start. Supply an absolute path because the underlying server and bridge are constructed by LocalNoSqlDb.start().

Methods & Properties

Method / Property Type Description
start() Promise<ILocalNoSqlDbConnectionInfo> Start and return connection info
stop() Promise<INoSqlDbServerStopResult> Stop the server; resolves with its server's stop result
getConnectionInfo() ILocalNoSqlDbConnectionInfo Get current connection info
getConnectionUri() string Get the connection URI
getServer() NoSqlDbServer Access the underlying server
running boolean Whether the server is running
LocalNoSqlDb.relocateStoppedStorageRoot(input, options?) Promise<ILocalNoSqlDbStoppedStorageRootRelocationReceipt> Atomically relocate one eligible stopped Linux or macOS file-storage root and retain a source receipt
LocalNoSqlDb.alignStoppedStorageRoot(input, options?) Promise<TLocalNoSqlDbStoppedStorageRootAlignmentResult> Adopt an eligible stopped copy as an independent provider
LocalNoSqlDb.abortStoppedStorageRootAlignment(input, options?) Promise<ILocalNoSqlDbStoppedStorageRootAlignmentAbortResult> Abort an exact incomplete alignment append
LocalNoSqlDb.inspectOfflineStringValue(input, options?) Promise<TLocalNoSqlDbOfflineStringValueInspectionResult> Read one exact top-level string value from stopped file storage without starting or mutating the engine
LocalNoSqlDb.inspectOfflinePhysicalNamespaces(input, options?) Promise<ILocalNoSqlDbOfflinePhysicalNamespaceInspectionResult> Validate stopped physical file storage and return only deterministic database and collection names

Stopped Storage-Root Relocation

relocateStoppedStorageRoot() moves an eligible stopped file-storage root without starting RustDb, binding a listener, running a TypeScript migration, initializing storage or auth, or performing WAL recovery. It supports Linux on the existing qualified local-filesystem set and macOS on writable local APFS volumes with ownership, persistent object identities, flock, and atomic exclusive/swap rename support. The source root, source parent, and destination parent must be trusted descriptor-safe objects on the same filesystem. The supplied source and destination must be absolute and resolve to distinct, non-root, non-nested physical paths, and the destination must be absent. macOS also rejects case-folded, Unicode-normalized, and ancestry aliases before creating relocation state. The trusted parents may differ.

import { LocalNoSqlDb } from '@lossless.org/nosqldb';

const receipt = await LocalNoSqlDb.relocateStoppedStorageRoot({
  sourceFolderPath: '/var/lib/myapp/harness-controller/nosqldb',
  destinationFolderPath: '/var/lib/myapp/hcon/nosqldb',
  relocationId: 'move-2026-08-17-1',
}, {
  timeoutMs: 30_000,
});

// Start only the destination. The source is now the retained receipt file.
const db = new LocalNoSqlDb({ folderPath: receipt.destinationFolderPath });
await db.start();

The input must be a plain object with exactly the own enumerable data fields sourceFolderPath, destinationFolderPath, and relocationId. Each supplied path must already be absolute, is normalized before sidecar creation, is limited to 1 through 4095 UTF-8 bytes, and cannot contain control characters. Longer destination paths are supported within that bound. relocationId is a 1 through 256-byte ASCII identifier beginning with an alphanumeric character and otherwise using only alphanumerics, ., _, :, @, or -. Management timeoutMs and signal use the same validation and one-deadline behavior as other NoSQLDB management operations. The native operation also checks its monotonic deadline and parent-process lifetime during admission and publication.

The returned exact receipt has this shape:

interface ILocalNoSqlDbStoppedStorageRootRelocationReceipt {
  format: 'smartdb.storage-root-relocation.receipt.v1';
  version: 1;
  relocationId: string;
  sourceFolderPath: string;
  destinationFolderPath: string;
  providerRootId: string;
  storageRootDevice: string; // canonical decimal u64
  storageRootInode: string;  // canonical decimal u64
  receiptSha256: string;
  sourceReceiptRetained: true;
}

On success, the destination directory is the original storage-root inode and the source path is an exact mode-0600 regular-file receipt. NoSQLDB does not remove that source receipt. The operation appends one native commit that changes only the fencing profile's physical path binding. All other record bytes and versions are preserved, including documents, indexes, empty namespaces, auth verifiers, principal identities, transaction outcomes, fencing history, holds and counters. The native group and timeline, provider identity, and original fencing receipt domain remain unchanged. The native commit position advances exactly once, including after retries.

Relocation supports stopped current native production roots with or without authentication. It never initializes missing ownership or storage metadata. Rehearsal destinations, legacy or mixed layouts, active read-access grants, unfinished fencing or allocation operations, building or quarantined indexes, symlinks, unknown entries, unsafe modes/ownership/link counts, exceeded inspection bounds, active owners and ambiguous identities are rejected explicitly. Admission uses the same owning catalog, auth, index, outcome and fencing validators as other stopped native operations. This operation moves the existing authority; it does not promote a rehearsal copy or duplicate production authority.

The operation durably records one immutable native append plan and reserves the receipt before changing the path binding. It retains exclusive ownership through the append, atomic directory/receipt exchange and confirmed journal cleanup. If a call returns RECOVERY_REQUIRED, stop startup attempts and retry the exact normalized source path, destination path and relocationId with the same native-capable NoSQLDB version. Startup rejects any relocation-workspace occupancy and never mutates or recovers it. Do not delete, rename, edit or replace either path between attempts. Native relocation does not accept unfinished journals from the removed Bitcask engine.

An exact completed replay returns the same receipt only while the destination remains the current root. A later successful relocation leaves another receipt at that destination path and supersedes the earlier replay topology; retry the later operation instead. Manual copy or rename fails provider path/inode fencing. alignStoppedStorageRoot can adopt an eligible copy as an independent fork under the stricter policy below; it does not move production authority.

Failures are exposed as LocalNoSqlDbStorageRootRelocationError with a code-only union: INVALID_REQUEST, UNSUPPORTED, BUSY, NOT_FOUND, DESTINATION_CONFLICT, IDENTITY_MISMATCH, STATE_CORRUPT, SOURCE_CHANGED, CAPACITY, CANCELLED, IO, or RECOVERY_REQUIRED. Errors and receipts contain no credential or document material. Transport ambiguity after dispatch is RECOVERY_REQUIRED; exact retry is the recovery action. An explicit native cancellation or I/O error can also leave private recovery state, which the identical request can finish after the sidecar has confirmed exit.

Stopped storage-root alignment

Never start a copied or restored root before alignment. Ordinary serving startup may recover a torn tail before rejecting its foreign binding. Stop the source, inspect it for pins, copy it while it remains stopped, and align the copy before its first start. Pins cannot prove that an unverified backup is complete; a file truncated exactly at a commit boundary can still be internally consistent.

Version 1 requires current production file storage, a clean committed tail that no serving open would still repair, no auth users, and no fencing, allocation, publication-hold, grant or import-stage authority. A root that a 10.x release wrote is refused; a start converts it first. The complete catalog, auth, index, transaction-outcome and fencing audits must pass. Rehearsals, memory storage, unknown files (including .DS_Store/AppleDouble), symlinks, hard links and unsafe ownership or modes are refused. The operation never repairs them. The attestation covers every copy of the lineage, not just the local root.

import { LocalNoSqlDb } from '@lossless.org/nosqldb';

// Run against the source after stopping it; retain these pins through the copy.
const source = await LocalNoSqlDb.inspectStoppedStorageRoot({ folderPath: '/srv/source/db' });
if (source.status !== 'audited' || !source.alignmentEligible) throw new Error('Source is ineligible');
const request = {
  folderPath: '/srv/destination/db',
  receiptPath: '/srv/destination/adoption.json',
  alignmentId: 'laptop-move-1',
  identityPolicy: 'fork' as const,
  attestLineageNeverFenced: true as const,
  expect: { groupId: source.groupId, timelineId: source.timelineId, lsn: source.lsn,
    recordsSha256: source.recordsSha256, providerRootId: source.providerRootId },
};
// /srv/destination is an existing mode-0700 directory owned by the service user.
const preview = await LocalNoSqlDb.alignStoppedStorageRoot({ ...request, dryRun: true });
const result = await LocalNoSqlDb.alignStoppedStorageRoot(request, { timeoutMs: 60_000 });

All paths must be canonical absolute paths. receiptPath is required, outside the root on the same filesystem, with an existing private parent. An unrelated existing file is never overwritten. Use the relocation identifier rules for alignmentId. Supply either expect from the stopped source or explicit acknowledgeUnverifiedCopy: true when no source pins survive, never both. binaryPath, timeoutMs and signal select the engine and bound the operation.

The following archive recipe was qualified on this release's format-3 roots, from macOS arm64 APFS to Linux x86_64 ext4 and XFS (alignment.md records the run). Run as the service user, with the source stopped throughout, and retain its inspection pins separately. Start with a new private destination parent; preserve the private file/directory modes and do not include filesystem metadata sidecars. On macOS use the exact canonical case and Unicode spelling; alternate spellings are refused rather than treated as portable aliases.

# macOS: /absolute/source-parent/db is stopped and inspected.
COPYFILE_DISABLE=1 tar --no-xattrs -czf /absolute/db-copy.tar.gz -C /absolute/source-parent db
# After transferring the archive to Linux, as the destination service user:
mkdir -m 700 /absolute/destination-parent
tar --no-same-owner -xzf /absolute/db-copy.tar.gz -C /absolute/destination-parent
# Run alignStoppedStorageRoot with the retained source pins before any start.

A dry run returns eligible with inspected evidence. An eligible root already bound to its location returns already-bound with bindingChange: 'none'. Neither creates a workspace or receipt. Alignment appends exactly one fresh schema-1 fencing profile: a new providerRootId, live physical binding and LSN+1. Every other record's bytes and version remain unchanged. groupId and timelineId remain lineage identifiers; they are not the new provider identity. The immutable receipt records both provider IDs, both LSNs, source/aligned full digests, preserved-record digest and request/receipt SHA-256 commitments. Retain it. A pre-reserved receipt proves completion only together with its matching target profile and lineage. Exact retries verify that evidence and return the same receipt, including after subsequent writes with the same target profile.

Alignment failures throw NoSqlDbStoppedOperationError with a code-only code. On RECOVERY_REQUIRED, keep the exact request and all files unchanged and retry alignment with the same capable engine. Its private workspace excludes ordinary startup and retains the exact append plan through crashes and partial writes. Do not delete or edit the workspace or receipt. BUSY means an owner still holds the root; IDENTITY_MISMATCH or SOURCE_CHANGED requires investigating changed pins or files. UNSUPPORTED indicates an ineligible root or older engine; stopped inspection provides bounded eligibility reasons. Other code-only errors include INVALID_REQUEST, NOT_FOUND, DESTINATION_CONFLICT, STATE_CORRUPT, CAPACITY, CANCELLED and IO.

abortStoppedStorageRootAlignment(request, options?) accepts the identical request and only rolls back an exact incomplete planned append. A complete append must finish alignment. If permissions changed after interruption, restore the required ownership and modes first. Abort permits a changed change time, which restoring ownership or modes leaves, while still checking every file's identity, length and modification time, the original bytes, and that the planned append's place holds only its own bytes or zeros. It durably records abort, writes zeros where the interrupted append wrote and syncs, removes its exact reserved receipt and cleans up. Retry abort after an interrupted abort; alignment cannot override that decision. An absent workspace returns not-pending.

Finish or abort a pending alignment with this release; deleting the workspace is not a fix. No earlier release reads a root that this release has aligned. Qualification results and study corrections are recorded in the repository's alignment design note.

Stopped Storage-Root Inspection

LocalNoSqlDb.inspectStoppedStorageRoot({ folderPath }, { timeoutMs?, signal?, binaryPath? }) audits an existing stopped source or copy without initializing or repairing it. Use a canonical absolute path. The outer and native ownership locks remain held through the complete record-owner audit. A running engine returns BUSY.

An audited result includes the recorded provider identity, live binding state, lineage (groupId/timelineId), decimal-string LSN and counts, full-record and profile-excluded SHA-256 digests, storageFormat: 'format-3' for a root of this release or the generation inventory of a root that a 10.x release wrote, and alignment eligibility. A format-3 root that a serving open would still repair, such as one whose log ends inside an unfinished write, is ineligible with RECOVERY_PENDING. A root that a 10.x release wrote is audited read-only as that release audited it, with the same identity, counts, digests and generation inventory, and is ineligible with CONVERSION_PENDING first among its reasons: this release does not align it, and a start converts it first. A folder that @push.rocks/smartdb's LocalSmartDb wrote is answered unavailable with UNSUPPORTED in 11.1.0, and alignment, relocation and rehearsal refuse it with UNSUPPORTED; a start converts it. It counts allocation replays as well as fences, receipts, holds and allocations. The audit accepts no active grants or import stages. Auth users or authority history make a fully audited root ineligible for independent-fork alignment.

An unavailable result contains bounded advisory reasons and no trusted logical identity, digests or partial counts. It indicates physical-admission or owner-audit failure; it is not evidence that a copy is safe. Reading may affect filesystem access times, but never changes data, modes, ownership or modification timestamps.

All stopped inspection and relocation methods accept binaryPath to select an exact executable. Unsupported management methods in older engines report UNSUPPORTED; interrupted dispatched mutations still require recovery. Health also exposes providerRootId and the current decimal-string lsn, and non-null resource fence summaries expose providerRootId. These fields are optional in the TypeScript interfaces for older engine binaries.

Offline String-Value Inspection

LocalNoSqlDb.inspectOfflineStringValue(input, options?) reads one string from a stopped native file root of this release, or read-only from one that a 10.x release wrote, on supported Linux filesystems or macOS APFS. It starts only a disposable management sidecar, acquires the existing native leases read-only, and pins the committed root. It never starts a listener, initializes storage or writes to the source: a root of this release is recovered in memory only, as a start would recover it, and a cut log tail or a leftover anchor replacement stays on disk as found. A root that a 10.x release wrote is read up to its committed state, and one that a 10.x process left without a clean stop is refused with RECOVERY_REQUIRED.

import { LocalNoSqlDb } from '@lossless.org/nosqldb';

const result = await LocalNoSqlDb.inspectOfflineStringValue({
  folderPath: '/var/lib/myapp/nosqldb',
  databaseName: 'myapp',
  collectionName: 'MetadataDoc',
  match: { field: 'key', value: 'schemaVersion' },
  valueField: 'value',
  limits: {
    maximumEntries: 100_000,
    maximumTotalFileBytes: 1024 ** 4,
    maximumRecords: 1_000_000,
    maximumRecordBytes: 16 * 1024 * 1024,
    maximumResultBytes: 1024 * 1024,
  },
}, { timeoutMs: 5000 });

if (result.status === 'found') console.log(result.value);

The result is exactly { status: 'not-found' } or { status: 'found', value: string }. No other document fields cross IPC. Multiple matches, non-string results, corrupted storage, changed source state, unsafe objects, missing owner metadata and exhausted limits reject the operation.

Offline Physical Namespace Inspection

LocalNoSqlDb.inspectOfflinePhysicalNamespaces(input, options?) validates and lists the stopped native database/collection catalog. Empty namespaces are retained. It validates native document/count/index relationships and compares complete bounded filesystem observations before returning; no partial result is published. Documents, index definitions, IDs and values stay inside the sidecar.

const namespaces = await LocalNoSqlDb.inspectOfflinePhysicalNamespaces({
  folderPath: '/var/lib/myapp/nosqldb',
  limits: {
    maximumEntries: 100_000,
    maximumDatabases: 1_000_000,
    maximumCollections: 1_000_000,
    maximumIndexes: 1_000_000,
    maximumTotalFileBytes: 1024 ** 4,
    maximumRecordsPerCollection: 1_000_000_000,
    maximumRecordBytes: 16 * 1024 * 1024,
    maximumResultBytes: 16 * 1024 * 1024,
  },
}, { timeoutMs: 30_000 });
// { schemaVersion: 1, databases: [{ name: 'myapp', collections: ['MetadataDoc'] }] }

These required limits are validated before sidecar creation:

Limit Supported range
maximumEntries 1–100,000 filesystem entries
maximumTotalFileBytes 1 byte–1 TiB
maximumRecords / maximumRecordsPerCollection 1–1,000,000,000
maximumRecordBytes 5 bytes–16 MiB
maximumResultBytes 1 byte–16 MiB for a value; 32 bytes–16 MiB for namespaces
maximumDatabases, maximumCollections, maximumIndexes 1–1,000,000 each for namespace inspection
Complete encoded request At most 2 MiB
timeoutMs 1–2,147,483,647 milliseconds

All numeric values must be safe integers. Paths are limited to 4095 UTF-8 bytes before and after absolute resolution; namespace and field names to 255 bytes. The string match is at most 1 MiB and must fit maximumRecordBytes. Removed per-Bitcask-file limits are rejected.

One optional deadline and AbortSignal cover lookup/spawn, native inspection and delivery. Cancellation kills and reaps the disposable sidecar before rejection. An active database owner blocks inspection. The caller must retain continuous lifecycle exclusion through acceptance or handoff of the result: the native lease covers inspection, and cannot prevent a restart after the sidecar exits.

NoSqlDbDebugServer

Web-based debug dashboard served via @api.global/typedserver. Import from the debugserver subpath:

import { NoSqlDbDebugServer } from '@lossless.org/nosqldb/debugserver';

const debugServer = new NoSqlDbDebugServer(server, { port: 4000 });
await debugServer.start();
// Dashboard at http://localhost:4000

await debugServer.stop();

The UI is bundled as base64-encoded content (via tsrepo bundle from @git.zone/tsrepo) and served from memory — no static file directory needed.

NoSqlDbDebugUi (Web Component)

For embedding the debug UI directly into your own web application, import the <nosqldb-debugui> web component:

import { NoSqlDbDebugUi } from '@lossless.org/nosqldb/debugui';

// In your HTML/lit template:
// <nosqldb-debugui .server=${myNoSqlDbServer}></nosqldb-debugui>
//
// Or in HTTP mode (when served by NoSqlDbDebugServer):
// <nosqldb-debugui apiBaseUrl=""></nosqldb-debugui>

Supported Operations

NoSQLDB supports the core operations through the wire protocol. Use the standard mongodb driver — these all work:

CRUD

// Insert
await collection.insertOne({ name: 'Bob' });
await collection.insertMany([{ a: 1 }, { a: 2 }]);

// Find
const doc = await collection.findOne({ name: 'Bob' });
const docs = await collection.find({ age: { $gte: 18 } }).toArray();

// Update
await collection.updateOne({ name: 'Bob' }, { $set: { age: 25 } });
await collection.updateMany({ active: false }, { $set: { archived: true } });

// Delete
await collection.deleteOne({ name: 'Bob' });
await collection.deleteMany({ archived: true });

// Replace
await collection.replaceOne({ _id: id }, { name: 'New Bob', age: 30 });

// Find and Modify
await collection.findOneAndUpdate({ name: 'Bob' }, { $inc: { visits: 1 } }, { returnDocument: 'after' });
await collection.findOneAndDelete({ expired: true });
await collection.findOneAndReplace({ _id: id }, { name: 'Replaced' }, { returnDocument: 'after' });

Query Operators

$elemMatch has two forms, implemented to be MongoDB compatible. An operand whose first key is a query operator applies its operators ($eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $exists, $type, $regex, $size, $mod, $all, $not, $elemMatch) to each element as a whole; any other operand is a query over each embedded document, and a nested array is matched as a document keyed by index. A dotted path tests every array it reaches on its own. $expr inside $elemMatch is refused with BadValue (2), implemented to be MongoDB compatible.

A regular expression matches the same way wherever a filter holds one — as a field's value, through $regex and $options, in a $in, $nin or $all list, under $not, and as a $pull condition: a string or symbol matches when the pattern finds a match in it, an array matches through its elements, a stored regular expression matches only when its pattern and options are equal, and a missing field never matches. $options takes the MongoDB-compatible flags i, m, s, x and u. Patterns run on Rust's regex crate rather than PCRE, so matching takes time linear in the input, constructs only PCRE has (look-around, backreferences, \Q…\E, \Z, (?#…) comments, a { that starts no repetition) are refused as invalid, and a few match differently: \d, \w and \s take Unicode digits, letters and spaces, $ without m does not match before a final newline, a possessive quantifier such as a++ is greedy, and x ignores whitespace inside a character class too. A pattern is checked when the filter is validated, before any document is read and even when the collection does not exist, with MongoDB-compatible errors in a MongoDB-compatible order: options given in both $regex and $options are Location51074 or Location51075, an unknown flag Location51108, a null byte BadValue (2), a pattern longer than 32,764 bytes or one that does not compile Location51091 Regular expression is invalid: <reason> (the reason is the crate's, such as unclosed group where MongoDB, as observed by runtime testing, answers missing closing parenthesis), and a regular expression as the operand of $ne, $gt, $gte, $lt or $lte BadValue (2). A match charges the work budget the size of the field's value times the length of the pattern.

Update Operators

{ $set: { name: 'New Name' } }
{ $unset: { tempField: '' } }
{ $inc: { count: 1 } }
{ $mul: { price: 1.1 } }
{ $min: { low: 50 } }        { $max: { high: 100 } }
{ $push: { tags: 'new' } }   { $pull: { tags: 'old' } }
{ $addToSet: { tags: 'unique' } }
{ $pop: { queue: 1 } }       // Remove last
{ $pop: { queue: -1 } }      // Remove first
{ $rename: { old: 'new' } }
{ $currentDate: { lastModified: true } }

Aggregation-pipeline updates are supported wherever the MongoDB driver accepts them (updateOne, updateMany, findOneAndUpdate, and upserts): the update may be an array of $addFields, $set, $project, $unset, $replaceRoot or $replaceWith stages, and any other stage is refused by name. The pipeline must produce exactly one document and cannot change _id.

An upsert that matches nothing inserts the filter's equality conditions, to be MongoDB compatible: top-level fields and $and clauses, $eq, a single-element $in, $all members and a single-clause $or. A dotted path such as 'owner.id' becomes a nested document, and a regular expression is copied only as an $eq operand. A replacement takes only _id from the filter and fails with NotExactValueField (111) when the filter matches a sub-path such as '_id.tenant' instead of _id itself. A filter that matches the same path twice, or a path together with its prefix, fails with NotSingleValueField (54).

Aggregation Pipeline

const results = await collection.aggregate([
  { $match: { status: 'active' } },
  { $group: { _id: '$category', total: { $sum: '$amount' } } },
  { $sort: { total: -1 } },
  { $limit: 10 },
  { $project: { category: '$_id', total: 1, _id: 0 } },
]).toArray();

Supported stages: $match, $project, $group, $sort, $limit, $skip, $unwind, $lookup, $addFields, $count, $facet, $replaceRoot, $set, $unionWith, $out, $merge

A leading $match narrows the pipeline's source load instead of filtering after it: only matching documents are materialized, using an equality index when one serves the filter and a bounded scan otherwise. Filter shapes the query matcher cannot pre-compile fall back to the unnarrowed load, so results are unchanged. The materialization limit therefore applies to the matched set, which is what lets countDocuments(filter) run against collections larger than it.

The scalar members of a $in or $nin list are looked up in a set built when the filter is validated, so a document costs the size of its value, not the length of the list: countDocuments({ _id: { $in: ids } }) answers for tens of thousands of ids. Equality is unchanged: numbers of every type are equal by value (1, NumberLong(1), 1.0 and NumberDecimal("1.00")), an array field matches through its elements or as a whole, and a missing field matches a list that holds null. Documents and arrays in a list are compared one by one and charged as such, and a regular expression in a list matches the strings it describes, as it does on its own.

Group accumulators: $sum, $avg, $min, $max, $first, $last, $push, $addToSet, $count

Indexes

await collection.createIndex({ email: 1 }, { unique: true });
await collection.createIndex({ name: 1, age: -1 });    // compound
await collection.createIndex({ field: 1 }, { sparse: true });
const indexes = await collection.listIndexes().toArray();
await collection.dropIndex('email_1');
await collection.dropIndexes();  // drop all except _id

🛡️ Unique indexes are enforced at the engine level. Duplicate values are rejected with a DuplicateKey error (code 11000) before the document is written to disk — on insertOne, updateOne, findAndModify, and upserts. Index definitions and entries are committed with native storage and survive restart.

List fields. An index on a field that holds lists has an entry per element, to be compatible with MongoDB multikey indexes, and a compound index an entry per combination of its list fields' elements. Equality, $in and one-sided ranges on the elements read their bounds, and a document with several entries within them is returned once. A list matches a range bounded on both sides when one element meets the lower bound and one the upper, so such a read takes the side with fewer entries. Equality to a whole list, such as { tags: ['a', 'b'] }, is not answered from element entries, and a list nested in a list stops range reads through the index. An index with element entries returns no order, so a sorted read sorts its matches, and a hint naming it is refused. A unique index on a list field is unique per element: a document that shares an element with another is a DuplicateKey (11000), while one document may repeat an element. An index whose field already held a list under 10.7.3 or earlier, which those releases kept with whole-list entries, gets element entries when 11.0.0 converts its root at start, and is then selected like any other index; a unique one whose documents share an element refuses the start before anything is written. Engines up to 10.7.3 refuse an index with element entries instead of maintaining it with whole lists.

find() and collection aggregate() accept hint as an index name (including _id_) or an exact ascending/descending key pattern. Key-pattern order matters; if several indexes share a key pattern, use a name. The selected active scalar index supplies every candidate, using safe full-key or leading-prefix equality bounds, or a full scan of that index. The complete predicate still runs, and explicit sorting, projection, skip, and limit retain their normal semantics. Index traversal uses bounded pages from a pinned committed root across client requests. Unsorted results have no guaranteed BSON value order.

await collection.createIndex({ organizationId: 1, userId: 1 }, { name: 'organization_user' });
const page = await collection.find({ organizationId: 'org-1' }, {
  hint: 'organization_user', sort: { userId: 1, _id: 1 }, skip: 20, limit: 10,
}).toArray();
const count = await collection.countDocuments({ organizationId: 'org-1' }, {
  hint: 'organization_user',
});

Unknown or quarantined indexes, malformed/ambiguous patterns, $natural, and hints on missing collections or views fail explicitly. Forced multikey traversal is unsupported and rejects; unhinted queries on list fields use element entries as described under List fields. Sparse hints intentionally exclude documents absent from that index, including for empty filters and counts. A hint is never silently replaced by a collection scan or another index.

Hinted transaction reads construct the selected index from the existing bounded transaction snapshot plus buffered writes; they never fetch candidate documents from live storage. Read-only aggregate source pipelines also use that transaction view. Foreign-collection stages ($lookup, $unionWith) and writing stages ($out, $merge), including stages inside $facet, reject within transactions. Nontransaction execution uses the streaming and bounded-resource paths below.

Scalable query execution

Collections have no fixed total-document ceiling on the nontransaction find, count, distinct, update, delete, and findAndModify paths. Result cursors read bounded pages instead of retaining the whole result. Multi-document writes visit each source document once, including when updates move indexed values. Index publication stages changed keys instead of copying the complete collection index engine for each write.

Nontransaction aggregation pipelines composed of $match, $project, $unset, $set, $addFields, $replaceRoot, $replaceWith, $skip, $limit, $sort, $group, $count, $unwind, $lookup, $unionWith, and $facet also operate beyond 10,000 input or output documents. Counts use each collection's exact count from its statistics record where applicable, otherwise stream matching records. A limit bounds the work needed to find the requested number of matches. Without a limit, exact counts continue to EOF. Hints, deadlines and empty-result shapes remain supported; signed BSON count overflow and resource exhaustion reject explicitly. Exact counts never substitute estimates, and transaction counts retain the snapshot admission limits described below. Grouping accumulates one group at a time after an external sort over the group key; only keys with no field reference and no operator stream directly into a single group. Composite document keys sort and group like any other key. Group key and accumulator semantics follow the supported expression operators listed above; this does not add the complete MongoDB aggregation language. Unwind retains one input while expanding arrays across batches. Lookup scans bounded foreign pages or indexed candidates, then assembles one byte-bounded result document. Unindexed joins can still require repeated foreign scans. Facet branches replay a shared input buffer or temporary spool; their combined result must fit the BSON document budget.

Scalar ordered indexes can satisfy a sort prefix after equality fields, including reverse traversal and mixed compound directions. This path currently requires distinct full index keys. Equal requested sort keys follow the remaining index columns; include a unique field in the sort when tie order matters. Arrays, unsupported BSON index types, and duplicate full index keys use the general sort executor. Numeric comparison and scalar equality indexes share exact Int32, Int64, Double, and Decimal128 ordering, including large integers, signed zero, and NaNs.

Small sorted limits retain top-k candidates. Larger sorts and groups use bounded runs in unnamed temporary files, with automatic cleanup on exhaustion, cursor closure, cancellation, errors, and process exit. Configure the shared resources through NoSqlDbServer:

const server = new NoSqlDbServer({
  storage: 'file',
  storagePath: '/path/to/database',
  query: {
    sortBufferBytes: 4 * 1024 * 1024,
    maxSortSpillBytes: 4 * 1024 * 1024 * 1024,
    maxConcurrentSorts: 4,
  },
});

sortBufferBytes is an encoded-input target, not an exact allocator heap limit; a single BSON document may exceed the target. The spill budget includes retained cursor files, facet input spools, and simultaneous merge input/output. Exhausting resource admission returns an explicit error instead of truncating results. Cursor continuations share a 256 MiB accounting budget. One continuation admits at most 160 MiB plus 4 KiB, including pinned query state, changed-document overlays and bounded large-index-key candidates. Wire batches and pending document pages retain their 16 MiB limits. distinct results and individual group documents must fit their BSON response/document byte budgets. The 10,000-operation wire batch limit still applies to each request; the driver can split larger input batches.

Native streaming reads pin a committed root across cursor pages. Later writes do not change that snapshot. Current authorization and publication ownership are revalidated before a continuation is served. Ownership checks, namespace gates, byte budgets, and maxTimeMS remain active through getMore and cleanup.

Transactions use pinned roots and changed-document overlays; the total database size is not a transaction snapshot limit. Complete write sets remain bounded by transaction and native commit byte admission. $out and $merge have no 10,000-document output ceiling; their atomic output is subject to the same write budgets and rejects before publication if those budgets are exceeded. Native database import uses private staging for larger supported exports, then publishes the catalog and receipt atomically. A start converts a root that a 10.x release wrote (see Storage format migration under File startup admission); any other old storage is rejected without conversion.

Comparing performance with MongoDB

The repository includes benchmark/run.ts, which starts fresh, loopback-only NoSQLDB and original MongoDB processes with isolated temporary storage. Supply an original mongod executable; the harness does not download one or use a MongoDB-compatible replacement as the reference server.

NOSQLDB_RUST_BINARY="$PWD/rust/target/release/rustdb" \
  MONGODB_BINARY=/path/to/original/mongod \
  tsx benchmark/run.ts --engine both --sizes 1000,10001,100000 \
  --concurrency 1,16 --repetitions 3 --samples 250 --warmup 50 \
  > .nogit/scaling-comparison.jsonl

Run on Linux after building the current binary and stopping competing benchmark or build work. Both engines use the same driver and data, w:1, j:true, local reads, snapshot transactions, disabled retryable writes, and one connection per worker. MongoDB runs as a single-voter replica set. Repetitions alternate engine order. --id-type string covers string identifiers; --payload-bytes changes record size. Full scans and transactions use the separate --scan-samples count (default 3) because they are substantially more expensive.

JSON-lines output records binary hashes, host/version metadata, correctness failures, seed/index time, latency percentiles, throughput, process RSS and peak RSS, CPU ticks, and physical read/write bytes. Temporary database directories are retained for inspection and their paths are recorded. Failed operations remain visible; they must not be compared as successful fast queries. Small sample counts and a shared development host are diagnostic evidence, not a general claim of superiority over MongoDB. The recorded comparison includes both measured improvements and remaining gaps.

Database & Admin

await db.listCollections().toArray();
await db.createCollection('new');
await db.dropCollection('old');
await db.dropDatabase();
await db.stats();

const admin = client.db().admin();
await admin.listDatabases();
await admin.ping();
await admin.serverStatus();

NoSQLDB creates ordinary collections, collections with document validators (see below) and one deliberately restricted view form. Capped collections, collation, time-series collections, clustered indexes, encrypted fields, and all general view pipelines remain unsupported and are rejected before the database or namespace is created.

Collection names. A name with a NUL, an empty name and a name starting with . that storage cannot hold, such as . or .., are InvalidNamespace (73) on every command naming them, with MongoDB-compatible messages, before the command runs. A name MongoDB accepts that one storage path component cannot hold, one with /, \ or a control character or longer than 255 bytes, names a collection that cannot exist: reads, drop, viewOn and a rename source see it missing, and commands that create or write it are refused with InvalidNamespace, or IllegalOperation (20) for $out or a rename into a name longer than 255 bytes. Such a refused write ends its transaction, to be compatible with MongoDB's refusal of a namespace longer than 255 bytes. NoSQLDB limits the collection name to 255 bytes where MongoDB, as observed by runtime testing, limits the whole database.collection, and it accepts a name such as .a that starts with . but that its storage holds, which MongoDB refuses.

Collection Validators

create and collMod accept the MongoDB-compatible validator, validationLevel (strict, moderate, off) and validationAction (error, warn), and listCollections reports them under options exactly as set:

await db.createCollection('ffb_ci_v2_runners', {
  validator: {
    $jsonSchema: {
      bsonType: 'object',
      required: ['revokedAt'],
      properties: { revokedAt: { bsonType: ['date', 'null'] } },
    },
  },
  validationLevel: 'strict',
  validationAction: 'error',
});
await db.command({ collMod: 'ffb_ci_v2_runners', validationLevel: 'moderate' });

A validator is a query document. Its top level may hold one $jsonSchema; every other clause is an ordinary query predicate evaluated by the query engine, $expr with the command's $$NOW included. The $jsonSchema subset is bsonType (every BSON alias and number), type (object, array, number, boolean, string, null), required, properties, additionalProperties (boolean), minProperties, maxProperties, enum, minimum, maximum, exclusiveMinimum, exclusiveMaximum, minLength, maxLength, pattern, items (one schema), minItems, maxItems, title and description, with MongoDB-compatible semantics: a keyword applies only to values of its kind and not at all when bsonType/type excludes that kind, enum compares numbers by value and embedded documents regardless of field order, NaN fails minimum and maximum, and lengths count code points. Everything else is refused when the validator is set, never ignored: patternProperties, additionalItems, uniqueItems, dependencies, allOf, anyOf, oneOf, not, multipleOf, encrypt, encryptMetadata, items as an array and additionalProperties as a schema answer FailedToParse (9) $jsonSchema keyword '…' is not currently supported, the MongoDB-compatible answer for $ref, format, default, definitions, id and $schema; $jsonSchema below the top level answers BadValue (2). Malformed validators answer MongoDB-compatible codes (2, 9, 14, Location51091 for an invalid pattern). pattern uses the same regular-expression dialect as $regex; constructs only PCRE supports are refused as invalid.

Every insert and update is validated: inserts, upserts, update operators, replacements, pipeline updates, findAndModify, bulk writes, $merge and $out output, inside and outside transactions. strict validates every written document; moderate skips updates to documents that did not satisfy the validator before the update; off validates nothing; a no-op update and a delete are never validated. A refused document answers DocumentValidationFailure (121), errmsg: "Document failed validation", and the MongoDB-compatible errInfo: { failingDocumentId, details } — as a write error of insert/update, keeping ordered and unordered batch semantics, and as the command error of findAndModify and aggregate. A refused statement aborts its transaction. details has a MongoDB-compatible shape for every $jsonSchema keyword above and for $and, $or, $nor, $not, $eq, $ne, $gt, $gte, $lt, $lte, $exists, $type, $in, $nin, $regex, $size, $all, $elemMatch, $mod and $expr; other operators report only operatorName and specifiedAs. validationAction: 'warn' writes the document and logs a Document would fail validation warning with the namespace and errInfo. bypassDocumentValidation (a boolean or number) on insert, update, findAndModify and writing aggregate skips validation; with authentication it requires dbAdmin on the written database, as collMod does — readWrite alone is refused with Unauthorized (13), implemented to be compatible with MongoDB's built-in roles.

collMod changes only the options it names; validator: {} removes the validator; a null option is absent; collMod without options answers { ok: 1 }. A missing collection answers NamespaceNotFound (26), a view or system.* collection InvalidOptions (72), the other MongoDB collMod options (index, viewOn, pipeline, expireAfterSeconds, timeseries, changeStreamPreAndPostImages, dryRun, cappedSize, cappedMax, …) InvalidOptions (72) by name, and unknown fields IDLUnknownField (40415). Options persist with the collection, survive restarts and renameCollection, and $out keeps its target's options. The encoded options are limited to 60 KiB (MongoDB has no separate limit); larger ones answer InvalidOptions (72). Canonical export writes a collection's options beside its documents and indexes only when it has options, import restores them, and the database content digest includes them only then, so digests of collections without options are unchanged. Engines up to 10.6.1 refuse a storage root or an export holding collection options instead of silently not enforcing them.

Differences from MongoDB, as observed by runtime testing: create on an existing collection answers NamespaceExists (48) even when the options are identical (MongoDB answers ok); a validator together with viewOn is refused (MongoDB ignores it); an invalid pattern at the schema root is refused (MongoDB accepts it there); command errors say Document failed validation without the Plan executor error during … prefix MongoDB adds; the warn log carries errInfo, not the whole document.

Restricted Deny-All Views

The only accepted view definition is the exact deny-all pipeline below. This is intended for compatibility barriers such as renaming a legacy source and leaving its old namespace present but permanently empty:

await db.collection('secrets').rename('secrets_v2');
await db.createCollection('secrets', {
  viewOn: 'secrets_v2',
  pipeline: [{ $match: { $expr: { $eq: [1, 0] } } }],
});

Both viewOn and pipeline are required. The pipeline must contain exactly one $match stage with exactly $expr: { $eq: [1, 0] }; empty pipelines, numeric or structural variants, additional stages, and additional collection-definition options are rejected with InvalidOptions (code 72) before mutation. Command metadata such as $db, lsid, comment, and API-version fields remains accepted. viewOn must identify an existing same-database collection or restricted view at creation time. General MongoDB views are not supported.

The logical view has no physical collection. find, count, and distinct return empty results. aggregate starts with an empty source and still applies the client pipeline; $lookup and $unionWith resolve the view as empty. Transactions first reading a current view record an explicit empty snapshot; a transaction that captured a populated collection before it is renamed and replaced by a view keeps that original snapshot for find, count, and distinct until commit conflict handling. Inserts, updates, deletes, findAndModify, indexes, $out, $merge, rename, and other data mutations fail with CommandNotSupportedOnView (code 166). dropCollection is the supported DDL exception for removing a logical restricted view.

Definitions are persisted in the physical system.views catalog. To be MongoDB compatible, listCollections lists system.views as a physical storage collection and the logical namespace as type view with { viewOn, pipeline }, read-only metadata, and no idIndex. system.views is reserved from direct wire reads, writes, index operations, create, drop, rename, and aggregation output. Whole database drop and trusted database export/import/digest operations handle it as physical catalog data. Export never materializes a view; cross-database import remaps the exact catalog _id database prefix. Startup validates databases whose publication lease can be acquired before listener bind; a publication-held database is validated on its first permit after release. Import validates before mutation. Malformed, oversized, noncanonical, duplicate, reserved, or colliding catalog state fails closed.

system.views was not interpreted by earlier SmartDB releases. This reservation is a major-version compatibility boundary: before upgrading, operators must use the older release to rename or remove any unrelated physical system.views collection. Once upgraded, direct catalog rename or drop is intentionally rejected with code 166. A start on a smartdb folder that still holds such a collection refuses the folder with UNSUPPORTED and leaves it as it was (see smartdb folders under File startup admission).

Bulk Operations

const result = await collection.bulkWrite([
  { insertOne: { document: { name: 'Bulk1' } } },
  { updateOne: { filter: { name: 'X' }, update: { $set: { bulk: true } } } },
  { deleteOne: { filter: { name: 'Expired' } } },
]);

Count & Distinct

const count = await collection.countDocuments({ status: 'active' });
const estimated = await collection.estimatedDocumentCount();
const names = await collection.distinct('name');

Wire Protocol Commands

Category Commands
Handshake hello, isMaster, ismaster
CRUD find, insert, update, delete, findAndModify, getMore, killCursors
Aggregation aggregate, count, distinct
Indexes createIndexes, dropIndexes, listIndexes
Sessions startSession, endSessions, killSessions
Transactions startTransaction, commitTransaction, abortTransaction through driver sessions
Admin ping, buildInfo/buildinfo, listDatabases, listCollections, drop, dropDatabase, create, collMod (validation options), serverStatus, dbStats, collStats, connectionStatus, renameCollection

The moreToCome request flag is accepted only for the exact endSessions or killSessions cleanup envelope: an admin command body containing only the session array, writeConcern: { w: 0 }, and $db: "admin", with no document sequence. It executes without a transport response and leaves the connection reusable. Every other moreToCome envelope closes before command routing or response-owned resource creation.

Received bytes are charged against the server's encoded-byte budget, 256 MiB by default, until their request is decoded. A connection reads into an 8 KiB receive buffer charged before each read. Once a frame's length prefix shows it is larger, the connection takes the charge for the whole frame before it reads more of it, and the frame is received into a buffer of its own, which grows with the bytes received; frames pipelined behind it are copied out, each with its own charge. Receive buffers and the frames that fit them are charged against a separate 16 MiB share kept beside the budget, which holds a buffer and a frame for each of the 1,024 connections a server accepts, so they never wait: handshakes, pings, authentication, ordinary commands and getMore are served while large frames wait. A connection whose frame's charge the budget cannot hold now waits for it behind the frames that arrived before it, holding only its receive buffer, instead of closing, and the wait counts toward its read timeouts. Once a frame has its own charge, it has a 10-second grace, after which the bytes of it received must average 1 MiB per second or more since the charge was taken while others need the budget: while a connection waits for it, or while more than seven eighths of it are charged. The connection whose frame is then slowest below the rate is closed and the frame's charge released, and the next only if that contention remains, so on an idle server a frame arrives however slowly, within the read timeouts. A connection told to close does so at once, also while its previous command runs or a reply is written to it, and the close is logged at info with the client's address, the frame's length, the bytes received and the time taken, at warn when another closed within the minute before. A frame that keeps the rate may take one second for each MiB of it, 46 seconds for 48 MB, and large frames from other clients can wait that long, or the grace, behind it. A frame that fits the receive buffer is bounded by the read timeouts alone. The budget bounds encoded input rather than the exact allocator heap.

A server sizes both budgets when it starts from the memory the process may use: the lowest cgroup memory limit on the process's cgroup and its ancestors (memory.max, or memory.limit_in_bytes in cgroup v1), or physical memory when that is lower or no limit is set. On macOS physical memory (hw.memsize) alone sizes them; that path is not tested yet. The encoded budget is a sixteenth of it, from 64 MiB to 256 MiB, and the decoded budget a quarter, from 64 MiB to 1 GiB, so both full take under a third of it from 1 GiB of memory up: 192 MiB and 768 MiB with 3 GiB, and the defaults from 4 GiB. Their small shares take 16 MiB each beside them, so with 1 GiB of memory all four take 352 MiB. The 64 MiB minimum holds the largest frame. The engine logs the budgets, the limit it found and the frame rate at startup. Either budget can be set, from 64 MiB to 1 TiB, and so can the frame grace, from 1 to 300,000 milliseconds, and the frame rate, from 1 byte per second:

const server = new NoSqlDbServer({
  inbound: {
    encodedBytes: 256 * 1024 * 1024,
    decodedBytes: 1024 * 1024 * 1024,
    frameGraceMs: 10_000,
    minFrameBytesPerSecond: 1024 * 1024,
  },
});

The decoded budget also bounds the largest request the server accepts, so a smaller one refuses smaller requests with BSONObjectTooLarge. Each server has its own budgets, so servers in one process do not share them.

  • A frame over 8 KiB whose bytes average less than 1 MiB per second after a 10-second grace closes its connection while another connection waits for the encoded budget or more than seven eighths of it are charged; MongoDB has no such limit.

Identifying the Engine

NoSQLDB speaks the MongoDB wire protocol, so it must also state plainly what it is. Every hello, ismaster and isMaster reply carries a self-contained nosqldb document, and so does buildInfo/buildinfo:

{
  "product": "NoSQLDB",
  "vendor": "lossless.org",
  "version": "<the released package version>",
  "storageEngine": "native",
  "mongodbCompatibility": "7.0.0",
  "minWireVersion": 0,
  "maxWireVersion": 21
}

version is the released @lossless.org/nosqldb version the running binary was built from: the Rust build compiles it in from the package manifest, so the answer belongs to the shipped artifact and cannot drift from it. mongodbCompatibility is the compatibility version NoSQLDB reports to drivers and tools for the command surface it implements to be MongoDB compatible — a compatibility value, never a claim to be a MongoDB build.

The handshake carries the identity unconditionally, before authentication, so a client can derive its backend from the connection it already has. buildInfo is available to every authenticated connection, including a readWrite-only tenant user and an allocation read grant; it requires no clusterMonitor role and creates no session state. buildInfo answers from build metadata alone and therefore still answers on a fenced or closed runtime, while hello fails closed. It answers in a MongoDB-compatible shape so existing tooling keeps working:

{
  "version": "7.0.0",
  "versionArray": [7, 0, 0, 0],
  "gitVersion": "<source commit, or \"unknown\" when the build had no Git metadata>",
  "modules": [],
  "javascriptEngine": "none",
  "bits": 64,
  "debug": false,
  "maxBsonObjectSize": 16777216,
  "storageEngines": ["native"],
  "nosqldb": { "...": "the identity above" },
  "ok": 1
}

buildInfo.version states the implemented compatibility level, exactly like serverStatus.version; the engine's own version is in nosqldb.version. A commit is never fabricated: gitVersion is either the exact source commit of the build or the literal unknown. serverStatus keeps every field it already reported, including version and process: "rustdb", and now reports the same nosqldb identity beside them — but it remains a clusterMonitor command, so least-privilege connections use hello or buildInfo.

serverStatus.engineFigures reports the storage engine's own figures, each read from a counter or from the allocator's own statistics, so forming them takes no time that grows with the data. Every count is an int64 that is 0 when the server opens its storage and never decreases while it runs. commitMode is buffered: commits go through the write buffer, the only commit path. bufferedBytes is the bytes of committed writes that the buffer holds and no merge has yet written into the tree, the sum of activeGenerationBytes and frozenGenerationBytes; commits counts the publications made durable and visible, and mergesStarted and mergesCompleted the buffer's merges. freezes counts the buffer's generations frozen for a merge, by cause: size when the generation reached its size, idle when no commit arrived for the quiet period, age when its oldest commit reached the age limit, and prune when records that history pruning wrote waited for a merge. backstopStall counts the commits that waited at the buffer's backstop (stalls) and the nanoseconds they waited (stalledNanoseconds), and gives the commits waiting there now (stalledWriters). A commit waits there when it would take the active generation past its size while a generation is pending, from its freeze until the checkpoint after its merge is durable, or while commits that passed the backstop before it and are not linked yet fill the active generation; and a background commit waits there at its claim on the buffer while another background commit holds one. refusals counts the requests for space that the engine refused for lack of free space, a commit's or the engine's own work's (space), and the commits refused after waiting at the backstop as long as their limit allows (backstop). While a merge cannot get room, serverStatus.engineFigures.mergeWedge reports what it lacks (bytesShort or segmentsShort), and is null again once a later attempt of the merge is admitted; mergeRefusals counts the merge attempts refused since the root opened. restarts counts the write preparations started again by cause, catalogChange, pruningWindow and merge; one for a conflict on a document or unique key counts under none. bytesWritten counts the bytes handed to the file system for the storage files by duty: redo, merge, cleaner, indexes, checkpoint, directory and fill. liveBytes is the bytes the current state reaches and fixedBytes the bytes the engine's files hold apart from data: the anchor, segment headers and pre-sized log space. directoryBytes and cacheResidentBytes are the memory that the directory of stored frames and the node cache hold, and cacheBudgetBytes is the cache budget that storageEngine.cacheBytes reports. serverStatus.engineFigures.pinnedBytes is the bytes in the storage's data segments that the engine keeps, where it would otherwise free them, only because a live snapshot, such as an open cursor's, can still read superseded data in them: the value its latest maintenance round found, and 0 when no snapshot keeps any. allocator states whether a check at run time found the shipped allocator (verified), and only then its arena count, its decay times in milliseconds, whether it runs background threads, and the bytes allocated through it and resident; while verified is false, those fields are null.

Read and Write Concerns and Wire Deadlines

NoSQLDB serves every database from exactly one voting node and states its concern profile in those terms instead of ignoring or blanket-rejecting the fields:

  • Write concern. Every acknowledged write is in the write-ahead log before the response is sent: fsynced there with file storage, while memory storage keeps its log in the process's memory. So w: 1, w: "majority" (the majority of one voter is that voter), and j: true are all satisfied by the same commit. w: 0 is acknowledged the same way whenever a response is expected; the exact writeConcern: { w: 0 } endSessions and killSessions envelopes still run without a transport response. wtimeout bounds a replication wait that does not exist on one node and has no additional effect. w: N for N > 1 fails with UnsatisfiableWriteConcern (code 100), tag-set modes fail with UnknownReplWriteConcern (code 79), and j: true fails with BadValue (code 2) on the in-memory storage backend, which does not journal. Write concern is accepted only on commands that write (including DDL, user management, commitTransaction, and abortTransaction) and is rejected with InvalidOptions (code 72) inside a multi-statement transaction, where it belongs on the commit.
  • Read concern. local, available, majority, and linearizable all observe the durably committed state, because nothing is acknowledged before it is durable and reads never observe uncommitted transaction buffers. snapshot is accepted only on the first statement of a multi-document transaction and is served by the database-wide transaction snapshot described below. Read concern is accepted on find, count, distinct, and aggregate only, and afterClusterTime, atClusterTime, and afterOpTime are rejected with InvalidOptions because a single node issues no cluster time.

Callers may therefore rely on a successful NoSQLDB response as a durable, single-node acknowledgement. They must not read it as a replica-backed acknowledgement: majority semantics across several voters arrive with replication.

Unsupported Administrative Commands

NoSQLDB returns CommandNotFound (code 59) for the wire commands validate, legacy authenticate, hostInfo, whatsmyuri, getLog, getCmdLineOpts, getParameter, setFreeMonitoring, currentOp, killOp, top, profile, compact, reIndex, fsync, and connPoolSync. These commands previously had placeholder success responses but do not yet implement their advertised MongoDB semantics. Authentication and authorization errors retain precedence; otherwise rejection occurs before database publication permits, session or transaction creation, and command dispatch. This restriction applies to the wire validate command, not the supported offline --validate-data tool. getFreeMonitoringStatus remains available and reports NoSQLDB's fixed disabled state.


Rust Crate Architecture 🦀

The Rust engine is organized as a Cargo workspace with 15 focused crates:

Crate Purpose
rustdb Binary entry point: TCP/Unix listener, management IPC, CLI
rustdb-config Server configuration types (serde, camelCase JSON)
rustdb-wire Wire protocol parser/encoder (OP_MSG, OP_QUERY, OP_REPLY)
rustdb-query Query matcher, update engine, aggregation, sort, projection
rustdb-state Internal deterministic storage commands, stable retry identities, and durable receipts
rustdb-kernel Native storage format 3: a log of commits, a write buffer merged into a tree, checkpoints and a cleaner, with stable timelines, contiguous LSNs and strict recovery; and the format-2 reader of 10.x roots
rustdb-replication Internal non-serving OpenRaft 0.9.25 one-voter fresh-root foundation; not linked into the released rustdb server
rustdb-storage Native catalogs, documents, exact counts, staged imports, reclamation, the rule that admits a database's view catalog, and bounded OpLog
rustdb-index Native index records, query plans, uniqueness, bounded builds and TTL
rustdb-txn Transaction + session management with snapshot isolation
rustdb-auth SCRAM-SHA-256 credential handling, user metadata persistence, RBAC checks
rustdb-commands 40+ command handlers wiring everything together
rustdb-process Process settings that the engine binary and its test builds link: jemalloc as the global allocator with the shipped settings, their check and the raise of the soft descriptor limit at process start, and, behind its measurement feature, the measurement facilities
rustdb-root A file storage root's file operations, lock, ownership, names and fencing profile; it knows no predecessor format
rustdb-upgrade What a storage path holds before a start writes anything there, and the conversion into format 3 of a root that an earlier release wrote or of a folder that @push.rocks/smartdb's LocalSmartDb wrote

The native MVCC engine serves both memory and file storage. Its Rust APIs use mvcc modules and Mvcc type names independently of npm versions and persisted format versions. The format-3 store writes each publication as one checksummed COMMIT frame of typed BSON records to its log, keeps recent writes in an in-memory buffer, merges them into a copy-on-write tree in data segments and writes checkpoints from which recovery replays the log. Snapshots pin a published view, and maintenance frees nothing a pinned snapshot can reach. Descriptor-relative access and the enclosing native file-root lease protect the entire lifecycle. Ambiguous publication fences the owner until recovery. Confirmed close drains requests, sessions, cursors and retained workers before releasing ownership.

The store keeps verified tree nodes in one node cache, half of its memory budget, evicted by CLOCK. The budget is storageBudgetBytes where that option of NoSqlDbServer is set, from 4 MiB to 1 TiB, and otherwise a sixteenth of the memory the process may use, measured as for the inbound budgets above, from 16 MiB to 4 GiB; serverStatus.storageEngine.cacheBytes reports the cache's half. A root the server serves takes it when it is created or opened and keeps it until it closes; the stopped operations' reads of a stopped root, a start's checks of a root before it serves it and a 10.x conversion open the store with 64 MiB. Each cached node carries a referenced bit: inner nodes enter with it set and leaves without it, and a point read sets it on the nodes it uses. To make room, a hand sweeps the entries in turn, clears each set bit it passes and evicts the first entry whose bit is clear. A read that is one row of a pass over many records, such as a scan, a merge or a page of the commit journal, clears no bit and is cached only in place of an unreferenced entry at the hand, so such reads cannot displace the nodes point reads keep. A publication removes the nodes it supersedes from the cache. There is no value cache: a value stored outside its leaf is read from storage each time. A collection scan reads each page of documents as one range read of their bodies in natural order, with no lookup per document.

Database and collection lookups, absent names included, are remembered for the newest committed root a read has used, up to 256 names and 1 MiB, so the commands reading one root resolve each name once. Roots are immutable, so nothing is invalidated: a commit's new root starts afresh, and a snapshot of an older root reads its catalog from storage. A remembered lookup still validates the storage root as an uncached one does, and a failed lookup is never kept. A collection's index catalog is remembered the same way, for up to 256 collections and 1 MiB per root, so planning, staging and publishing a write on one root list its indexes once. The first listing on a root reads the catalog whole; a catalog of more than 64 indexes, aliases included, and a listing that fails are not kept.

Document, catalog, statistics, natural-order, index-entry, unique-owner, authentication, fencing and retry-receipt records share atomic publications. Transactions retain changed documents and immutable read views with byte and concurrency admission; they do not copy a complete database snapshot or impose a 10,000-document base-snapshot ceiling. Reads, getMore, indexes and streaming aggregation use pinned bounded pages. Transaction sorts use the shared external sort executor. Successful retryable statements retain exact results, including generated IDs and zero-match outcomes, across restart. Failed statements do not receive success receipts. Session termination drains active work and retires its cursors.

Custom indexes build in the background: createIndexes publishes them as building in one short commit and answers once they are ready, while other writes go on. A build sorts its entries off the writer, after reserving the space its sort runs and its output need, with its runs in a private scratch directory inside a file root and in memory on a memory root, and attaches the entries in one commit; it then applies the writes made meanwhile until the writers keep its indexes up to date in their own commits, and makes its indexes ready together in a commit that carries no entry. No read uses its indexes before then. A request for the same indexes joins a running build, and serverStatus reports builds under indexBuilds. A build that its collection's writes outpace, or that meets a resource limit, fails with an explicit refusal and leaves no index; a duplicate key fails a unique build with DuplicateKey (11000). Interrupted builds restart from the beginning under native ownership before serving, and run in the background, so serving does not wait for them. A restarted build that fails removes its indexes, restores a quarantined index it was rebuilding, logs a warning that names neither the index nor a key, and shows as failed in indexBuilds until the next build of that index; it is not retried by itself. Held databases remain closed and are skipped during recovery; releasing the hold restarts their interrupted builds.

Index names and aliases, sparse and unique constraints, empty collections, restricted-view catalogs and exact BSON survive supported database export/import. Native import replaces the entire data catalog and its publication receipt in one commit, preserving managed principal and allocation identities. Old pinned reads retain the previous catalog. Exact fenced retries append nothing; held replacements remain inaccessible until their exact receipt is released.

The serving profile is:

Surface Native support
Runtime Linux and macOS; memory or current native file storage
Wire data find, count, distinct, aggregate, getMore, killCursors, insert, update, delete, findAndModify; supported $out/$merge
Catalog/admin Collection/database/index DDL, rename, listings, supported statistics and diagnostics, user/role administration
Auth/sessions SCRAM-SHA-256, logical sessions, snapshot transactions, commit/abort and durable retryable writes
Management Health, tenants, grants, attestation, fencing, publication holds, export/import and content digest
Debug Bounded current-session OpLog, atomic data revert, document/collection pages and dashboard
Maintenance Bounded TTL expiry, retired record reclamation, orphan import retirement, and the store's merges, checkpoints, tombstone pruning and cleaning
Stopped storage Native inspection and relocation; isolated authenticated rehearsal on its qualified platform profile
Import/control admission 100 MiB encoded import; bounded private staging and a small final catalog/receipt commit. Other control requests: 1 MiB
External cluster Replication and HA remain unsupported; the one-voter OpenRaft foundation is not linked into serving

These are operation and resource budgets, not total database/document limits. Unsupported options and commands reject explicitly. Imports that cannot fit one derived document or index entry within the native commit budget reject before publishing the replacement. Reclamation preserves pinned roots and retires parent identities only after their children have been handled. The store's cleaner moves the live data out of a data segment of which at least a third of the written bytes is garbage, and a segment is removed once no checkpoint or pinned snapshot needs it.

There is one serving engine and no Bitcask fallback. A start converts a root that a 10.x release wrote, and a folder that @push.rocks/smartdb's LocalSmartDb wrote, into format 3 (see Storage format migration under File startup admission); no other storage conversion and no authentication conversion runs during startup or rehearsal. Storage conversions live in the rustdb-upgrade crate. Top-level ts_migration/ is for application data: a migration of it, when explicitly commissioned, belongs there exclusively.

NoSQLDB makes no general MongoDB performance or feature-parity claim. Versioned packages contain Linux amd64/arm64 and macOS amd64/arm64 native executables through @git.zone/rsrepo.

Multi-host release artifacts

Consumers obtain the matching engines from the same versioned @lossless.org/nosqldb npm tarball on npmjs or Verdaccio. It includes dist_rust/rustdb_linux_amd64, dist_rust/rustdb_linux_arm64, dist_rust/rustdb_linux_amd64_musl, dist_rust/rustdb_linux_arm64_musl, dist_rust/rustdb_macos_amd64 and dist_rust/rustdb_macos_arm64, each accompanied by <binary>.tsrust-build.json. These provenance files contain binarySha256, the package version, Git commit and target. Verify the tarball's registry integrity and each executable's SHA-256 against that matching provenance file before installation. The TypeScript API selects static musl executables on all Linux hosts, including Alpine. The GNU-named Linux artifacts remain available for explicit selection and dynamically load replaceable system glibc, requiring a GNU/Linux host with glibc 2.34 or later, the matching loader, libm.so.6 and libgcc_s.so.1; The musl artifacts have no dynamic loader dependency. The native distribution terms and qualified library requirements are recorded in third-party notices. Spark can extract and install these files independently of its compiled JavaScript bundle; NoSQLDB does not download engines at startup.

A plain rsrepo build, as run by pnpm build and before pnpm test, compiles only the configured targets of the host's own architecture (its GNU and musl engines on Linux); rsrepo --configured-targets builds the host's complete partition from targetsByHost. The development suite checks the packaged engines of the host architecture. Releases use rsrepo matrix to coordinate both partitions after the version commit exists, then publish only after native verification passes and every exact-commit binary has executed and passed strict provenance assembly.

The release coordinator needs Git, Node.js, pnpm and SSH access to the configured macOS builder. The local Linux amd64 builder needs aarch64-linux-gnu-gcc, x86_64-linux-musl-gcc and aarch64-linux-musl-gcc and a registered Linux arm64 binfmt interpreter. Linux file storage requires kernel 5.6 or newer with openat2 enabled. Cross-architecture file qualification requires a full-system Linux guest or a native host. A linux-user executable probe does not qualify secure filesystem syscalls such as openat2. The built-package acceptance script test/verify-alpine.mjs exercises authenticated nested document persistence and restart through Node and Deno on Alpine; run it on both Linux architectures before publication. The Apple Silicon builder needs Rosetta. The matrix qualifies macOS first and then Linux; both partitions must pass before artifact assembly. Set these environment variables to authorized builders and their dedicated temporary roots:

export NOSQLDB_RELEASE_MACOS_SSH="release-user@mac-builder.example"
export NOSQLDB_RELEASE_MACOS_TEMP_ROOT="/absolute/dedicated/nosqldb-release"

gitzone release first runs pnpm run release:check-builders, before creating a version commit or tag. It verifies the project-local rsrepo version and rejects missing cross-toolchains or emulation, an unsuitable macOS host, missing tools, or an unsafe remote temporary root. The release build then:

  1. Builds the TypeScript package and debug UI bundle from the version commit.
  2. Transfers an exact Git bundle into isolated local Linux and SSH macOS worker workspaces and installs each frozen lockfile.
  3. Runs the configured release:verify-native command on each worker: the full Rust workspace, TypeScript test checks, a rsrepo --configured-targets build of the worker's partition, the public API suite with the packaged-engine checks for both architectures, and explicit-engine authentication/ownership/rehearsal tests through the worker's other packaged architecture. Tests use short, private disposable filesystem roots under /var/tmp on Linux and canonical /tmp on macOS. Linux builders must provide /var/tmp on a native-supported durable filesystem; tmpfs is rejected. Failures retain diagnostics and prevent assembly.
  4. Builds and executes GNU and musl Linux amd64/arm64 plus macOS arm64/amd64 binaries on their assigned workers.
  5. Retrieves bounded artifact sets and transactionally publishes the complete provenance-checked matrix into dist_rust/.

The qualified musl toolchains are cross-tools/musl-cross 20260823, GCC 16.2.0 with musl 1.2.6. Expose the upstream toolchain bin directories on PATH and alias their full *-unknown-linux-musl-gcc names to the compiler names above. Rust supplies its pinned, patched musl runtime; its notices and the cross-toolchain runtime notices are included with the binaries.

No host address or path is stored in the repository. A failed run retains its unique local diagnostics and owner-marked remote diagnostics, then prints their paths. Remote cleanup is an owner-validated publication gate, and an incomplete matrix never replaces the prior dist_rust/.

Storage Engine Reliability 🔒

  • Atomic native publications include data, indexes, authentication, fences and durable outcomes. File writes are acknowledged after durable commit.
  • Snapshots keep reads stable through concurrent writes and maintenance: merges, checkpoints and the cleaner free nothing a snapshot can reach.
  • Checksums, frame identities and contiguous positions validate committed history. Only a valid incomplete final append is repairable during owned startup; complete or interior corruption is rejected.
  • A device that loses acknowledged bytes at the end of the log and reads them back as zeros cannot be detected: recovery takes the zeros for the end of the log. The loss reaches back at most to the newest durable checkpoint's redo start. The commits it can lose hold at most half the buffer budget plus two holds, counted as their keys and values plus 64 bytes per record: one hold of commits, which is one commit or one group or batch of commits, and one background commit's claim on the buffer. The server sets the budget when it opens a root, to storageBudgetBytes where that option is set (4 MiB to 1 TiB), and otherwise to a sixteenth of the memory the process may use, from 16 MiB to 4 GiB: 64 MiB plus the two holds can be lost in a 2 GiB container, and at most 2 GiB plus the two holds unless the option sets a larger budget. Each hold is at most the batch limit, 64 MiB by default.
  • Uncertain publication fences the engine until confirmed recovery.
  • Bounded maintenance reclaims unreachable records, and segments that hold only superseded data, after their readers release them.
  • Disk use under live snapshots is unbounded: the store does not limit how long a snapshot stays open. A segment whose data has all been superseded stays on disk while a snapshot that can still read it is open, and serverStatus reports those bytes at serverStatus.engineFigures.pinnedBytes.
  • Writes are admitted at full speed until the free space falls to the store's floor: 64 MiB or two segments, whichever is more, beyond what the write buffer holds. There, every write but a delete or a drop is refused with the capacity code 16500 and writes nothing. Deletes and drops are still admitted, down to two segments, while no snapshot older than the newest write is open.
  • A durable session whose records this version cannot read does not stop maintenance. The obsolete-outcome sweep passes over it and leaves its records in place. It logs an error at most once a minute and counts the session in serverStatus.maintenance.unreadableSessions: skipped since start, lastPass, and the last session with its error and time.
  • Unix listener startup preserves active sockets and non-socket targets. Verified stale sockets are recoverable; cleanup checks the owned inode.

Data Integrity CLI 🔍

./dist_rust/rustdb_linux_amd64 --validate-data /path/to/data
# {"schemaVersion":1,"databases":[{"name":"mydb","collections":["users"]}]}

The CLI uses the same stopped native namespace inspector. It validates storage, catalogs and indexes while retaining the existing native leases, without starting a listener or repairing the source. It returns JSON on success and a nonzero exit on failure. No configuration file is required. The operation has a one-hour deadline, 100,000 filesystem-entry and 1 TiB byte limits, and bounded native record/result admission. This is storage integrity evidence; it does not replace the full authenticated rehearsal or production ownership protocol.

A failure names a stable code (NoSQLDB stopped inspection failed with code …). Two codes describe states the tool deliberately does not validate:

  • RECOVERY_REQUIRED while a stopped relocation or alignment has left its workspace in the root: finish that relocation or alignment, or abort the alignment, with its exact request, then validate again. A root of this release that a crash left needs no start first: the tool reads it as a start would recover it, writing nothing. A root that a 10.x process left without a clean stop also reports RECOVERY_REQUIRED; a start of this release converts it as of its last completed commit.
  • UNSUPPORTED while an index build is unfinished: an index that is building, quarantined, or importing into a collection that is not dropped. A start restarts interrupted builds from the beginning in the background, without waiting for them before it serves, so a root stopped before they publish still reports UNSUPPORTED, as does one whose database's publication hold keeps them back. A restarted build that fails, for example on duplicate keys, removes its indexes or restores the quarantined index it was rebuilding, which validation reports until that index is dropped or rebuilt.

Testing Example

The offline regression test/test.commit-journal-compatibility.node.ts reads test/format-2/compatibility.tar, a root that the published 10.8.1 release wrote once from the definition in test/format-2/roots.ts (test/format-2/archives.md records how). It checks every archive in test/format-2/ against SHA256SUMS, and each of its tests that reads the root extracts that archive into a new directory that only its owner can access, so the root it reads is a copy. It qualifies that the stopped audit and the namespace and string inspections read the 10.x copy without changing a byte: the audit finds it foreign and ineligible, with CONVERSION_PENDING, AUTH_USERS_PRESENT and the definition's two users, and the inspections give the definition's databases, collections and values. An existing-only start converts the copy, leaving only this release's names, and is refused because a copied root is served only after alignment; a second start is refused the same way and changes nothing. The converted root keeps the 10.x root's provider, group, timeline and LSN, its two users and its databases, collections and values, and alignment refuses it with UNSUPPORTED, as a dry run and for real, because it holds auth users; it writes no receipt and changes nothing. The test starts only this release's engine; build it normally before running the test.

The commit-journal lifetime regression uses the existing test-support executable and its three-second NOSQLDB_TEST_TRANSACTION_LIFETIME_MS setting. It compares expired transaction age with commit-call latency and verifies that expiry publishes neither documents nor journal entries. pnpm test builds this qualification executable through test:before; production keeps its unchanged 60-second transaction lifetime and the suite keeps its 60-second file timeout.

import { expect, tap } from '@git.zone/tsrepo/tapbundle';
import { NoSqlDbServer } from '@lossless.org/nosqldb';
import { MongoClient } from 'mongodb';

let server: NoSqlDbServer;
let client: MongoClient;

tap.test('setup', async () => {
  server = new NoSqlDbServer({ port: 27117 });
  await server.start();
  client = new MongoClient('mongodb://127.0.0.1:27117', { directConnection: true });
  await client.connect();
});

tap.test('should insert and find', async () => {
  const col = client.db('test').collection('items');
  await col.insertOne({ name: 'Widget', price: 9.99 });
  const item = await col.findOne({ name: 'Widget' });
  expect(item?.price).toEqual(9.99);
});

tap.test('should track changes in oplog', async () => {
  const oplog = await server.getOpLog();
  expect(oplog.entries.length).toBeGreaterThan(0);
  expect(oplog.entries[0].op).toEqual('insert');
});

tap.test('teardown', async () => {
  await client.close();
  await server.stop();
});

export default tap.start();

This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the license file.

Please note: The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.

Trademarks

This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.

Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.

Company Information

Task Venture Capital GmbH
Registered at District Court Bremen HRB 35230 HB, Germany

For any legal inquiries or further information, please contact us via email at hello@task.vc.

By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.

S
Description
No description provided
Readme MIT
19 MiB
0 Stars 3 Watchers 0 Forks
Languages
Rust 76%
TypeScript 14.5%
JavaScript 9.4%