mongofire 6.5.5 → 6.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.md +288 -334
- package/bin/mongofire.cjs +1237 -0
- package/dist/bin/mongofire.cjs +1 -1
- package/dist/src/changetrack.js +1 -1
- package/dist/src/compactor.js +1 -1
- package/dist/src/connection.js +1 -1
- package/dist/src/device.js +1 -1
- package/dist/src/diff.js +1 -1
- package/dist/src/field-merge.js +1 -1
- package/dist/src/index.cjs +1 -1
- package/dist/src/local-manager.js +1 -0
- package/dist/src/plugin.js +1 -1
- package/dist/src/rate-limiter.js +1 -1
- package/dist/src/reconcile.js +1 -1
- package/dist/src/schema-manager.js +1 -1
- package/dist/src/state.js +1 -1
- package/dist/src/sync.js +1 -1
- package/dist/src/utils.js +1 -1
- package/dist/types/index.d.ts +140 -1
- package/index.cjs +17 -1
- package/index.mjs +7 -1
- package/package.json +11 -10
- package/types/index.d.ts +317 -0
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# 🔥 MongoFire
|
|
2
2
|
|
|
3
3
|
> **Offline-first MongoDB sync** — Local + Atlas feel like ONE database.
|
|
4
|
-
>
|
|
4
|
+
> Drop-in production setup, automatic connection management, zero boilerplate.
|
|
5
5
|
|
|
6
6
|
[](https://www.npmjs.com/package/mongofire)
|
|
7
7
|
[](https://nodejs.org)
|
|
@@ -11,15 +11,24 @@
|
|
|
11
11
|
|
|
12
12
|
## What is MongoFire?
|
|
13
13
|
|
|
14
|
-
MongoFire keeps a **local MongoDB** and **MongoDB Atlas** in sync — automatically, reliably, and with zero boilerplate. Your app reads and writes to a local MongoDB instance that is always fast and always available, even when offline. MongoFire handles
|
|
14
|
+
MongoFire keeps a **local MongoDB** and **MongoDB Atlas** in sync — automatically, reliably, and with zero boilerplate. Your app reads and writes to a local MongoDB instance that is always fast and always available, even when offline. MongoFire handles the rest in the background.
|
|
15
15
|
|
|
16
|
+
> ⚡ **MongoFire manages every MongoDB connection for you.**
|
|
17
|
+
> You never call `mongoose.connect()`. You never call `app.listen()` directly.
|
|
18
|
+
> You import two functions — `startApp` and `plugin` — and everything else is automatic.
|
|
19
|
+
|
|
20
|
+
**Features:**
|
|
16
21
|
- **Offline-first** — your app never waits for the network
|
|
22
|
+
- **Zero-config connection** — local MongoDB connects automatically on import
|
|
23
|
+
- **`startApp(app, port)`** — replaces `app.listen()`, waits for DB, then opens the server
|
|
17
24
|
- **Automatic sync** — uploads local changes and downloads remote ones on a configurable interval
|
|
18
25
|
- **Real-time mode** — optional Atlas Change Streams for near-instant propagation
|
|
19
|
-
- **Conflict resolution** — deterministic last-writer-wins with version tracking
|
|
20
|
-
- **
|
|
21
|
-
- **
|
|
22
|
-
- **
|
|
26
|
+
- **Conflict resolution** — deterministic last-writer-wins with version tracking
|
|
27
|
+
- **Per-field merge** — field-level LWW prevents data loss when devices edit different fields simultaneously
|
|
28
|
+
- **Resumable bootstrap** — first sync streams from Atlas in batches, survives crashes
|
|
29
|
+
- **Self-healing** — detects and recovers lost writes caused by crashes or local DB resets
|
|
30
|
+
- **Auto-spawn mongod** — if MongoDB is not running, MongoFire starts it automatically
|
|
31
|
+
- **CLI tools** — interactive commands for status, conflicts, reconciliation, and safe reset
|
|
23
32
|
- **TypeScript** — full type declarations included
|
|
24
33
|
|
|
25
34
|
---
|
|
@@ -30,7 +39,7 @@ MongoFire keeps a **local MongoDB** and **MongoDB Atlas** in sync — automatica
|
|
|
30
39
|
npm install mongofire
|
|
31
40
|
```
|
|
32
41
|
|
|
33
|
-
**Peer dependencies
|
|
42
|
+
**Peer dependencies** (install once in your project):
|
|
34
43
|
|
|
35
44
|
```bash
|
|
36
45
|
npm install mongodb mongoose dotenv
|
|
@@ -38,21 +47,23 @@ npm install mongodb mongoose dotenv
|
|
|
38
47
|
|
|
39
48
|
---
|
|
40
49
|
|
|
41
|
-
## Quick Start
|
|
50
|
+
## Quick Start (3 steps)
|
|
42
51
|
|
|
43
|
-
### 1
|
|
52
|
+
### Step 1 — Run the setup wizard
|
|
44
53
|
|
|
45
54
|
```bash
|
|
46
55
|
npx mongofire init
|
|
47
56
|
```
|
|
48
57
|
|
|
49
|
-
|
|
58
|
+
This creates three files in your project root:
|
|
50
59
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
60
|
+
| File | Purpose |
|
|
61
|
+
|---|---|
|
|
62
|
+
| `.env` | MongoDB connection strings |
|
|
63
|
+
| `mongofire.config.js` | Collections to sync, intervals, options |
|
|
64
|
+
| `mongofire.js` | The MongoFire entry point — **do not delete** |
|
|
54
65
|
|
|
55
|
-
### 2
|
|
66
|
+
### Step 2 — Fill in `.env`
|
|
56
67
|
|
|
57
68
|
```env
|
|
58
69
|
ATLAS_URI=mongodb+srv://user:pass@cluster0.xxxxx.mongodb.net/
|
|
@@ -60,434 +71,350 @@ LOCAL_URI=mongodb://127.0.0.1:27017
|
|
|
60
71
|
DB_NAME=myapp
|
|
61
72
|
```
|
|
62
73
|
|
|
63
|
-
|
|
74
|
+
> `ATLAS_URI` is optional — omit it to run in local-only mode during development.
|
|
75
|
+
|
|
76
|
+
### Step 3 — Use MongoFire in your project
|
|
77
|
+
|
|
78
|
+
**`server.js` — your Express entry point:**
|
|
64
79
|
|
|
65
80
|
```js
|
|
66
|
-
//
|
|
67
|
-
|
|
68
|
-
|
|
81
|
+
// ESM
|
|
82
|
+
import express from 'express';
|
|
83
|
+
import cors from 'cors';
|
|
84
|
+
import cookieParser from 'cookie-parser';
|
|
85
|
+
import { startApp } from './mongofire.js'; // ← import from YOUR mongofire.js
|
|
86
|
+
|
|
87
|
+
import authRoutes from './routes/auth.routes.js';
|
|
88
|
+
import studentRoutes from './routes/student.routes.js';
|
|
89
|
+
|
|
90
|
+
const app = express();
|
|
91
|
+
app.use(express.json());
|
|
92
|
+
app.use(cookieParser());
|
|
93
|
+
app.use(cors({ origin: process.env.FRONTEND_URL, credentials: true }));
|
|
94
|
+
|
|
95
|
+
app.use('/auth', authRoutes);
|
|
96
|
+
app.use('/students', studentRoutes);
|
|
97
|
+
|
|
98
|
+
// ✅ Replaces app.listen() — waits for local DB then starts the server
|
|
99
|
+
startApp(app, process.env.PORT || 3000);
|
|
69
100
|
```
|
|
70
101
|
|
|
71
102
|
```js
|
|
72
|
-
//
|
|
73
|
-
|
|
74
|
-
|
|
103
|
+
// CommonJS
|
|
104
|
+
const express = require('express');
|
|
105
|
+
const { startApp } = require('./mongofire');
|
|
106
|
+
|
|
107
|
+
const app = express();
|
|
108
|
+
app.use(express.json());
|
|
109
|
+
|
|
110
|
+
app.use('/auth', require('./routes/auth.routes'));
|
|
111
|
+
|
|
112
|
+
startApp(app, process.env.PORT || 3000);
|
|
75
113
|
```
|
|
76
114
|
|
|
77
|
-
|
|
115
|
+
**`models/User.js` — attach the plugin to your Mongoose schemas:**
|
|
78
116
|
|
|
79
117
|
```js
|
|
80
|
-
|
|
118
|
+
// ESM — from a file inside models/
|
|
119
|
+
import mongoose from 'mongoose';
|
|
120
|
+
import { plugin } from '../mongofire.js'; // ← note: ../ because models/ is one level deep
|
|
81
121
|
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
122
|
+
const UserSchema = new mongoose.Schema({
|
|
123
|
+
name: String,
|
|
124
|
+
email: { type: String, unique: true },
|
|
85
125
|
updatedAt: Date,
|
|
86
126
|
});
|
|
87
127
|
|
|
88
|
-
|
|
128
|
+
UserSchema.plugin(plugin('users')); // collection name must match mongofire.config.js
|
|
89
129
|
|
|
90
|
-
|
|
130
|
+
export default mongoose.model('User', UserSchema);
|
|
91
131
|
```
|
|
92
132
|
|
|
93
|
-
Every `save()`, `create()`, `update()`, and `delete()` is now tracked and synced automatically.
|
|
94
|
-
|
|
95
|
-
---
|
|
96
|
-
|
|
97
|
-
## Config Options
|
|
98
|
-
|
|
99
133
|
```js
|
|
100
|
-
//
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
atlasUri: process.env.ATLAS_URI,
|
|
104
|
-
dbName: process.env.DB_NAME || "myapp",
|
|
134
|
+
// CommonJS — from a file inside models/
|
|
135
|
+
const mongoose = require('mongoose');
|
|
136
|
+
const { plugin } = require('../mongofire'); // ← note: ../ one level up
|
|
105
137
|
|
|
106
|
-
|
|
138
|
+
const StudentSchema = new mongoose.Schema({
|
|
139
|
+
name: String,
|
|
140
|
+
grade: Number,
|
|
141
|
+
updatedAt: Date,
|
|
142
|
+
});
|
|
107
143
|
|
|
108
|
-
|
|
109
|
-
batchSize: 200, // documents per batch
|
|
110
|
-
syncOwner: "*", // '*' = sync all data (default)
|
|
111
|
-
realtime: false, // enable Atlas Change Streams
|
|
144
|
+
StudentSchema.plugin(plugin('students'));
|
|
112
145
|
|
|
113
|
-
|
|
114
|
-
if (result.uploaded + result.downloaded + result.deleted > 0) {
|
|
115
|
-
console.log(`Synced: up:${result.uploaded} down:${result.downloaded}`);
|
|
116
|
-
}
|
|
117
|
-
},
|
|
118
|
-
onError(err) {
|
|
119
|
-
console.error("Sync error:", err.message);
|
|
120
|
-
},
|
|
121
|
-
};
|
|
146
|
+
module.exports = mongoose.model('Student', StudentSchema);
|
|
122
147
|
```
|
|
123
148
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
| `collections` | `string[]` | required | Collection names to sync |
|
|
129
|
-
| `localUri` | `string` | `'mongodb://localhost:27017'` | Local MongoDB URI |
|
|
130
|
-
| `atlasUri` | `string` | `null` | Atlas URI. Omit for local-only mode |
|
|
131
|
-
| `dbName` | `string` | `'mongofire'` | Database name |
|
|
132
|
-
| `syncInterval` | `number` | `30000` | Polling interval in ms |
|
|
133
|
-
| `batchSize` | `number` | `200` | Documents per upload/download batch |
|
|
134
|
-
| `syncOwner` | `string \| fn` | `'*'` | Owner filter. See [Multi-Tenant](#multi-tenant) |
|
|
135
|
-
| `realtime` | `boolean` | `false` | Enable Atlas Change Streams for instant sync |
|
|
136
|
-
| `onSync` | `function` | `null` | Called after each sync cycle with a `SyncResult` |
|
|
137
|
-
| `onError` | `function` | `null` | Called when a sync cycle throws |
|
|
138
|
-
| `reconcileOnStart` | `boolean` | `true` | Scan for lost writes at startup |
|
|
139
|
-
| `reconcileFullScan` | `boolean` | `true` | Include deep phase of reconciliation |
|
|
149
|
+
> **Critical:** The import path is always a **relative path** to `mongofire.js` in your project root.
|
|
150
|
+
> It is **never** `'mongofire'` (the npm package name).
|
|
151
|
+
> From `server.js` (root) → `'./mongofire.js'`
|
|
152
|
+
> From `models/User.js` (one level deep) → `'../mongofire.js'`
|
|
140
153
|
|
|
141
154
|
---
|
|
142
155
|
|
|
143
|
-
##
|
|
156
|
+
## Project folder structure
|
|
144
157
|
|
|
145
|
-
```js
|
|
146
|
-
mongofire.on("ready", () => console.log("MongoFire started"));
|
|
147
|
-
mongofire.on("online", () => console.log("Atlas connected"));
|
|
148
|
-
mongofire.on("offline", () => console.log("Working locally"));
|
|
149
|
-
mongofire.on("sync", (r) => console.log("Sync result:", r));
|
|
150
|
-
mongofire.on("conflict", (c) => console.warn("Conflict:", c));
|
|
151
|
-
mongofire.on("conflictResolved", (d) => console.log("Resolved:", d.opId));
|
|
152
|
-
mongofire.on("reconcileComplete", (r) =>
|
|
153
|
-
console.log("Re-queued:", r.totalQueued),
|
|
154
|
-
);
|
|
155
|
-
mongofire.on("realtimeStarted", () => console.log("Change streams active"));
|
|
156
|
-
mongofire.on("realtimeStopped", () => console.log("Realtime stopped"));
|
|
157
|
-
mongofire.on("stopped", () => console.log("Shut down cleanly"));
|
|
158
|
-
mongofire.on("error", (e) => console.error("Error:", e));
|
|
159
158
|
```
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
| `stopped` | — | `stop()` finished |
|
|
173
|
-
| `error` | `Error` | Unexpected sync error |
|
|
159
|
+
backend/
|
|
160
|
+
├── mongofire.js ← Generated by init — the bridge between your app and MongoFire
|
|
161
|
+
├── mongofire.config.js ← Your sync configuration
|
|
162
|
+
├── .env ← Connection strings (never commit this)
|
|
163
|
+
├── server.js ← import { startApp } from './mongofire.js'
|
|
164
|
+
├── models/
|
|
165
|
+
│ ├── User.js ← import { plugin } from '../mongofire.js'
|
|
166
|
+
│ └── Student.js ← import { plugin } from '../mongofire.js'
|
|
167
|
+
└── routes/
|
|
168
|
+
├── auth.routes.js
|
|
169
|
+
└── student.routes.js
|
|
170
|
+
```
|
|
174
171
|
|
|
175
172
|
---
|
|
176
173
|
|
|
177
|
-
##
|
|
174
|
+
## What `startApp()` does
|
|
178
175
|
|
|
179
|
-
|
|
176
|
+
`startApp(app, port)` replaces `app.listen()`. It:
|
|
180
177
|
|
|
181
|
-
|
|
178
|
+
1. Waits for local MongoDB to be fully connected
|
|
179
|
+
2. Calls `app.listen(port)` only after the DB is ready
|
|
180
|
+
3. Logs `🚀 [MongoFire] Server ready on port <port>` on success
|
|
181
|
+
4. If the local DB fails: logs a descriptive error and exits with code 1
|
|
182
182
|
|
|
183
|
-
|
|
183
|
+
**Console output on startup:**
|
|
184
184
|
|
|
185
|
-
|
|
185
|
+
```
|
|
186
|
+
✅ [MongoFire] Local MongoDB connected
|
|
187
|
+
🚀 [MongoFire] Server ready on port 3000
|
|
188
|
+
🌐 [MongoFire] Atlas connected — sync active
|
|
189
|
+
🔄 [MongoFire] Sync complete — ↑2 uploaded ↓5 downloaded 🗑 0 deleted
|
|
190
|
+
```
|
|
186
191
|
|
|
187
|
-
|
|
192
|
+
---
|
|
188
193
|
|
|
189
|
-
|
|
194
|
+
## Config Options (`mongofire.config.js`)
|
|
190
195
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
196
|
+
```js
|
|
197
|
+
export default {
|
|
198
|
+
localUri: process.env.LOCAL_URI || 'mongodb://127.0.0.1:27017',
|
|
199
|
+
atlasUri: process.env.ATLAS_URI,
|
|
200
|
+
dbName: process.env.DB_NAME || 'myapp',
|
|
195
201
|
|
|
196
|
-
|
|
202
|
+
collections: ['users', 'students', 'orders'], // every collection your app uses
|
|
197
203
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}
|
|
204
|
+
syncInterval: 30000, // ms between sync cycles (minimum 500)
|
|
205
|
+
batchSize: 200,
|
|
206
|
+
syncOwner: '*', // '*' = sync all | 'userId' = per-user isolation
|
|
207
|
+
realtime: false, // true = Atlas Change Streams
|
|
208
|
+
cleanDays: 7,
|
|
209
|
+
|
|
210
|
+
onSync(result) {
|
|
211
|
+
if (result.uploaded + result.downloaded + result.deleted > 0)
|
|
212
|
+
console.log(`Synced: ↑${result.uploaded} ↓${result.downloaded}`);
|
|
213
|
+
},
|
|
214
|
+
onError(err) { console.error('Sync error:', err.message); },
|
|
215
|
+
};
|
|
207
216
|
```
|
|
208
217
|
|
|
209
|
-
|
|
218
|
+
| Option | Type | Default | Description |
|
|
219
|
+
|---------------------|----------------|--------------------------------------|---------------------------------------------------------|
|
|
220
|
+
| `collections` | `string[]` | **required** | Collection names to sync |
|
|
221
|
+
| `localUri` | `string` | `'mongodb://127.0.0.1:27017'` | Local MongoDB URI |
|
|
222
|
+
| `atlasUri` | `string` | `null` | Atlas URI. Omit for local-only mode |
|
|
223
|
+
| `dbName` | `string` | `'myapp'` | Database name |
|
|
224
|
+
| `syncInterval` | `number` | `30000` (`5000` if `realtime:true`) | Polling interval in ms (minimum: 500) |
|
|
225
|
+
| `batchSize` | `number` | `200` | Documents per batch (1–10 000) |
|
|
226
|
+
| `syncOwner` | `string\|fn` | `'*'` | Owner filter — see Multi-Tenant section |
|
|
227
|
+
| `realtime` | `boolean` | `false` | Enable Atlas Change Streams |
|
|
228
|
+
| `cleanDays` | `number` | `7` | Auto-clean synced records older than N days |
|
|
229
|
+
| `onSync` | `function` | `null` | Called after each sync cycle |
|
|
230
|
+
| `onError` | `function` | `null` | Called when a sync cycle throws |
|
|
231
|
+
| `reconcileOnStart` | `boolean` | `true` | Scan for lost writes at startup |
|
|
210
232
|
|
|
211
|
-
|
|
233
|
+
---
|
|
212
234
|
|
|
213
|
-
|
|
214
|
-
| ------------------- | -------------- | ----------------------------------------------- |
|
|
215
|
-
| `days` | `7` | Delete synced records older than N days |
|
|
216
|
-
| `opts.conflictDays` | same as `days` | Delete stale conflict records older than N days |
|
|
235
|
+
## Common import mistakes
|
|
217
236
|
|
|
218
|
-
###
|
|
237
|
+
### ❌ Wrong — using the npm package name
|
|
219
238
|
|
|
220
239
|
```js
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
console.log(`${c.collection}/${c.docId} op:${c.type} v${c.version}`);
|
|
224
|
-
console.log("Error:", c.lastError);
|
|
225
|
-
}
|
|
240
|
+
import { plugin } from 'mongofire'; // ❌ ERR_MODULE_NOT_FOUND
|
|
241
|
+
const { plugin } = require('mongofire'); // ❌ wrong unless mongofire is installed globally
|
|
226
242
|
```
|
|
227
243
|
|
|
228
|
-
###
|
|
244
|
+
### ✅ Correct — relative path to your local mongofire.js
|
|
229
245
|
|
|
230
|
-
|
|
246
|
+
```js
|
|
247
|
+
// server.js (same folder as mongofire.js)
|
|
248
|
+
import { startApp, plugin } from './mongofire.js';
|
|
231
249
|
|
|
232
|
-
|
|
250
|
+
// models/User.js (one folder deep)
|
|
251
|
+
import { plugin } from '../mongofire.js';
|
|
233
252
|
|
|
234
|
-
|
|
253
|
+
// routes/user.routes.js (one folder deep)
|
|
254
|
+
import { plugin } from '../mongofire.js';
|
|
255
|
+
```
|
|
235
256
|
|
|
236
|
-
|
|
257
|
+
---
|
|
237
258
|
|
|
238
|
-
|
|
259
|
+
## Events
|
|
239
260
|
|
|
240
261
|
```js
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
262
|
+
import { mongofire } from './mongofire.js';
|
|
263
|
+
|
|
264
|
+
mongofire.on('localReady', () => console.log('Local DB connected'));
|
|
265
|
+
mongofire.on('online', () => console.log('Atlas connected'));
|
|
266
|
+
mongofire.on('offline', () => console.log('Working offline'));
|
|
267
|
+
mongofire.on('sync', (r) => console.log('Synced:', r));
|
|
268
|
+
mongofire.on('conflict', (c) => console.warn('Conflict:', c));
|
|
269
|
+
mongofire.on('error', (e) => console.error('Error:', e));
|
|
270
|
+
mongofire.on('stopped', () => console.log('Shut down cleanly'));
|
|
244
271
|
```
|
|
245
272
|
|
|
246
|
-
|
|
|
247
|
-
|
|
248
|
-
|
|
|
249
|
-
|
|
|
273
|
+
| Event | Payload | When emitted |
|
|
274
|
+
|--------------------|--------------------------------|----------------------------------------|
|
|
275
|
+
| `localReady` | `Db` | Local MongoDB connected (before Atlas) |
|
|
276
|
+
| `ready` | — | `start()` fully completed |
|
|
277
|
+
| `online` | — | Atlas connected |
|
|
278
|
+
| `offline` | — | Atlas becomes unreachable |
|
|
279
|
+
| `sync` | `SyncResult` | After each sync cycle |
|
|
280
|
+
| `conflict` | `ConflictData` | Local write conflicts with remote |
|
|
281
|
+
| `conflictResolved` | `{ opId, resolution }` | After retry or dismiss |
|
|
282
|
+
| `stopped` | — | `stop()` finished |
|
|
283
|
+
| `error` | `Error` | Unexpected sync error |
|
|
250
284
|
|
|
251
|
-
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
## API Reference
|
|
288
|
+
|
|
289
|
+
### `startApp(app, port)` → `Promise<http.Server>`
|
|
252
290
|
|
|
253
|
-
|
|
291
|
+
Replaces `app.listen()`. Waits for local DB, then opens the server port. Exits with code 1 on DB failure.
|
|
254
292
|
|
|
255
293
|
```js
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
if (pending > 0) {
|
|
259
|
-
console.warn(`${pending} unsynced operations will be lost`);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
const { dropped } = await mongofire.resetLocal();
|
|
263
|
-
console.log(
|
|
264
|
-
`Wiped ${dropped} collections. Restart to re-bootstrap from Atlas.`,
|
|
265
|
-
);
|
|
294
|
+
import { startApp } from './mongofire.js';
|
|
295
|
+
startApp(app, process.env.PORT || 3000);
|
|
266
296
|
```
|
|
267
297
|
|
|
268
|
-
|
|
298
|
+
### `plugin(collectionName, options?)` → Mongoose plugin function
|
|
269
299
|
|
|
270
|
-
|
|
300
|
+
Attaches change-tracking to a schema. Apply **before** `mongoose.model()`.
|
|
271
301
|
|
|
272
302
|
```js
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
// With options
|
|
277
|
-
UserSchema.plugin(
|
|
278
|
-
mongofire.plugin("users", {
|
|
279
|
-
ownerField: "userId", // required only for multi-tenant
|
|
280
|
-
batchSize: 200,
|
|
281
|
-
concurrency: 8,
|
|
282
|
-
}),
|
|
283
|
-
);
|
|
303
|
+
import { plugin } from '../mongofire.js';
|
|
304
|
+
UserSchema.plugin(plugin('users'));
|
|
305
|
+
UserSchema.plugin(plugin('users', { ownerField: 'userId' })); // multi-tenant
|
|
284
306
|
```
|
|
285
307
|
|
|
286
|
-
|
|
287
|
-
| ------------- | -------- | ------- | ----------------------------------------------------- |
|
|
288
|
-
| `ownerField` | `string` | `null` | Dot-path to owner field. Only needed for multi-tenant |
|
|
289
|
-
| `batchSize` | `number` | `200` | Batch size for bulk operations |
|
|
290
|
-
| `concurrency` | `number` | `8` | Concurrent tracking calls per batch |
|
|
308
|
+
### `localReady` → `Promise<Db>`
|
|
291
309
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
## Real-Time Sync
|
|
295
|
-
|
|
296
|
-
Enable Atlas Change Streams for near-instant propagation between devices:
|
|
310
|
+
Resolves as soon as local MongoDB is connected, before Atlas.
|
|
297
311
|
|
|
298
312
|
```js
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
collections: ["orders"],
|
|
302
|
-
realtime: true, // requires Atlas M10+ or a local replica set
|
|
303
|
-
syncInterval: 5000, // polling fallback interval
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
mongofire.on("realtimeStarted", () => console.log("Changes appear instantly"));
|
|
313
|
+
import { localReady } from './mongofire.js';
|
|
314
|
+
await localReady; // DB is guaranteed ready after this
|
|
307
315
|
```
|
|
308
316
|
|
|
309
|
-
|
|
317
|
+
### `ready` → `Promise<MongoFire>`
|
|
310
318
|
|
|
311
|
-
|
|
319
|
+
Resolves after Atlas connect and the first sync.
|
|
312
320
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
> **Most apps do not need this.**
|
|
316
|
-
> If all users share the same data — a café, a team app, a single company — use the default `syncOwner: '*'` and skip this section entirely.
|
|
317
|
-
|
|
318
|
-
Multi-tenant mode is for apps where **each user must only sync their own private data**.
|
|
321
|
+
### `mongofire.sync(type?)` → `Promise<SyncResult>`
|
|
319
322
|
|
|
320
|
-
|
|
323
|
+
Manually trigger a sync (`'required'` or `'all'`).
|
|
321
324
|
|
|
322
|
-
|
|
323
|
-
| ---------------------------------- | ------------------------- |
|
|
324
|
-
| Café / restaurant system | No — staff share data |
|
|
325
|
-
| Single-company team app | No — everyone shares data |
|
|
326
|
-
| SaaS with per-tenant isolation | **Yes** |
|
|
327
|
-
| Per-user notes / tasks | **Yes** |
|
|
328
|
-
| Ride-hailing — each driver's data | **Yes** |
|
|
329
|
-
| Multi-school, each school isolated | **Yes** |
|
|
325
|
+
### `mongofire.status()` → `Promise<SyncStatus>`
|
|
330
326
|
|
|
331
|
-
|
|
327
|
+
Returns `{ online, pending, creates, updates, deletes, realtime }`.
|
|
332
328
|
|
|
333
|
-
|
|
329
|
+
### `mongofire.conflicts()` / `retryConflict(opId)` / `dismissConflict(opId)`
|
|
334
330
|
|
|
335
|
-
|
|
336
|
-
const OrderSchema = new mongoose.Schema({
|
|
337
|
-
items: Array,
|
|
338
|
-
total: Number,
|
|
339
|
-
userId: { type: mongoose.Types.ObjectId, required: true },
|
|
340
|
-
updatedAt: Date,
|
|
341
|
-
});
|
|
331
|
+
View and resolve sync conflicts.
|
|
342
332
|
|
|
343
|
-
|
|
344
|
-
```
|
|
333
|
+
### `mongofire.reconcile(opts?)` → `Promise<ReconcileResult[]>`
|
|
345
334
|
|
|
346
|
-
|
|
335
|
+
Scan for and recover writes lost in a crash.
|
|
347
336
|
|
|
348
|
-
|
|
349
|
-
module.exports = {
|
|
350
|
-
collections: ["orders", "products"],
|
|
351
|
-
syncOwner: "userId",
|
|
352
|
-
// ...
|
|
353
|
-
};
|
|
354
|
-
```
|
|
337
|
+
### `mongofire.resetLocal()` → `Promise<{ dropped: number }>`
|
|
355
338
|
|
|
356
|
-
|
|
339
|
+
Wipe the local DB. Next startup re-bootstraps from Atlas. **Unsynced changes are lost.**
|
|
357
340
|
|
|
358
|
-
|
|
359
|
-
async function login(req, res) {
|
|
360
|
-
const user = await User.findOne({ email: req.body.email });
|
|
361
|
-
// ... password check ...
|
|
362
|
-
|
|
363
|
-
await mongofire.start({
|
|
364
|
-
...config,
|
|
365
|
-
syncOwner: user._id.toString(),
|
|
366
|
-
});
|
|
367
|
-
|
|
368
|
-
res.json({ token, user });
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
async function logout(req, res) {
|
|
372
|
-
await mongofire.stop();
|
|
373
|
-
res.json({ message: "Logged out" });
|
|
374
|
-
}
|
|
375
|
-
```
|
|
341
|
+
### `mongofire.stop(timeoutMs?)` → `Promise<void>`
|
|
376
342
|
|
|
377
|
-
|
|
343
|
+
Flush in-flight ops and close connections. Called automatically on `SIGINT`/`SIGTERM`.
|
|
378
344
|
|
|
379
|
-
|
|
380
|
-
const order = await Order.create({
|
|
381
|
-
items: req.body.items,
|
|
382
|
-
total: req.body.total,
|
|
383
|
-
userId: req.user._id,
|
|
384
|
-
});
|
|
385
|
-
```
|
|
345
|
+
---
|
|
386
346
|
|
|
387
|
-
|
|
347
|
+
## Real-Time Sync
|
|
388
348
|
|
|
389
349
|
```js
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
350
|
+
// mongofire.config.js
|
|
351
|
+
export default {
|
|
352
|
+
atlasUri: process.env.ATLAS_URI,
|
|
353
|
+
collections: ['orders'],
|
|
354
|
+
realtime: true, // requires Atlas M10+ or a local replica set
|
|
355
|
+
syncInterval: 5000, // polling fallback
|
|
356
|
+
};
|
|
394
357
|
```
|
|
395
358
|
|
|
396
|
-
|
|
359
|
+
Falls back to polling if Change Streams are unavailable. Saves a resume token — restarts pick up exactly where they left off. Restarts use exponential backoff (2 s → 60 s).
|
|
397
360
|
|
|
398
361
|
---
|
|
399
362
|
|
|
400
|
-
##
|
|
401
|
-
|
|
402
|
-
```js
|
|
403
|
-
// CommonJS
|
|
404
|
-
const mongofirePlugin = require("mongofire/plugin");
|
|
405
|
-
OrderSchema.plugin(mongofirePlugin, { collection: "orders" });
|
|
363
|
+
## Multi-Tenant
|
|
406
364
|
|
|
407
|
-
|
|
408
|
-
const { factory } = require("mongofire/plugin");
|
|
409
|
-
OrderSchema.plugin(factory("orders"));
|
|
410
|
-
```
|
|
365
|
+
For apps where each user must only sync their own data:
|
|
411
366
|
|
|
412
367
|
```js
|
|
413
|
-
//
|
|
414
|
-
|
|
415
|
-
|
|
368
|
+
// mongofire.config.js
|
|
369
|
+
export default {
|
|
370
|
+
collections: ['notes'],
|
|
371
|
+
syncOwner: () => currentUserId, // returns the current user's ID
|
|
372
|
+
};
|
|
416
373
|
```
|
|
417
374
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
If the local database is cleared or corrupted, MongoFire automatically detects and resolves any stale pending operations during the next bootstrap — no manual conflict resolution, no stuck queues.
|
|
423
|
-
|
|
424
|
-
For a deliberate clean reset, use either:
|
|
425
|
-
|
|
426
|
-
```bash
|
|
427
|
-
# Interactive CLI — confirms before wiping
|
|
428
|
-
npx mongofire reset-local
|
|
375
|
+
```js
|
|
376
|
+
// model
|
|
377
|
+
NoteSchema.plugin(plugin('notes', { ownerField: 'userId' }));
|
|
429
378
|
```
|
|
430
379
|
|
|
431
380
|
```js
|
|
432
|
-
//
|
|
433
|
-
|
|
381
|
+
// create — always set the owner field
|
|
382
|
+
await Note.create({ title: 'My note', userId: req.user._id });
|
|
434
383
|
```
|
|
435
384
|
|
|
436
|
-
Both drop all local data and MongoFire state so the next startup re-bootstraps from Atlas cleanly.
|
|
437
|
-
|
|
438
385
|
---
|
|
439
386
|
|
|
440
387
|
## CLI Reference
|
|
441
388
|
|
|
442
389
|
```bash
|
|
443
|
-
npx mongofire init
|
|
444
|
-
npx mongofire init --force
|
|
445
|
-
npx mongofire init --esm
|
|
446
|
-
npx mongofire init --cjs
|
|
447
|
-
npx mongofire config
|
|
448
|
-
npx mongofire status
|
|
449
|
-
npx mongofire clean
|
|
450
|
-
npx mongofire
|
|
451
|
-
npx mongofire
|
|
452
|
-
npx mongofire reconcile
|
|
453
|
-
npx mongofire
|
|
454
|
-
npx mongofire reconcile --collection=orders # Single collection
|
|
455
|
-
npx mongofire reset-local # Safely wipe local DB and re-bootstrap
|
|
390
|
+
npx mongofire init # Setup wizard (creates mongofire.js, config, .env)
|
|
391
|
+
npx mongofire init --force # Overwrite existing files
|
|
392
|
+
npx mongofire init --esm # Force ESM output
|
|
393
|
+
npx mongofire init --cjs # Force CJS output
|
|
394
|
+
npx mongofire config # Update config interactively
|
|
395
|
+
npx mongofire status # Show pending sync counts
|
|
396
|
+
npx mongofire clean --days=7 # Delete records older than 7 days
|
|
397
|
+
npx mongofire conflicts # View and resolve conflicts
|
|
398
|
+
npx mongofire reconcile # Recover writes lost from crashes
|
|
399
|
+
npx mongofire reconcile --collection=users # Single collection
|
|
400
|
+
npx mongofire reset-local # Wipe local DB and re-bootstrap
|
|
456
401
|
```
|
|
457
402
|
|
|
458
|
-
|
|
459
|
-
| ------------- | --------------------------------------------------------- | ------------- | ------------------------------------- |
|
|
460
|
-
| `init` | Setup wizard | Optional | `--esm`, `--cjs`, `--force` |
|
|
461
|
-
| `config` | Update an existing config | Yes | — |
|
|
462
|
-
| `status` | Show pending ops and online state | No | — |
|
|
463
|
-
| `clean` | Delete old sync records | Optional | `--days=N` (1–3650, default 7) |
|
|
464
|
-
| `conflicts` | View and resolve conflicts interactively | Yes | — |
|
|
465
|
-
| `reconcile` | Recover writes lost from crashes | No | `--no-full-scan`, `--collection=NAME` |
|
|
466
|
-
| `reset-local` | Wipe local DB and all sync state for a clean re-bootstrap | Yes | — |
|
|
467
|
-
|
|
468
|
-
> **Tip:** Set `MONGOFIRE_DEBUG=1` for full error stack traces in any command.
|
|
403
|
+
> Set `MONGOFIRE_DEBUG=1` for full error stack traces.
|
|
469
404
|
|
|
470
405
|
---
|
|
471
406
|
|
|
472
407
|
## TypeScript
|
|
473
408
|
|
|
474
409
|
```ts
|
|
475
|
-
import
|
|
476
|
-
|
|
477
|
-
const config: SyncConfig = {
|
|
478
|
-
collections: ["orders", "products"],
|
|
479
|
-
atlasUri: process.env.ATLAS_URI,
|
|
480
|
-
realtime: true,
|
|
481
|
-
};
|
|
482
|
-
|
|
483
|
-
await mongofire.start(config);
|
|
410
|
+
import { startApp, plugin, mongofire } from './mongofire.js';
|
|
411
|
+
import type { SyncResult, ConflictData } from 'mongofire';
|
|
484
412
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
});
|
|
413
|
+
UserSchema.plugin(plugin('users'));
|
|
414
|
+
startApp(app, 3000);
|
|
488
415
|
|
|
489
|
-
mongofire.on(
|
|
490
|
-
console.
|
|
416
|
+
mongofire.on('sync', (result: SyncResult) => {
|
|
417
|
+
console.log(`↑${result.uploaded} ↓${result.downloaded}`);
|
|
491
418
|
});
|
|
492
419
|
```
|
|
493
420
|
|
|
@@ -495,24 +422,51 @@ mongofire.on("conflict", (c: ConflictData) => {
|
|
|
495
422
|
|
|
496
423
|
## Environment Variables
|
|
497
424
|
|
|
498
|
-
| Variable | Default
|
|
499
|
-
|
|
500
|
-
| `
|
|
501
|
-
| `
|
|
502
|
-
| `
|
|
425
|
+
| Variable | Default | Description |
|
|
426
|
+
|------------------------------------|--------------------------|------------------------------------------------------|
|
|
427
|
+
| `ATLAS_URI` | — | MongoDB Atlas connection string |
|
|
428
|
+
| `LOCAL_URI` | `mongodb://127.0.0.1:27017` | Local MongoDB URI |
|
|
429
|
+
| `DB_NAME` | `myapp` | Database name |
|
|
430
|
+
| `MONGOFIRE_DEBUG` | unset | Set to `1` for full stack traces |
|
|
431
|
+
| `MONGOFIRE_VERIFY_REMOTE` | `0` | Set to `1` to checksum-verify each uploaded document |
|
|
432
|
+
| `MONGOFIRE_COLLECTION_CONCURRENCY` | `4` | Collections synced in parallel (max 32) |
|
|
433
|
+
| `MONGOFIRE_DBPATH` | `~/.mongofire/<dbName>` | Data directory for auto-spawned mongod |
|
|
503
434
|
|
|
504
435
|
---
|
|
505
436
|
|
|
506
|
-
##
|
|
437
|
+
## Troubleshooting
|
|
438
|
+
|
|
439
|
+
### `ERR_MODULE_NOT_FOUND: Cannot find package 'mongofire'`
|
|
440
|
+
|
|
441
|
+
You are importing `from 'mongofire'` instead of `from './mongofire.js'`.
|
|
442
|
+
|
|
443
|
+
```js
|
|
444
|
+
// ❌ Wrong
|
|
445
|
+
import { plugin } from 'mongofire';
|
|
446
|
+
|
|
447
|
+
// ✅ Correct (from project root)
|
|
448
|
+
import { plugin } from './mongofire.js';
|
|
449
|
+
|
|
450
|
+
// ✅ Correct (from models/ folder)
|
|
451
|
+
import { plugin } from '../mongofire.js';
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
### `mongoose.connect() should not be called`
|
|
455
|
+
|
|
456
|
+
Remove any `mongoose.connect()` calls from your code. MongoFire manages the connection through `localUri` in `mongofire.config.js`.
|
|
457
|
+
|
|
458
|
+
### `Local MongoDB failed to connect`
|
|
459
|
+
|
|
460
|
+
- MongoDB is not installed — [download here](https://www.mongodb.com/try/download/community)
|
|
461
|
+
- `mongod` is not in your system PATH
|
|
462
|
+
- `LOCAL_URI` in `.env` is incorrect
|
|
463
|
+
- Port 27017 is blocked by a firewall
|
|
507
464
|
|
|
508
|
-
|
|
465
|
+
MongoFire tries to auto-spawn `mongod`. Set `MONGOFIRE_DBPATH` to a writable directory if the default fails.
|
|
509
466
|
|
|
510
|
-
|
|
511
|
-
- Contain only letters, digits, `_`, `-`, or `.`
|
|
512
|
-
- **Not** contain `:` — causes internal key collisions
|
|
513
|
-
- **Not** start with `_mf_` — reserved prefix
|
|
467
|
+
### `Cannot find module './mongofire.config.js'`
|
|
514
468
|
|
|
515
|
-
|
|
469
|
+
Run `npx mongofire init` to regenerate the config file.
|
|
516
470
|
|
|
517
471
|
---
|
|
518
472
|
|