Getting started
Install from npm, define a schema, open a database, and start reading and writing.
Install
bash
npm install worker-sync-dbPeer: TypeScript 5+ (for types). Install React 18+ only if you use worker-sync-db/react.
Package: worker-sync-db on npm.
Define a schema
A schema maps collection names to IndexedDB object stores:
typescript
import { defineSchema } from 'worker-sync-db';
export const schema = defineSchema({
todos: {
keyPath: 'id',
indexes: {
byStatus: 'status',
},
},
notes: {
keyPath: 'id',
},
});keyPath— primary key field on each document (usually'id').indexes— optional named indexes forquery({ index: 'byStatus', range: ... }).
defineSchema preserves literal collection names for typed db.put('todos', ...).
Open a database
typescript
import { createDatabase, sharedWorkerUrl, dedicatedWorkerUrl } from 'worker-sync-db';
import { schema } from './schema';
const db = await createDatabase({
schema,
dbName: 'my-app', // SharedWorker.name + IndexedDB database name
mode: 'auto',
sharedWorker: sharedWorkerUrl,
dedicatedWorker: dedicatedWorkerUrl,
});dbName— must be stable across deploys for the same app; different apps should use different names.sharedWorker/dedicatedWorker— recommended so bundlers emit worker scripts. See Install & bundlers. You can omit both — these exports are the defaults.- Call only in the browser (
windowrequired; not for SSR).
CRUD
typescript
// Create / update
const doc = await db.put('todos', {
id: crypto.randomUUID(),
title: 'Learn worker-sync-db',
status: 'open',
});
console.log(doc._version, doc._updatedAt);
// Read
const todo = await db.get('todos', doc.id);
// Query by index (range uses IDBKeyRangeInit shape)
const open = await db.query('todos', {
index: 'byStatus',
range: { lower: 'open', upper: 'open' },
});
// Soft delete
await db.delete('todos', doc.id);Subscriptions (multi-tab)
typescript
const unsub = db.subscribe('todos', (event) => {
if (event.type === 'put') {
console.log('Updated:', event.doc);
} else {
console.log('Deleted:', event.id);
}
});
// Later
unsub();Each tab must call subscribe locally. The SharedWorker broadcasts change events only to ports with an active subscription on that collection.
Remote sync (optional)
typescript
import { createDatabase, sharedWorkerUrl, dedicatedWorkerUrl } from 'worker-sync-db';
const db = await createDatabase({
schema,
dbName: 'my-app',
sharedWorker: sharedWorkerUrl,
dedicatedWorker: dedicatedWorkerUrl,
remote: {
async pull(cursor) {
const res = await fetch(`/api/sync/pull?cursor=${cursor ?? ''}`);
return res.json();
},
async push(ops) {
const res = await fetch('/api/sync/push', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ops }),
});
return res.json();
},
},
});
await db.syncNow();
db.onSyncStatusChange((status) => {
console.log('Pending ops:', status.pending);
});Full contract: Remote / BYO sync.
Cleanup
typescript
db.close();Call when unmounting a SPA root or logging out. Other tabs keep their connections to the SharedWorker.