@mostajs/replicatorPart of the
@mostajs/ormecosystem — the multi-dialect ORM with one API across 13 databases. This post zooms in on replication.
@mostajs/replicator gives a Node/TypeScript app
database replication as a library — no Debezium, no
Kafka, no broker to operate:
round-robin /
least-lag / random).services/replicator.mjs into your project.npm install @mostajs/replicator @mostajs/orm @mostajs/mprojectYou have a primary database and you want: read replicas to take load off the master, a failover path when the master dies, and — increasingly — a different store for analytics or search (Mongo, a column store) fed from your transactional Postgres.
In the JS/TS world there is no library answer. Prisma and Drizzle are query builders; replication is “someone else’s problem”. So teams reach for Debezium + Kafka Connect, a managed CDC service, or a pile of bespoke cron scripts — infrastructure to stand up, secure and babysit, long before the first row moves.
@mostajs/replicator collapses that into a dependency. It
sits on top of @mostajs/orm,
so every replica is just an IDialect + isolated
EntityService — the same schema model you already use.
Register a master and one or more slaves per project. Reads are routed by a strategy you pick; writes go to the master.
import { ReplicationManager } from '@mostajs/replicator'
import { ProjectManager } from '@mostajs/mproject'
const rm = new ReplicationManager(new ProjectManager())
await rm.addReplica('secuaccess', {
name: 'master', role: 'master',
dialect: 'postgres', uri: 'postgresql://u:p@master:5432/secuaccess',
})
await rm.addReplica('secuaccess', {
name: 'slave-1', role: 'slave',
dialect: 'postgres', uri: 'postgresql://u:p@slave1:5432/secuaccess',
lagTolerance: 5000, // ms of lag tolerated before it's "stale"
})
rm.setReadRouting('secuaccess', 'least-lag') // or 'round-robin' | 'random'
const readService = rm.resolveReadService('secuaccess') // EntityService for readsleast-lag is the interesting one: reads go to whichever
slave is freshest, so you get scale-out without serving
badly stale data. getReplicaStatus() returns each replica’s
role, status, lag and schema count for a dashboard.
When the master dies, promote a slave. The old master, when it comes back, rejoins as a slave.
await rm.promoteToMaster('secuaccess', 'slave-1') // slave-1 is now masterAnd here is the honest part, straight from the docs:
promoteToMaster() can lose in-flight writes that weren’t
replicated yet. For replicas that must never lose data, set
lagTolerance: 0 so promotion is blocked
while the replica is behind. No silent data-loss-by-default.
This is what no other JS/TS ORM does. Define a replication rule that captures changes on a source project and replays them on a target — across dialects. PostgreSQL → MongoDB, verbatim, as a rule:
rm.addReplicationRule({
name: 'pg-to-mongo',
source: 'secuaccess', // PostgreSQL
target: 'analytics', // MongoDB
mode: 'cdc', // 'snapshot' | 'cdc' | 'bidirectional'
collections: ['users', 'clients'],
conflictResolution: 'source-wins',
})
await rm.sync('pg-to-mongo')Three modes, each with a precise meaning:
snapshot — full mirror
(create/update/delete). It removes target rows that no longer
exist on the source.cdc — incremental change capture; it
does not delete target orphans.bidirectional — two-way merge,
conflicts resolved by timestamp.Under the hood a SchemaMapper validates the
cross-dialect move before you run it —
validateCrossDialect(source, target, schemas) returns a
CompatReport flagging issues like string-length limits or
paradigm mismatches (relational ↔︎ document). You find out the Mongo
target can’t represent something at design time, not at
2 a.m.
Replication is where libraries oversell. This one labels exactly what you get — three levels:
| Level | Guarantee |
|---|---|
| Local transactions on one replica | ACID ✅ |
| Master → slave | Eventually consistent (read-your-writes only on the master) |
| CDC rules | Eventually consistent + at-least-once → rules must be idempotent (upsert on the target) |
There is no distributed cross-dialect transaction — and the docs say so, pointing you to saga/compensation instead. That candour is the point: you can architect correctly because the contract is stated, not implied.
You don’t wire this by hand. A scaffolder emits a ready-to-run service:
npx mostajs-replicator-scaffold --dir . --force # writes services/replicator.mjsThe generated service reconnects the whole topology from a persisted
replicator-tree.json (replicas + rules + routing), resolves
'*' collection wildcards by introspecting the master
catalogue, and is safe to run on a cron or as a long-lived process.
enableAutoPersist('replicator-tree.json') keeps that tree
in sync as you change the topology — which is exactly what the companion
@mostajs/replica-monitor
dashboard reads (no DB connection needed) to show live lag and
status.
@mostajs/replicator is one module of the @mostajs/orm
ecosystem. Because every replica is a plain @mostajs/orm
connection, the schemas you replicate are the same
EntitySchema your app already runs on — one mental model
from CRUD to CDC. For live supervision pair it with @mostajs/replica-monitor;
to move bulk data outside a live rule, see @mostajs/orm-copy-data.
npm install @mostajs/replicator @mostajs/orm @mostajs/mproject
npx mostajs-replicator-scaffold --dir . # then edit services/replicator.mjs@mostajs/orm — 13
databases, one API, zero codegenIf cross-dialect replication as a library (not an ops project) is useful to you, a ⭐ on GitHub helps — it’s the signal AI dev tools use to surface the package.
Tags: #nodejs #typescript
#database #replication #cdc
#postgresql #mongodb #cqrs
#orm #failover
#eventual-consistency #mostajs
Auteur : Dr Hamid MADANI drmdh@msn.com