Sync to a server database
With enableOutbox: true, every fact and task mutation also writes an outbox event to SQLite,
in the same transaction. @equationalapplications/prisma-outbox reads that table and replays
the events into your Prisma database, with at-least-once delivery and ordering preserved.
npm install @equationalapplications/prisma-outbox @equationalapplications/core-llm-wiki @prisma/clientimport { WikiMemory } from '@equationalapplications/core-llm-wiki';import { PrismaOutboxWorker } from '@equationalapplications/prisma-outbox';import { PrismaClient } from '@prisma/client';
const wiki = new WikiMemory(db, { llmProvider, config: { enableOutbox: true } });await wiki.setup();
const worker = new PrismaOutboxWorker({ wikiMemory: wiki, prisma: new PrismaClient(), mapEvent: async (event, tx) => { // MUST be idempotent: an event can be redelivered if the ack fails after commit. if (event.operation === 'INSERT' && event.table_name.includes('entries')) { await tx.wikiEntry.upsert({ where: { id: event.record_id }, create: { id: event.record_id, ...(event.payload as Record<string, unknown>) }, update: {}, }); } }, pollIntervalMs: 5000, batchSize: 100, onError: (err, event) => { console.error('outbox event failed', event.id, err); return false; // false = halt (preserve order); true = skip this poison event },});
worker.start();// on shutdown:worker.stop();How it works
Section titled “How it works”- A mutation writes the domain row and an outbox row in one SQLite transaction.
- The worker polls
getUnprocessedOutboxEvents()and runsmapEventinside a Prisma transaction. - Successful event IDs go to
markOutboxEventsProcessed(), which deletes them. - If a full batch drained without errors, the worker polls again immediately to catch up.
Config
Section titled “Config”| Field | Default | Description |
|---|---|---|
wikiMemory |
required | The instance to poll |
prisma |
required | Anything with $transaction (your PrismaClient) |
mapEvent(event, tx) |
required | Applies one event in a Prisma transaction |
batchSize |
100 |
Events per poll cycle |
pollIntervalMs |
5000 |
Time between polls |
onError(err, event) |
halt | Return true to skip the event, false to halt |
onWorkerError(err) |
— | Called for SQLite read or ack failures |