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/server';
import { ShareDBSQLite, installQueryDependencies } from '@silt-db/sharedb';

const database = await openDatabase('./sharedb.sqlite', {
  adapter: 'better-sqlite3',
  readPoolSize: 2,
});
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();
  });
});

The explicit server entry runs SQLite in workers with one writer and a bounded pool of WAL readers. Normal ShareDB reads use the pool; query planning and execution share a consistent reader transaction. Writable files use 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.

Share one owner with custom SQL

Pass the same database to integrations that store BLOBs or other application SQL rows. Use its managed SQL facade so writes share ShareDB's writer coordinator:

const sql = database.sql;
await sql.run('CREATE TABLE IF NOT EXISTS app_blobs (id TEXT PRIMARY KEY, data BLOB)');
await sql.run('INSERT INTO app_blobs (id, data) VALUES (?, ?)', [
  'asset-1',
  new Uint8Array([0, 128, 255]),
]);
const row = await sql.read.get('SELECT length(data) AS size FROM app_blobs WHERE id = ?', [
  'asset-1',
]);

sql.read uses the reader pool. sql.transaction(async tx => …) supplies a scoped SQL handle; use that handle for every SQL operation in the transaction. ShareDB metadata writes still go through document operations and the adapter. Ordinary Silt collections and ShareDB snapshots are separate storage namespaces; direct snapshot SQL writes bypass OT and subscription notifications.

The framework providers apply this pattern to StartupJS UI files. For Expo or an injected native connection, open the appropriate portable/platform entry and supply its database instead; the server worker entry is Node-only.

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.