@push.rocks/smartdb
v5.5.1
Published
A MongoDB-compatible embedded database server with wire protocol support, backed by a high-performance Rust engine.
Maintainers
Readme
@push.rocks/smartdb
A MongoDB-wire-compatible embedded database server powered by Rust 🦀⚡. It supports the documented command surface through the official mongodb driver without an external MongoDB server. No binary downloads, instant startup, zero config. Features a built-in operation log with point-in-time revert and a web-based debug dashboard.
Install
pnpm add @push.rocks/smartdb
# or
npm install @push.rocks/smartdbIssue 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.
What It Does
@push.rocks/smartdb 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.
Why SmartDB?
| | SmartDB | External DB Server |
|---|---|---|
| Startup time | ~30ms | ~2-5s |
| Binary download | Bundled (~7MB) | ~200MB+ |
| 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 ⏪ | Requires oplog tailing |
| Perfect for | Unit tests, CI/CD, prototyping, local dev, embedded | Production at scale |
Three Ways to Use It
- 🎯
LocalSmartDb— Zero-config convenience. Give it a folder path, get a persistent database over a Unix socket. Done. - 🏗️
SmartdbServer— Full control. Configure port, host, storage backend, Unix sockets. Great for test fixtures or custom setups. - 🖥️
SmartdbDebugServer— Launch a web dashboard to visually browse collections, inspect the operation log, and revert to any point in time.
Architecture: TypeScript + Rust 🦀
SmartDB uses a sidecar binary pattern — TypeScript handles lifecycle, Rust handles all database operations:
┌──────────────────────────────────────────────────────────────┐
│ Your Application │
│ (TypeScript / Node.js) │
│ ┌──────────────────┐ ┌───────────────────────────┐ │
│ │ SmartdbServer │─────▶│ RustDbBridge (IPC) │ │
│ │ or LocalSmartDb │ │ @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 │ │
│ └─────────┘ └────────┘ └───────────┘ └─────────────┘ │
│ │
│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────┐ │
│ │ MemoryStorage │ │ FileStorage │ │ 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: LocalSmartDb (Zero Config) 🎯
The fastest way to get a persistent local database:
import { LocalSmartDb } from '@push.rocks/smartdb';
import { MongoClient } from 'mongodb';
// Point it at a folder — that's it
const db = new LocalSmartDb({ 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: '[email protected]' });
const user = await users.findOne({ name: 'Alice' });
console.log(user); // { _id: ObjectId(...), name: 'Alice', email: '[email protected]' }
// Data persists to disk automatically — survives restarts!
await client.close();
await db.stop();Option 2: SmartdbServer (Full Control) 🏗️
import { SmartdbServer } from '@push.rocks/smartdb';
import { MongoClient } from 'mongodb';
// TCP mode
const server = new SmartdbServer({ 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 { SmartdbServer } from '@push.rocks/smartdb';
import { SmartdbDebugServer } from '@push.rocks/smartdb/debugserver';
const server = new SmartdbServer({ storage: 'memory' });
await server.start();
const debugServer = new SmartdbDebugServer(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
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 64 MiB, whichever is hit first; configurable via the oplog server option). Once a limit is exceeded, the oldest entries are evicted. 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 { SmartdbServer } from '@push.rocks/smartdb';
const server = new SmartdbServer({ 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) |
✋ 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.
Two classes are skipped:
- Identical — the post-image equals the stored document byte for byte.
- Volatile-only — the post-image differs only in top-level volatile metadata fields (currently
_updatedAt, the field ODM layers such as@push.rocks/smartdatarestamp on every save). The stored document is kept as-is, including its existing_updatedAt, so the timestamp means "last real change" rather than "last save call".
This makes periodic reconcile loops that re-save unchanged documents cost nothing at the engine level. A caller that needs a document to actually change must change a non-volatile field.
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: 271, // only volatile metadata differed
// },
// }API Reference
SmartdbServer
The core server class. Manages the Rust database engine and exposes connection details.
Constructor Options (ISmartdbServerOptions)
import { SmartdbServer } from '@push.rocks/smartdb';
// TCP mode (default)
const server = new SmartdbServer({
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 SmartdbServer({
socketPath: '/tmp/smartdb.sock',
storage: 'file',
storagePath: './data',
});
// Memory storage with periodic persistence
const server = new SmartdbServer({
storage: 'memory',
persistPath: './data/snapshot.json',
persistIntervalMs: 30000, // Save every 30s
});
// Bounded in-memory oplog retention
const server = new SmartdbServer({
port: 27017,
oplog: {
maxEntries: 50000, // Default: 10000
maxBytes: 128 * 1024 * 1024, // Default: 64 MiB
},
});
// TLS transport for TCP mode
const tlsServer = new SmartdbServer({
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 SmartdbServer({
port: 27017,
auth: {
enabled: true,
usersPath: './data/smartdb-users.json', // Optional: persists derived SCRAM credentials
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:[email protected]: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, plus readAnyDatabase, readWriteAnyDatabase, dbAdminAnyDatabase, and userAdminAnyDatabase. When usersPath is set, SmartDB persists SCRAM credential material atomically and does not store plaintext passwords. Auth metadata version 3 contains protected decoy material, an immutable persisted SCRAM profile, allocation read-grant principals, and bounded anti-replay state. auth.scramIterations must match the profile already established by that usersPath on every later startup and in every attached process; SmartDB fails closed on a mismatch rather than attempting to rederive credentials without plaintext passwords. SmartDB 4.0.0 startup persistently rewrites exact version 2 owner metadata to version 3 without changing existing owner SCRAM material, principal identities, generations, or roles. This is a one-way major-version compatibility boundary: after migration, SmartDB 3.x must not be restarted against that usersPath. Unsupported versions and malformed auth or grant metadata fail closed.
On Linux, startup performs one narrow compatibility migration before the Rust engine opens usersPath: an effective-user-owned, single-link regular file with the exact legacy mode 0644 is changed to 0600. Current 0600 files are left unchanged. Symlinks, hard links, unexpected modes or ownership, unsafe parent directories, cross-device targets, and identity changes remain fail-closed errors.
Legacy v0 JSON collections are converted into hidden sibling staging directories. SmartDB fsyncs every generated file and the staging directory before atomically publishing a complete v1 collection, while the v0 files remain unchanged. Cancellation removes unpublished staging and preserves the v0 input. Startup fails closed if it finds staging left by an unclean process exit or an incomplete published target, so neither state is mistaken for a complete migration.
Persisted users also carry a random principal identity and a monotonic generation. SmartDB reloads and resolves that identity for every authenticated command, so password or role changes take effect immediately and stale sockets are rejected. Deleting and recreating the same username creates a different principal; an old connection cannot inherit the replacement user's authority. Cross-process user updates are serialized through the persisted users-file lock.
Single-node transactions are supported through official MongoDB driver sessions. find, count, distinct, insert, update, findAndModify, and delete use the transaction snapshot and buffered write set; commitTransaction applies that write set with conflict checks, and abortTransaction discards it. The snapshot is database-wide: the first namespace a transaction materializes pins a publication sequence on the namespace clock, and any later namespace that was published after that sequence fails closed with SnapshotUnavailable (code 246) and the TransientTransactionError label so the driver restarts the transaction on fresh state; write-write conflicts surface at commit as WriteConflict (code 112) with the same label. Each session remembers the outcome of its last transaction number: a repeated commitTransaction replays ok, abortTransaction after a commit reports TransactionCommitted (code 256), commitTransaction after an abort reports NoSuchTransaction (code 251), and starting a transaction with an older number reports TransactionTooOld (code 225). After applicable authentication, authorization, and allocation-policy checks, collection, database, and index DDL, user-management mutations, and aggregate pipelines ending in $out or $merge reject transaction envelopes with OperationNotSupportedInTransaction (code 263) before session or transaction creation, maintenance gates, or mutation. Live logical sessions remain resumable across socket disconnects. Bounded background cleanup aborts expired transactions, removes expired sessions, and releases publication leases; explicit endSessions and killSessions do the same for their active transactions.
Durable Database Publication Holds
On Linux, file-backed SmartDB 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('SmartDB 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 { ISmartDbHeldPublicationReceipt } from '@push.rocks/smartdb';
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 ISmartDbHeldPublicationReceipt;
await persistAndFsyncDownstreamState(receipt);
const released = await server.commitDatabasePublication({
databaseName: 'tenant_a',
resourceFence: receipt,
});The held barrier survives restart and blocks wire commands, transactions, startup recovery, compaction, and index restoration for that database while unrelated databases remain available. Commit is exact and idempotent. A successful commit removes the raw capability from durable state and retains only protected verification material. Provider/root identity, durable fenced-mode markers, and bounded startup validation make copied, missing, corrupt, or legacy-active publication 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 remain Linux-only. The macOS support described under stopped storage-root relocation does not enable these runtime mutation features.
Coordinators that do not yet have their own durable record can call getDatabaseResourceFenceState({ databaseName }) before creating it. File-backed SmartDB returns the durable scope, highest token, current publication phase, and allocation lifecycle/identity when the name is allocation-managed, or null only when both fence state and its durable marker are absent. 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 ISmartDbResourceFence, and the tenant database, username, and password. roles is optional and defaults to ['readWrite', 'dbAdmin']. SmartDB proves database-root and auth-principal absence while holding the database resource lock, maintenance write gate, and a bounded cross-process reservation on the durable auth store, then returns an ISmartDbAllocateDatabaseTenantResult; its .allocation field is the durable ISmartDbDatabaseAllocationIdentity. 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 persists deprovisioning, durably deletes the exact marked root, atomically removes only the exact principal, then persists a permanent deprovisioned 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; database/user ownership-changing wire commands are rejected.
The provider marker .__rustdb_allocation.json is created and preserved by SmartDB, never accepted from an import payload, and never included in logical exports. The resource lock uses the distinct smartdb-resource-allocation-managed-v1 marker. Pre-allocation binaries, including 2.18.1, interpret that lock marker as invalid and fail closed. Consequently, N-1 rollback of a storage root after allocation fencing has touched a name is intentionally unavailable; restore the allocation-aware binary rather than removing or editing provider metadata.
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 always holds read-only local and maintenance guards. File-backed storage independently supplies the cross-process publication guard. A durable usersPath independently supplies a shared auth guard while current durable auth state is read. Memory storage and auth without a usersPath use their configured in-process providers. 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, a durable usersPath bound to the storage root, and ready allocation fencing. The caller supplies databaseName, a bounded grantId, a bounded unique username, password, and the complete ISmartDbDatabaseAllocationIdentity. Roles and expiry are not caller-controlled. SmartDB 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. SmartDB 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.
Grant-specific rejections from the issue and revoke methods are normalized automatically as SmartDbAllocationReadAccessGrantError with stable EGRANT_INVALID_REQUEST, EGRANT_CREDENTIAL_MISMATCH, EGRANT_CONFLICT, EGRANT_REPLAY_CAPACITY, or EGRANT_STATE_UNAVAILABLE codes. The companion exports TSmartDbAllocationReadAccessGrantErrorCode, smartDbAllocationReadAccessGrantErrorCodes, and normalizeSmartDbAllocationReadAccessGrantError(error, code) support type-safe handling and explicit normalization of structured bridge errors. Allocation and resource-fence failures retain their existing SmartDbResourceFenceError codes. Callers should classify errors by these exported classes and codes rather than by message text.
Active allocation read grants never survive a SmartDB 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.
exportDatabase() drains live MongoDB wire commands for the selected database and holds an exclusive local and cross-process database lease while producing the snapshot. The export is therefore consistent across all collections. It enforces server ceilings while visiting documents instead of first materializing an unbounded database; callers can request lower maxEncodedBytes, maxCollections, maxDocuments, and maxIndexes limits. The result emits canonical MongoDB Extended JSON so every BSON type, including 64-bit integers, survives the JSON management channel exactly. Restricted views are carried only through the physical system.views catalog and are never materialized. importDatabase() accepts canonical or relaxed Extended JSON, validates a candidate catalog before mutation, and remaps exact system.views._id source-database prefixes when importing under another database name. Database migration clients should preserve the exported objects as JSON values and must not coerce Extended JSON numeric wrappers into JavaScript numbers.
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. ISmartDbGetDatabaseContentDigestInput accepts databaseName and optional ISmartDbDatabaseContentDigestLimits 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 ISmartDbDatabaseContentDigest result reports format smartdb.database.content-digest.v1, algorithm sha256, the lowercase digest, and exact scan counters.
SmartdbServer.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 ISmartDbManagementOperationOptions 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 legacy storage migration, sidecar spawn, auth metadata migration, and database readiness. Cancellation and deadlines are fail-stop once the sidecar is owned: SmartDB 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 SmartDB 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<void> | Wait for active startup work, then stop the server and confirm Rust bridge cleanup, including partial startup cleanup |
| 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<ISmartDbMetrics> | Server metrics (db/collection counts, sessions, transactions, auth, uptime) |
| getOpLog(params?) | Promise<IOpLogResult> | Query oplog entries with optional filters |
| 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<ISmartDbHealth> | Read readiness and explicit publication-hold capability fields with optional fail-stop cancellation/deadline ownership |
| allocateDatabaseTenant(params, options?) | Promise<ISmartDbAllocateDatabaseTenantResult> | Authoritatively allocate an absent file-backed tenant and return its durable allocation identity; supports fail-stop cancellation and deadlines |
| attestDatabaseTenant(params, options?) | Promise<ISmartDbAttestDatabaseTenantResult> | Verify exact existing tenant ownership, roles, and candidate credential without mutating provider, auth, fence, or runtime cache state |
| issueDatabaseAllocationReadAccessGrant(params, options?) | Promise<ISmartDbIssueDatabaseAllocationReadAccessGrantResult> | Issue or exactly replay one fixed 12-minute read grant bound to the complete active allocation identity |
| revokeDatabaseAllocationReadAccessGrant(params, options?) | Promise<ISmartDbRevokeDatabaseAllocationReadAccessGrantResult> | Idempotently revoke/tombstone one exact allocation read grant and invalidate its live runtime state |
| ensureDatabaseTenant(params, options?) | Promise<ISmartDbEnsureDatabaseTenantResult> | Idempotently ensure an exact fenced tenant; supports fail-stop cancellation and deadlines |
| getDatabaseResourceFenceState(params, options?) | Promise<ISmartDbDatabaseResourceFenceState \| 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<ISmartDbDatabaseContentDigest> | 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<ISmartDbDatabaseExport> | Export one database as lossless canonical MongoDB Extended JSON; supports fail-stop cancellation and deadlines |
| importDatabase(params, options?) | Promise<ISmartDbImportDatabaseResult> | 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<ISmartDbDeleteDatabaseTenantResult> | Durably delete an exact tenant database/user, optionally leaving publication held; supports fail-stop cancellation and deadlines |
| commitDatabasePublication(params, options?) | Promise<TSmartDbCommitDatabasePublicationResult> | Idempotently release an exact held publication receipt; supports fail-stop cancellation and deadlines |
LocalSmartDb
Zero-config wrapper around SmartdbServer. Uses Unix sockets and file-based persistence.
Constructor Options (ILocalSmartDbOptions)
import { LocalSmartDb } from '@push.rocks/smartdb';
const db = new LocalSmartDb({
folderPath: './data', // Required: data storage directory
socketPath: '/tmp/custom.sock', // Optional: custom socket (default: auto-generated)
});Methods & Properties
| Method / Property | Type | Description |
|---|---|---|
| start() | Promise<ILocalSmartDbConnectionInfo> | Start and return connection info |
| stop() | Promise<void> | Stop the server |
| getConnectionInfo() | ILocalSmartDbConnectionInfo | Get current connection info |
| getConnectionUri() | string | Get the connection URI |
| getServer() | SmartdbServer | Access the underlying server |
| running | boolean | Whether the server is running |
| LocalSmartDb.relocateStoppedStorageRoot(input, options?) | Promise<ILocalSmartDbStoppedStorageRootRelocationReceipt> | Atomically relocate one eligible stopped Linux or macOS file-storage root and retain a source receipt |
| LocalSmartDb.inspectOfflineStringValue(input, options?) | Promise<TLocalSmartDbOfflineStringValueInspectionResult> | Read one exact top-level string value from stopped file storage without starting or mutating the engine |
| LocalSmartDb.inspectOfflinePhysicalNamespaces(input, options?) | Promise<ILocalSmartDbOfflinePhysicalNamespaceInspectionResult> | 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 { LocalSmartDb } from '@push.rocks/smartdb';
const receipt = await LocalSmartDb.relocateStoppedStorageRoot({
sourceFolderPath: '/var/lib/myapp/harness-controller/smartdb',
destinationFolderPath: '/var/lib/myapp/hcon/smartdb',
relocationId: 'move-2026-08-17-1',
}, {
timeoutMs: 30_000,
});
// Start only the destination. The source is now the retained receipt file.
const db = new LocalSmartDb({ 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. The normalized destination path may not be longer in UTF-8 bytes than the normalized source path, so every root-relative path accepted by SmartDB 5.0.1 at the source remains no longer after relocation. 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 SmartDB management operations.
The returned exact receipt has this shape:
interface ILocalSmartDbStoppedStorageRootRelocationReceipt {
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. SmartDB does not remove that source receipt. Data, WAL, hint, and index bytes are not copied or rewritten. providerRootId, root device/inode, and every provider/sentinel field are preserved; only storageRootSha256 changes to the destination's canonical path digest. This leaves a completed destination compatible with SmartDB 5.0.1.
Relocation deliberately supports only the plain, unbound LocalSmartDb topology. The existing owner lock, provider-identity.json, and resource-fencing sentinel must all be present; the operation never creates missing ownership metadata. A provider identity containing authUsersPathSha256 is unsupported. One narrow terminal transaction topology is also eligible: each retained internal transaction journal must be in the exact cleaned phase, contain no workspace debris or allocation binding, and match the exact transaction-publication marker in its database. Canonically named, empty retired legacy fence locks are accepted only when their resource digest belongs to one of those terminal transactions and their encoded inode matches the retained file. These terminal artifacts move unchanged with the root and continue the existing transaction token sequence after restart. Any unfinished or mismatched transaction, durable database-mutation journal, active fence receipt or capability, allocation state, unrelated or malformed retired lock, allocation/general publication marker, symlink, unknown entry, unsafe mode/owner/link count, exhausted scan bound, active owner, or ambiguous identity fails closed.
The operation journals phases preparing, prepared, exchanged, providerPublished, sentinelPublished, and cleaned. If a call returns RECOVERY_REQUIRED, stop all startup attempts and retry the exact same normalized source path, destination path, and relocationId with the same SmartDB version that began the relocation or a newer version. Startup rejects any relocation-workspace occupancy and does not mutate or recover it. Do not delete, rename, edit, or replace either path between attempts. Linux relocation state created by SmartDB 5.1.0 remains recoverable by 5.1.0 or newer; macOS recovery requires a macOS-capable release. A completed root remains 5.0.1-compatible.
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 remains unsupported and continues to fail provider path/inode fencing.
Failures are exposed as LocalSmartDbStorageRootRelocationError with a code-only union: INVALID_REQUEST, UNSUPPORTED, BUSY, NOT_FOUND, DESTINATION_CONFLICT, IDENTITY_MISMATCH, STATE_CORRUPT, IO, or RECOVERY_REQUIRED. Error messages and causes do not include supplied paths. Timeout, abort, write, exit, or malformed-response failures after command dispatch are always RECOVERY_REQUIRED; exact retry is the only recovery action.
Offline String-Value Inspection
inspectOfflineStringValue() is a Linux-only operational API for reading one metadata value while the owning LocalSmartDb engine is stopped. Other platforms reject the call because the reader requires Linux openat2 descriptor traversal. It starts only the Rust management sidecar: it does not start a database listener, run storage migrations, repair tails, replay or truncate the WAL, compact data, or persist hints.
import { LocalSmartDb } from '@push.rocks/smartdb';
const result = await LocalSmartDb.inspectOfflineStringValue({
folderPath: '/var/lib/myapp/smartdb',
databaseName: 'myapp',
collectionName: 'MetadataDoc',
match: {
field: 'key',
value: 'migrationStateV1',
},
valueField: 'value',
limits: {
maximumDataFileBytes: 16 * 1024 * 1024,
maximumWalFileBytes: 16 * 1024 * 1024,
maximumRecords: 100_000,
maximumRecordBytes: 1024 * 1024,
maximumResultBytes: 1024 * 1024,
},
}, {
timeoutMs: 5000,
});
if (result.status === 'found') {
console.log(result.value);
}The result is only { status: 'not-found' } or { status: 'found', value: string }. No other document fields cross the management boundary. The reader validates descriptor-safe paths, the current data/WAL format, CRCs and live-record semantics, applies any bounded uncommitted WAL overlay in memory, and rejects ambiguous matches, corruption, exceeded limits, symlinks, an engine-owned storage root, or files that change during inspection.
The TypeScript API enforces these bounds before creating the sidecar or serializing the IPC request:
| Input | Enforced range |
|---|---|
| folderPath | 1 to 4096 UTF-8 bytes before and after absolute resolution; no control characters |
| databaseName, collectionName | 1 to 255 UTF-8 bytes; one canonical path component; no controls, slash, or backslash |
| match.field, valueField | 1 to 255 UTF-8 bytes; no control characters |
| match.value | 0 to 1,048,576 UTF-8 bytes and no larger than maximumRecordBytes |
| Complete serialized request | At most 2,097,152 bytes |
| timeoutMs | 1 to 2,147,483,647 milliseconds |
| maximumDataFileBytes | 64 to 268,435,456 bytes |
| maximumWalFileBytes | 64 to 67,108,864 bytes |
| maximumRecords | 1 to 1,000,000 data/WAL items |
| maximumRecordBytes | 22 to 17,825,792 bytes |
| maximumResultBytes | 1 to 16,777,216 bytes |
All numeric limits must be positive safe integers. The caller must stop and retain external ownership of the LocalSmartDb daemon for the full call. Current storage roots additionally enforce this through SmartDB's storage-owner lock. Roots created before that lock existed rely on the caller's external single-owner coordination plus descriptor and stability validation. One timeoutMs budget covers sidecar lookup/spawn, readiness, and the command. Cancellation or timeout kills and reaps the disposable sidecar before the call rejects.
Offline Physical Namespace Inspection
inspectOfflinePhysicalNamespaces() is a Linux-only management API for validating and listing the physical database and collection namespaces in a stopped SmartDB file-storage root. It uses Linux openat2 descriptor traversal and starts only the disposable Rust management sidecar. It never calls SmartdbServer.start(), opens a database listener, initializes the storage adapter, runs migration or recovery, repairs a tail, truncates a WAL, compacts data, or persists a hint.
import { LocalSmartDb } from '@push.rocks/smartdb';
const namespaces = await LocalSmartDb.inspectOfflinePhysicalNamespaces({
folderPath: '/var/lib/myapp/smartdb',
limits: {
maximumEntries: 100_000,
maximumDatabases: 4096,
maximumCollections: 10_000,
maximumIndexes: 100_000,
maximumDataFileBytes: 256 * 1024 * 1024,
maximumWalFileBytes: 64 * 1024 * 1024,
maximumIndexFileBytes: 16 * 1024 * 1024,
maximumTotalFileBytes: 1024 * 1024 * 1024 * 1024,
maximumRecordsPerCollection: 1_000_000,
maximumRecordBytes: 17 * 1024 * 1024,
maximumResultBytes: 16 * 1024 * 1024,
},
}, {
timeoutMs: 30_000,
});
// {
// schemaVersion: 1,
// databases: [
// { name: 'myapp', collections: ['MetadataDoc', 'system.views'] },
// ],
// }The result is exactly { schemaVersion: 1, databases: Array<{ name, collections }> }. Databases and each database's collections are sorted by physical name, empty database directories are retained, and no documents, index definitions, record identifiers, or values cross the management boundary.
The accepted root must contain the current .__rustdb_internal/storage-owner.lock; legacy roots without that lock are rejected. The sidecar takes the shared owner lock, so an active engine causes the call to fail. Every reported collection must have the canonical complete profile of regular data.rdb, wal.rdb, and indexes.json files. A regular keydir.hint is optional and is treated only as an allowed legacy cache file; runtime recovery ignores its contents. Missing canonical files, unexpected collection entries, malformed index metadata, invalid data or WAL records, symlinks, cross-filesystem traversal, resource-limit exhaustion, and changes observed during inspection fail the complete operation. No partial result is returned and the storage tree is not mutated.
Inspection performs two full bounded passes and returns only if their namespace results and retained filesystem observations agree. The entry, database, collection, index, file-byte, and record limits are enforced independently on each pass, so total validation work is at most twice the corresponding caller cap. The result-size cap applies to the single serialized result. This is a contract for the current owner-locked canonical SmartDB file-storage profile, not a claim of compatibility with arbitrary MongoDB layouts, legacy SmartDB roots, or unspecified future storage formats.
All limits are required; this API supplies no defaults or compatibility aliases. TypeScript validates the following bounds before creating the sidecar and revalidates folderPath after resolving it to an absolute path:
| Input | Enforced range |
|---|---|
| folderPath | 1 to 4096 UTF-8 bytes before and after absolute resolution; no control characters |
| Complete serialized request | At most 2,097,152 bytes |
| timeoutMs | 1 to 2,147,483,647 milliseconds |
| maximumEntries | 1 to 100,000 directory entries per pass |
| maximumDatabases | 1 to 4,096 databases per pass |
| maximumCollections | 1 to 10,000 collections per pass |
| maximumIndexes | 1 to 100,000 index definitions per pass |
| maximumDataFileBytes | 64 to 268,435,456 bytes per data file |
| maximumWalFileBytes | 64 to 67,108,864 bytes per WAL file |
| maximumIndexFileBytes | 2 to 16,777,216 bytes per index file |
| maximumTotalFileBytes | 1 to 1,099,511,627,776 aggregate canonical collection-file bytes per pass |
| maximumRecordsPerCollection | 1 to 1,000,000 data records plus WAL records/commit markers per collection per pass |
| maximumRecordBytes | 22 to 17,825,792 bytes per record |
| maximumResultBytes | 32 to 16,777,216 serialized result bytes |
Every numeric value must be a safe integer. One optional timeoutMs budget and AbortSignal cover sidecar lookup/spawn, readiness, both inspection passes, and response delivery; timeout or cancellation kills and reaps the sidecar before rejection. For a point-in-time ownership proof, the caller must exclude every engine start and other storage lifecycle change continuously from before invocation through acceptance or handoff of the returned result. The owner lock proves that the engine was stopped during inspection, but it cannot replace that caller-held lifecycle exclusion after the sidecar releases its lock.
SmartdbDebugServer
Web-based debug dashboard served via @api.global/typedserver. Import from the debugserver subpath:
import { SmartdbDebugServer } from '@push.rocks/smartdb/debugserver';
const debugServer = new SmartdbDebugServer(server, { port: 4000 });
await debugServer.start();
// Dashboard at http://localhost:4000
await debugServer.stop();The UI is bundled as base64-encoded content (via @git.zone/tsbundle) and served from memory — no static file directory needed.
SmartdbDebugUi (Web Component)
For embedding the debug UI directly into your own web application, import the <smartdb-debugui> web component:
import { SmartdbDebugUi } from '@push.rocks/smartdb/debugui';
// In your HTML/lit template:
// <smartdb-debugui .server=${mySmartdbServer}></smartdb-debugui>
//
// Or in HTTP mode (when served by SmartdbDebugServer):
// <smartdb-debugui apiBaseUrl=""></smartdb-debugui>Supported Operations
SmartDB 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
// Comparison
{ age: { $eq: 25 } } { age: { $ne: 25 } }
{ age: { $gt: 18 } } { age: { $lt: 65 } }
{ age: { $gte: 18 } } { age: { $lte: 65 } }
{ status: { $in: ['active', 'pending'] } }
{ status: { $nin: ['deleted'] } }
// Logical
{ $and: [{ age: { $gte: 18 } }, { active: true }] }
{ $or: [{ status: 'active' }, { admin: true }] }
{ $not: { status: 'deleted' } }
// Element
{ email: { $exists: true } }
{ type: { $type: 'string' } }
// Array
{ tags: { $all: ['mongodb', 'database'] } }
{ scores: { $elemMatch: { $gte: 80, $lt: 90 } } }
{ tags: { $size: 3 } }
// Regex
{ name: { $regex: /^Al/i } }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
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
$lookup supports the equality form and a bounded correlated pipeline form with
let, an optional $match using $expr/$and/$eq, and an optional following
$limit. Correlated string equality predicates can use a full-key or subset
foreign equality index to reduce candidates when available. Candidates are
paged, the complete expression remains authoritative, and $limit counts only
exact expression matches. Other BSON value shapes use the bounded scan path.
Mixed localField/foreignField plus pipeline lookups and other inner stages
are rejected explicitly. Complete filter trees are validated before data is
read, and query nesting plus aggregation work are bounded.
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
DuplicateKeyerror (code 11000) before the document is written to disk — oninsertOne,updateOne,findAndModify, and upserts. Index definitions are persisted toindexes.jsonand automatically restored on restart.
SmartDB supports ascending and descending index keys with name, unique,
sparse, and expireAfterSeconds options. It accepts background as a
validated no-op and index version v: 2. Unsupported options, including
partialFilterExpression, collation, and hidden, are rejected before any
index in the request is created. TTL metadata is retained in the catalog;
automatic TTL expiry is not implemented.
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();SmartDB creates ordinary collections and one deliberately restricted view form. Capped collections, validators, 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.
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. As in MongoDB,
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.
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, listDatabases, listCollections, drop, dropDatabase, create, serverStatus, dbStats, collStats, connectionStatus, renameCollection |
Advertises MongoDB wire protocol versions 0–21. The documented command surface is tested with the official mongodb Node.js driver version 7.5.x.
OP_MSG parsing rejects unknown required flags, invalid CRC-32C checksums,
duplicate BSON keys, noncanonical array indexes, malformed section boundaries,
multiple body sections, duplicate or colliding document-sequence identifiers,
and missing or non-string $db fields. Early command-envelope validation then
accepts document sequences only as insert.documents, update.updates, or
delete.deletes; empty, oversized, extra, mismatched, and body-colliding forms
reject before authentication or command resources are acquired. All three accepted
forms are consumed as the corresponding bulk command argument. Unknown optional
flags are ignored. A message is limited to 48,000,000 bytes, each BSON document to
16,777,216 bytes, and BSON nesting to 100 levels. One message may contain at most
128 sections, 10,000 document-sequence documents, 100,000 BSON elements, and
100,000 BSON document or array containers. The advertised maxWriteBatchSize is
therefore 10,000.
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.
Raw receive buffers and parsed requests are charged separately against a process-wide 256 MiB encoded-byte admission budget. A partial frame is charged by its incrementally reserved receive capacity, not by the length in its prefix. Receive leases retain their high-water charge until the buffer is dropped or successfully compacted, and every allocation growth is reconciled before another socket read. Large unread pipelines remain charged and are compacted only at geometric utilization thresholds; the budget is an encoded-input bound rather than a claim about exact allocator heap usage.
Read and Write Concerns and Wire Deadlines
SmartDB 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 fsynced to the write-ahead log
and the data file before the response is sent, so
w: 1,w: "majority"(the majority of one voter is that voter), andj: trueare all satisfied by the same durable commit.w: 0is acknowledged the same way whenever a response is expected; the exactwriteConcern: { w: 0 }endSessionsandkillSessionsenvelopes still run without a transport response.wtimeoutbounds a replication wait that does not exist on one node and has no additional effect.w: NforN > 1fails withUnsatisfiableWriteConcern(code 100), tag-set modes fail withUnknownReplWriteConcern(code 79), andj: truefails withBadValue(code 2) on the in-memory storage backend, which does not journal. Write concern is accepted
