MongoDB Adapter for livedb / sharedb
livedb-mongo is a database adapter for `livedb` and its successor, `sharedb`, providing persistent storage and an oplog implementation using MongoDB. While historically named `livedb-mongo`, the project's development has transitioned to primarily support `sharedb`, with its GitHub repository now `sharedb-mongo`. The package is currently at v5.1.0 and maintains an active release cadence, frequently updating to support newer Node.js and MongoDB versions. It stores document snapshots directly in named collections and operations in `COLLECTION_ops`, enabling direct MongoDB queries against the unwrapped JSON documents, which include internal versioning fields (`_v`, `_type`). This adapter is crucial for enabling real-time collaborative applications built with `livedb` or `sharedb`, ensuring operational transformation (OT) works correctly with MongoDB as the backend. It explicitly warns against direct database manipulation outside of the `livedb`/`sharedb` API to prevent data corruption.
Common errors
-
MongoServerSelectionError: connect ECONNREFUSED
cause The MongoDB server is not running or is not accessible at the specified connection URL.fixEnsure your MongoDB instance is running and accessible from your application's environment. Verify the connection string and firewall rules. -
Error: Document already exists. Cannot create.
cause Attempted to `db.create` a document with an `_id` that already exists in the collection.fixBefore creating, use `db.fetch` to check if the document exists (`snapshot.v === 0`). If it does, use `db.apply` to update it, or choose a unique `_id`. -
Error applying operation: Invalid op
cause The operational transform (OT) operation applied via `db.apply` is malformed or incompatible with the document's current type/data.fixReview the structure of your OT operation. Ensure it correctly targets document paths and uses valid transformations for the document type (e.g., 'json0' for JSON documents). -
Application hangs when using $map query transform
cause A bug in older versions of the adapter (prior to v4.1.1) caused `$map` queries to hang indefinitely.fixUpgrade `livedb-mongo` to version 4.1.1 or newer. This issue was fixed in PR #154.
Warnings
- breaking Version 5.0.0 of `livedb-mongo` (which is effectively `sharedb-mongo`) dropped support for Node.js v16. Users on Node.js v16 or older must upgrade their Node.js environment to v18 or newer.
- breaking Version 4.0.0 dropped support for Node.js v14. Subsequent versions require Node.js v16 or higher.
- breaking Version 3.0.0 deprecated and subsequently dropped support for `mongodb@2`. Ensure your MongoDB database and driver are at version 3 or newer.
- gotcha Editing documents directly in MongoDB outside of the `livedb` (or `sharedb`) API can lead to data corruption or 'weird behaviour' due to the operational transformation (OT) system's reliance on specific document fields and operation sequencing. The adapter adds internal fields like `_v` and `_type` that must be managed by the library.
- gotcha When using the `disableIndexCreation` option for the `src_seq_v` index (introduced in v4.2.0), existing indexes are not automatically removed. If you disable an index that previously existed, you must manually delete it from your MongoDB collection if it's no longer desired.
- gotcha Older versions (prior to v4.1.1) had a memory leak when using cursor operations like `$count` or `$explain` with `mongodb@4-6`. This could lead to resource exhaustion in long-running applications.
Install
-
npm install livedb-mongo -
yarn add livedb-mongo -
pnpm add livedb-mongo
Imports
- ShareDbMongo
import { ShareDbMongo } from 'livedb-mongo';import ShareDbMongo from 'livedb-mongo'; // For ESM // Or for CommonJS: const ShareDbMongo = require('livedb-mongo'); - SharedbMongoOptions
import { SharedbMongoOptions } from 'livedb-mongo';import type { SharedbMongoOptions } from 'livedb-mongo'; - Db
import { Db } from 'mongodb'; // When passing an existing MongoDB Db instance
Quickstart
const ShareDbMongo = require('livedb-mongo'); // Or import ShareDbMongo from 'livedb-mongo'; for ESM
const livedb = require('livedb'); // This adapter is for livedb (and sharedb)
// MongoDB connection string. Ensure MongoDB is running locally.
// Replace with your actual MongoDB connection string in production.
const mongoUrl = process.env.MONGO_URL || 'mongodb://localhost:27017/livedb_test_db';
// Initialize the livedb-mongo adapter (also known as sharedb-mongo)
// The second argument is for MongoDB driver options.
const mongoAdapter = new ShareDbMongo(mongoUrl, {
// Modern driver options, crucial for recent MongoDB versions
useUnifiedTopology: true,
// replicaSet can be important for change streams if using sharedb's oplog features
// replicaSet: 'rs0'
});
// Initialize livedb client with the mongo adapter
const db = livedb.client(mongoAdapter);
const collection = 'documents';
const docId = 'exampleDoc';
console.log(`Attempting to connect to MongoDB at ${mongoUrl} via livedb-mongo...`);
// Try to fetch a document. If it doesn't exist, create it.
db.fetch(collection, docId, (err, snapshot) => {
if (err) {
console.error('Error fetching document:', err);
mongoAdapter.close(); // Ensure connection is closed on error
return;
}
if (snapshot.v === 0) { // Document does not exist (version is 0)
console.log(`Document '${docId}' not found. Creating it...`);
const initialData = { title: 'Hello World', content: 'This is the initial version.', counter: 0 };
db.create(collection, docId, 'json0', initialData, (createErr) => {
if (createErr) {
console.error('Error creating document:', createErr);
} else {
console.log(`Document '${docId}' created successfully with data:`, initialData);
}
mongoAdapter.close(); // Close connection after operation
});
} else {
console.log(`Document '${docId}' found (v${snapshot.v}):`, snapshot.data);
// Example: Apply an operation to update the document (e.g., increment counter)
const op = { p: ['counter'], na: 1 }; // Operational Transform: Increment 'counter' by 1
db.apply(collection, docId, op, { source: 'example_script', version: snapshot.v + 1 }, (applyErr) => {
if (applyErr) {
console.error('Error applying operation:', applyErr);
} else {
console.log('Operation applied successfully: counter incremented.');
// Fetch again to see the updated state
db.fetch(collection, docId, (fetchUpdatedErr, updatedSnapshot) => {
if (fetchUpdatedErr) console.error('Error fetching updated document:', fetchUpdatedErr);
else console.log('Updated document data:', updatedSnapshot.data);
mongoAdapter.close();
});
}
});
}
});