For AI agents: the complete documentation index is available at https://silt-db.dev/llms.txt, the full documentation bundle is available at https://silt-db.dev/llms-full.txt, and this page is available as Markdown at https://silt-db.dev/advanced/sharedb.md.

ShareDB adapter

@silt-db/sharedb implements ShareDB's database contract using Silt. It stores snapshots, tombstones, metadata, and operation history in SQLite. Mongo-shaped query matching, sorting, pagination, counts, grouping, and joins execute in SQL; ShareDB continues to apply OT operations and projections to returned document data.

Use this adapter to replace sharedb-mingo-memory when you need persistence without a database-sized JavaScript document cache. It preserves that adapter's query conventions, including aggregation results in query extra. For a standalone document database, start with the basic database guide.

Create a backend

The examples assume the Silt packages are available through a local checkout/package or a published release. ShareDB and the selected SQLite driver must also be installed.

import ShareDB from 'sharedb';
import { openDatabase } from '@silt-db/sqlite/better-sqlite3';
import { ShareDBSQLite, installQueryDependencies } from '@silt-db/sharedb';

const database = await openDatabase('./sharedb.sqlite');
const adapter = new ShareDBSQLite({ database, ownsDatabase: true });
await adapter.ready;

const backend = new ShareDB({ db: adapter });
installQueryDependencies(backend);

const connection = backend.connect();
const doc = connection.get('orders', 'order-1');
await new Promise((resolve, reject) => {
  doc.create({ customerId: 'customer-1', amount: 25 }, (error) => {
    if (error) reject(error);
    else resolve();
  });
});

Silt-opened writable Node files default to WAL and synchronous=FULL. Each ShareDB commit checks the expected document version and stores its operation and snapshot atomically. The adapter does not hydrate an in-memory document database at startup.

Initialization and lifecycle

new ShareDBSQLite('app.sqlite');
new ShareDBSQLite({ filename: 'app.sqlite' });
new ShareDBSQLite({ database });
new ShareDBSQLite({
  filename: 'app.sqlite',
  sqliteOptions: { adapter: 'better-sqlite3' },
});
Option or memberBehavior
filenameUsed when the adapter opens its own Silt database. Defaults to ':memory:'; provide a file path or persistent database for durable storage.
databaseAn existing Silt database, or compatible object exposing driver and close().
sqliteOptionsOpening options for an internally created database.
ownsDatabaseDefaults to true for a created database and false for an injected one. true closes the database when the adapter closes.
pollDebounce, pollIntervalPassed through to the ShareDB DB contract.
readyPromise for initialization; callback operations also wait automatically.
close(callback) / await close()Closes the adapter through a callback or promise.
database, driver, sqliteInitialized database and underlying driver/connection access. Await ready before relying on them.

Stop subscriptions and await outstanding application writes before shutdown. Close the ShareDB backend using its callback contract; if the database was borrowed, close it after backend activity has drained. For asynchronous browser workers, the Teamplay example includes an explicit helper that tracks and drains outstanding adapter calls.

Injecting a database is also how an application selects Expo, read-only behavior, or native regex. The constructor has no top-level regexExtension option; put it in sqliteOptions or pass an already configured database.

Ordinary queries

connection.createFetchQuery(
  'orders',
  {
    amount: { $gte: 10 },
    $sort: { amount: -1 },
    $limit: 20,
  },
  {},
  (error, snapshots) => {
    if (error) throw error;
    console.log(snapshots.map((snapshot) => snapshot.data));
  },
);

Query data fields appear at the root. The adapter adds these controls:

ControlBehavior
$sortSort specification; $orderby is a legacy alias.
$skip, $limitPagination controls.
$countReturns the SQL count as query extra, after skip and limit.
$aggregateExecutes a pipeline and returns its results as query extra, with an empty snapshot list.
$comment, $hintIgnored for compatibility with sharedb-mingo-memory; they do not change a plan.

_id, _v, _type, and _m are reserved query metadata fields. Data containing colliding names is preserved in fetched snapshots, but query metadata takes those names in the SQL query document. Ordinary queries exclude deleted snapshots unless _type is explicitly supplied.

Aggregations and lookups

connection.createFetchQuery(
  'orders',
  {
    $aggregate: [
      { $match: { _type: { $ne: null } } },
      {
        $lookup: {
          from: 'customers',
          localField: 'customerId',
          foreignField: '_id',
          as: 'customer',
        },
      },
      { $unwind: '$customer' },
      { $group: { _id: '$customer.name', total: { $sum: '$amount' } } },
    ],
  },
  {},
  (error, snapshots, extra) => {
    if (error) throw error;
    console.log(extra);
  },
);

Aggregations include tombstones, following sharedb-mingo-memory. Add an explicit _type filter when deleted documents should be excluded; foreign pipelines can apply the same filter to their sources. The pipeline reference describes equality, correlated, nested, and literal-source lookups, and their restrictions.

Use ShareDB's createSubscribeQuery for live query results. Call installQueryDependencies(backend) once, before creating subscriptions involving $lookup or $unionWith. The middleware discovers dependencies in nested pipelines and facets, so a foreign-collection write triggers a full SQL repoll of affected subscriptions.

When several backend processes serve one database, configure a shared ShareDB PubSub adapter as well. SQLite shares persisted state and locking; it does not distribute ShareDB operation notifications. Browser tabs have additional SQLite ownership constraints covered in Expo.

Named aggregations belong to a higher layer. In Teamplay, install its named-aggregation middleware before Silt's dependency middleware so a named query has resolved to its pipeline when dependency discovery runs. See Teamplay integration.

Indexes and query plans

await adapter.createIndex(
  'orders',
  { tenant: 1, updatedAt: -1 },
  {
    name: 'tenant_updated',
  },
);
const indexes = await adapter.listIndexes('orders');
const plan = await adapter.explain('orders', {
  tenant: 'team-1',
  updatedAt: { $gte: 100 },
});

await adapter.dropIndex('orders', 'tenant_updated');

These methods are async. createIndex returns a name; listIndexes returns { name, key } entries; explain returns { sql, params, queryPlan }. Only nonunique supported scalar/compound/multikey specifications are accepted. See Indexes for access patterns and restrictions.

SQL triggers update index keys in the same transaction as snapshots and operations. A parallel-array guard failure rolls back the whole commit. Core Silt collections and ShareDB snapshot collections use separate index namespaces on a shared connection.

Bulk snapshot reads use one parameterized SQL statement with json_each, reducing native bridge calls. ShareDB applies its requested snapshot projection after retrieval using its existing contract.

Compatibility and validation

The adapter uses the same JSON and operator contract as the database. Unsupported syntax fails explicitly. It has no JavaScript query fallback. ShareDB's own OT execution is separate from Silt's SQL filtering and aggregation.

The unmodified ShareDB DB/client harness runs with npm run test:sharedb. Focused tests cover persistence, version conflicts, rollback, joins, tombstones, metadata, and foreign-collection live queries. The testing page separates these checks from live MongoDB comparisons and actual Expo runtime evidence.

For the ORM integration and the same application code over local or WebSocket connections, continue to Teamplay.