mongofire 6.5.3 → 6.5.5
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 +21 -67
- package/README.md +365 -136
- package/dist/bin/mongofire.cjs +1 -851
- package/dist/src/changetrack.js +1 -1
- package/dist/src/compactor.js +1 -0
- package/dist/src/connection.js +1 -1
- package/dist/src/device.js +1 -1
- package/dist/src/diff.js +1 -0
- package/dist/src/field-merge.js +1 -0
- package/dist/src/index.cjs +1 -1
- package/dist/src/plugin.js +1 -1
- package/dist/src/plugin.mjs +6 -0
- package/dist/src/rate-limiter.js +1 -0
- package/dist/src/reconcile.js +1 -1
- package/dist/src/schema-manager.js +1 -0
- 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 +134 -307
- package/index.cjs +0 -2
- package/index.mjs +0 -1
- package/package.json +20 -19
- package/bin/mongofire.cjs +0 -852
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# 🔥 MongoFire
|
|
2
2
|
|
|
3
|
-
> **Offline-first MongoDB sync** — Local + Atlas feel like ONE database.
|
|
4
|
-
> Automatic conflict resolution, Mongoose plugin, zero boilerplate.
|
|
3
|
+
> **Offline-first MongoDB sync** — Local + Atlas feel like ONE database.
|
|
4
|
+
> Automatic conflict resolution, Mongoose plugin, interactive CLI, zero boilerplate.
|
|
5
5
|
|
|
6
6
|
[](https://www.npmjs.com/package/mongofire)
|
|
7
7
|
[](https://nodejs.org)
|
|
@@ -9,15 +9,18 @@
|
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
-
## What
|
|
12
|
+
## What is MongoFire?
|
|
13
13
|
|
|
14
|
-
MongoFire keeps a **local MongoDB** and **MongoDB Atlas** in sync automatically.
|
|
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 everything else in the background.
|
|
15
15
|
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
16
|
+
- **Offline-first** — your app never waits for the network
|
|
17
|
+
- **Automatic sync** — uploads local changes and downloads remote ones on a configurable interval
|
|
18
|
+
- **Real-time mode** — optional Atlas Change Streams for near-instant propagation
|
|
19
|
+
- **Conflict resolution** — deterministic last-writer-wins with version tracking; conflict events for manual handling when needed
|
|
20
|
+
- **Resumable bootstrap** — first sync streams from Atlas in batches; survives crashes and resumes exactly where it left off
|
|
21
|
+
- **Self-healing** — detects and recovers lost writes caused by crashes, local DB resets, or partial failures automatically
|
|
22
|
+
- **CLI tools** — interactive commands for status, conflict resolution, reconciliation, and safe local reset
|
|
23
|
+
- **TypeScript** — full type declarations included
|
|
21
24
|
|
|
22
25
|
---
|
|
23
26
|
|
|
@@ -27,26 +30,27 @@ MongoFire keeps a **local MongoDB** and **MongoDB Atlas** in sync automatically.
|
|
|
27
30
|
npm install mongofire
|
|
28
31
|
```
|
|
29
32
|
|
|
30
|
-
**Peer dependencies
|
|
33
|
+
**Peer dependencies:**
|
|
31
34
|
|
|
32
35
|
```bash
|
|
33
|
-
npm install mongodb mongoose
|
|
36
|
+
npm install mongodb mongoose dotenv
|
|
34
37
|
```
|
|
35
38
|
|
|
36
39
|
---
|
|
37
40
|
|
|
38
41
|
## Quick Start
|
|
39
42
|
|
|
40
|
-
### 1.
|
|
43
|
+
### 1. Run the setup wizard
|
|
41
44
|
|
|
42
45
|
```bash
|
|
43
46
|
npx mongofire init
|
|
44
47
|
```
|
|
45
48
|
|
|
46
|
-
|
|
49
|
+
The interactive wizard creates three files:
|
|
50
|
+
|
|
47
51
|
- `.env` — MongoDB connection strings
|
|
48
|
-
- `mongofire.config.js` — which collections to sync
|
|
49
|
-
- `mongofire.js` —
|
|
52
|
+
- `mongofire.config.js` — which collections to sync, intervals, and options
|
|
53
|
+
- `mongofire.js` — imports config and starts sync
|
|
50
54
|
|
|
51
55
|
### 2. Fill in `.env`
|
|
52
56
|
|
|
@@ -56,234 +60,459 @@ LOCAL_URI=mongodb://127.0.0.1:27017
|
|
|
56
60
|
DB_NAME=myapp
|
|
57
61
|
```
|
|
58
62
|
|
|
59
|
-
### 3.
|
|
63
|
+
### 3. Import mongofire.js in your app entry point
|
|
60
64
|
|
|
61
65
|
```js
|
|
62
66
|
// CommonJS
|
|
63
|
-
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
await mongofire.start(config);
|
|
67
|
+
require("./mongofire");
|
|
68
|
+
const mongofire = require("mongofire");
|
|
69
|
+
```
|
|
67
70
|
|
|
71
|
+
```js
|
|
68
72
|
// ESM
|
|
69
|
-
import mongofire
|
|
70
|
-
import
|
|
71
|
-
|
|
72
|
-
await mongofire.start(config);
|
|
73
|
+
import "./mongofire.js";
|
|
74
|
+
import mongofire from "mongofire";
|
|
73
75
|
```
|
|
74
76
|
|
|
75
|
-
### 4. Add the plugin to your Mongoose
|
|
77
|
+
### 4. Add the plugin to your Mongoose schemas
|
|
76
78
|
|
|
77
79
|
```js
|
|
78
|
-
const mongofire = require(
|
|
80
|
+
const mongofire = require("mongofire");
|
|
79
81
|
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
const OrderSchema = new mongoose.Schema({
|
|
83
|
+
items: Array,
|
|
84
|
+
total: Number,
|
|
85
|
+
updatedAt: Date,
|
|
84
86
|
});
|
|
85
87
|
|
|
86
|
-
//
|
|
87
|
-
// ownerField: field used for per-user data isolation (multi-tenant)
|
|
88
|
-
UserSchema.plugin(mongofire.plugin('users', { ownerField: 'userId' }));
|
|
88
|
+
OrderSchema.plugin(mongofire.plugin("orders")); // <— must be BEFORE creating the model
|
|
89
89
|
|
|
90
|
-
const
|
|
90
|
+
const Order = mongoose.model("Order", OrderSchema);
|
|
91
91
|
```
|
|
92
92
|
|
|
93
|
+
Every `save()`, `create()`, `update()`, and `delete()` is now tracked and synced automatically.
|
|
94
|
+
|
|
93
95
|
---
|
|
94
96
|
|
|
95
97
|
## Config Options
|
|
96
98
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
99
|
+
```js
|
|
100
|
+
// mongofire.config.js
|
|
101
|
+
module.exports = {
|
|
102
|
+
localUri: process.env.LOCAL_URI || "mongodb://127.0.0.1:27017",
|
|
103
|
+
atlasUri: process.env.ATLAS_URI,
|
|
104
|
+
dbName: process.env.DB_NAME || "myapp",
|
|
105
|
+
|
|
106
|
+
collections: ["orders", "products", "users"],
|
|
107
|
+
|
|
108
|
+
syncInterval: 30000, // ms between sync cycles (default: 30 s)
|
|
109
|
+
batchSize: 200, // documents per batch
|
|
110
|
+
syncOwner: "*", // '*' = sync all data (default)
|
|
111
|
+
realtime: false, // enable Atlas Change Streams
|
|
112
|
+
|
|
113
|
+
onSync(result) {
|
|
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
|
+
};
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### All config fields
|
|
125
|
+
|
|
126
|
+
| Option | Type | Default | Description |
|
|
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 |
|
|
109
140
|
|
|
110
141
|
---
|
|
111
142
|
|
|
112
143
|
## Events
|
|
113
144
|
|
|
114
145
|
```js
|
|
115
|
-
mongofire.on(
|
|
116
|
-
mongofire.on(
|
|
117
|
-
mongofire.on(
|
|
118
|
-
mongofire.on(
|
|
119
|
-
mongofire.on(
|
|
120
|
-
mongofire.on(
|
|
121
|
-
mongofire.on(
|
|
122
|
-
|
|
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));
|
|
123
159
|
```
|
|
124
160
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
161
|
+
| Event | Payload | When emitted |
|
|
162
|
+
| ------------------- | ------------------------------ | ------------------------------------------ |
|
|
163
|
+
| `ready` | — | `start()` completed |
|
|
164
|
+
| `online` | — | Atlas connection established |
|
|
165
|
+
| `offline` | — | Atlas becomes unreachable |
|
|
166
|
+
| `sync` | `SyncResult` | After each sync cycle |
|
|
167
|
+
| `conflict` | `ConflictData` | Local write conflicts with remote |
|
|
168
|
+
| `conflictResolved` | `{ opId, resolution }` | After `retryConflict` or `dismissConflict` |
|
|
169
|
+
| `reconcileComplete` | `{ collections, totalQueued }` | After reconciliation scan |
|
|
170
|
+
| `realtimeStarted` | — | Change streams activated |
|
|
171
|
+
| `realtimeStopped` | — | Change streams stopped |
|
|
172
|
+
| `stopped` | — | `stop()` finished |
|
|
173
|
+
| `error` | `Error` | Unexpected sync error |
|
|
136
174
|
|
|
137
175
|
---
|
|
138
176
|
|
|
139
177
|
## API
|
|
140
178
|
|
|
141
179
|
### `mongofire.start(config)` → `Promise<MongoFire>`
|
|
142
|
-
|
|
180
|
+
|
|
181
|
+
Connect to local MongoDB and Atlas, run the initial sync, start background polling. Safe to call multiple times — concurrent calls share the same init promise.
|
|
143
182
|
|
|
144
183
|
### `mongofire.stop(timeoutMs?)` → `Promise<void>`
|
|
145
|
-
|
|
184
|
+
|
|
185
|
+
Flush any in-flight operations and close all connections. Default timeout: **10,000 ms**.
|
|
146
186
|
|
|
147
187
|
### `mongofire.sync(type?)` → `Promise<SyncResult>`
|
|
148
|
-
|
|
188
|
+
|
|
189
|
+
Manually trigger a sync. Returns `{ error: 'offline', pending }` when Atlas is unreachable. Throttled to a minimum of 500 ms between calls.
|
|
190
|
+
|
|
191
|
+
| `type` | Behaviour |
|
|
192
|
+
| ------------ | --------------------------------------------------- |
|
|
193
|
+
| `'required'` | Upload pending ops + download new changes (default) |
|
|
194
|
+
| `'all'` | Full bi-directional sync |
|
|
149
195
|
|
|
150
196
|
### `mongofire.status()` → `Promise<SyncStatus>`
|
|
151
|
-
Get pending op counts and online/realtime status.
|
|
152
197
|
|
|
153
|
-
|
|
154
|
-
|
|
198
|
+
```ts
|
|
199
|
+
interface SyncStatus {
|
|
200
|
+
online: boolean;
|
|
201
|
+
pending: number; // total unsynced operations
|
|
202
|
+
creates: number;
|
|
203
|
+
updates: number;
|
|
204
|
+
deletes: number;
|
|
205
|
+
realtime: boolean;
|
|
206
|
+
}
|
|
207
|
+
```
|
|
155
208
|
|
|
156
|
-
### `mongofire.
|
|
157
|
-
Returns a Mongoose schema plugin. Options:
|
|
209
|
+
### `mongofire.clean(days?, opts?)` → `Promise<number>`
|
|
158
210
|
|
|
159
|
-
|
|
160
|
-
|---|---|---|---|
|
|
161
|
-
| `ownerField` | `string` | `null` | Dot-path to owner key field (e.g. `'userId'` or `'org._id'`) |
|
|
162
|
-
| `batchSize` | `number` | `200` | Batch size for bulk operations |
|
|
163
|
-
| `concurrency` | `number` | `8` | Concurrent change tracking calls per batch |
|
|
211
|
+
Delete old synced records to keep the local database tidy.
|
|
164
212
|
|
|
165
|
-
|
|
213
|
+
| Parameter | Default | Description |
|
|
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 |
|
|
166
217
|
|
|
167
|
-
|
|
218
|
+
### `mongofire.conflicts(collection?)` → `Promise<ConflictRecord[]>`
|
|
168
219
|
|
|
169
|
-
|
|
220
|
+
```js
|
|
221
|
+
const list = await mongofire.conflicts();
|
|
222
|
+
for (const c of list) {
|
|
223
|
+
console.log(`${c.collection}/${c.docId} op:${c.type} v${c.version}`);
|
|
224
|
+
console.log("Error:", c.lastError);
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### `mongofire.retryConflict(opId)` → `Promise<void>`
|
|
229
|
+
|
|
230
|
+
Reset a conflict back to pending so the next sync retries it. Emits `conflictResolved` with `resolution: 'retried'`.
|
|
231
|
+
|
|
232
|
+
### `mongofire.dismissConflict(opId)` → `Promise<void>`
|
|
233
|
+
|
|
234
|
+
Dismiss a conflict — remote version wins and the local change is discarded. Emits `conflictResolved` with `resolution: 'dismissed'`.
|
|
235
|
+
|
|
236
|
+
### `mongofire.reconcile(collectionOrOpts?, opts?)` → `Promise<ReconcileResult[]>`
|
|
237
|
+
|
|
238
|
+
Scan for writes lost in a crash and re-queue them for sync.
|
|
170
239
|
|
|
171
240
|
```js
|
|
172
|
-
//
|
|
173
|
-
|
|
174
|
-
|
|
241
|
+
await mongofire.reconcile(); // all collections
|
|
242
|
+
await mongofire.reconcile({ fullScan: false }); // fast scan only
|
|
243
|
+
await mongofire.reconcile("orders"); // single collection
|
|
244
|
+
```
|
|
175
245
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
246
|
+
| Phase | What it checks |
|
|
247
|
+
| ------- | ------------------------------------------------------- |
|
|
248
|
+
| Phase 1 | Metadata rows with no matching operation entry |
|
|
249
|
+
| Phase 2 | Data documents with no metadata entry (`fullScan` only) |
|
|
250
|
+
|
|
251
|
+
### `mongofire.resetLocal()` → `Promise<{ dropped: number }>`
|
|
252
|
+
|
|
253
|
+
Safely wipe the entire local database and all MongoFire state. The next `start()` re-bootstraps from Atlas cleanly.
|
|
254
|
+
|
|
255
|
+
```js
|
|
256
|
+
// Check for unsynced changes first
|
|
257
|
+
const { pending } = await mongofire.status();
|
|
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
|
+
);
|
|
179
266
|
```
|
|
180
267
|
|
|
268
|
+
> **Warning:** Any unsynced local changes are permanently lost. Use `mongofire.status()` first if you need to verify there is nothing pending.
|
|
269
|
+
|
|
270
|
+
### `mongofire.plugin(collectionName, options?)`
|
|
271
|
+
|
|
272
|
+
```js
|
|
273
|
+
// Basic
|
|
274
|
+
OrderSchema.plugin(mongofire.plugin("orders"));
|
|
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
|
+
);
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
| Option | Type | Default | Description |
|
|
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 |
|
|
291
|
+
|
|
181
292
|
---
|
|
182
293
|
|
|
183
294
|
## Real-Time Sync
|
|
184
295
|
|
|
185
|
-
Enable
|
|
296
|
+
Enable Atlas Change Streams for near-instant propagation between devices:
|
|
186
297
|
|
|
187
298
|
```js
|
|
188
299
|
await mongofire.start({
|
|
189
|
-
|
|
190
|
-
|
|
300
|
+
atlasUri: process.env.ATLAS_URI,
|
|
301
|
+
collections: ["orders"],
|
|
302
|
+
realtime: true, // requires Atlas M10+ or a local replica set
|
|
303
|
+
syncInterval: 5000, // polling fallback interval
|
|
191
304
|
});
|
|
305
|
+
|
|
306
|
+
mongofire.on("realtimeStarted", () => console.log("Changes appear instantly"));
|
|
192
307
|
```
|
|
193
308
|
|
|
194
|
-
|
|
309
|
+
Falls back to polling automatically if Change Streams are unavailable.
|
|
195
310
|
|
|
196
311
|
---
|
|
197
312
|
|
|
198
|
-
## Multi-Tenant
|
|
313
|
+
## Multi-Tenant
|
|
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**.
|
|
319
|
+
|
|
320
|
+
### Do you need it?
|
|
321
|
+
|
|
322
|
+
| App type | Need multi-tenant? |
|
|
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** |
|
|
330
|
+
|
|
331
|
+
### Setup (4 steps)
|
|
332
|
+
|
|
333
|
+
**Step 1 — Add an owner field to every synced model**
|
|
199
334
|
|
|
200
335
|
```js
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
336
|
+
const OrderSchema = new mongoose.Schema({
|
|
337
|
+
items: Array,
|
|
338
|
+
total: Number,
|
|
339
|
+
userId: { type: mongoose.Types.ObjectId, required: true },
|
|
340
|
+
updatedAt: Date,
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
OrderSchema.plugin(mongofire.plugin("orders", { ownerField: "userId" }));
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
**Step 2 — Set `syncOwner` in config**
|
|
347
|
+
|
|
348
|
+
```js
|
|
349
|
+
module.exports = {
|
|
350
|
+
collections: ["orders", "products"],
|
|
351
|
+
syncOwner: "userId",
|
|
205
352
|
// ...
|
|
353
|
+
};
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
**Step 3 — Pass the current user's ID when starting sync**
|
|
357
|
+
|
|
358
|
+
```js
|
|
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
|
+
```
|
|
376
|
+
|
|
377
|
+
**Step 4 — Always set the owner field when creating documents**
|
|
378
|
+
|
|
379
|
+
```js
|
|
380
|
+
const order = await Order.create({
|
|
381
|
+
items: req.body.items,
|
|
382
|
+
total: req.body.total,
|
|
383
|
+
userId: req.user._id,
|
|
206
384
|
});
|
|
207
385
|
```
|
|
208
386
|
|
|
209
|
-
|
|
387
|
+
### Dynamic owner using a function
|
|
388
|
+
|
|
389
|
+
```js
|
|
390
|
+
await mongofire.start({
|
|
391
|
+
...config,
|
|
392
|
+
syncOwner: () => getCurrentUser()?.id ?? null,
|
|
393
|
+
});
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
> **Security note:** If `syncOwner` is a function and it throws, the sync is **aborted** and an `error` event is emitted. MongoFire never falls back to syncing all data on error.
|
|
397
|
+
|
|
398
|
+
---
|
|
399
|
+
|
|
400
|
+
## Using the plugin directly (without the MongoFire instance)
|
|
401
|
+
|
|
402
|
+
```js
|
|
403
|
+
// CommonJS
|
|
404
|
+
const mongofirePlugin = require("mongofire/plugin");
|
|
405
|
+
OrderSchema.plugin(mongofirePlugin, { collection: "orders" });
|
|
406
|
+
|
|
407
|
+
// CommonJS factory
|
|
408
|
+
const { factory } = require("mongofire/plugin");
|
|
409
|
+
OrderSchema.plugin(factory("orders"));
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
```js
|
|
413
|
+
// ESM
|
|
414
|
+
import mongofirePlugin, { factory } from "mongofire/plugin";
|
|
415
|
+
OrderSchema.plugin(factory("orders"));
|
|
416
|
+
```
|
|
210
417
|
|
|
211
418
|
---
|
|
212
419
|
|
|
213
|
-
##
|
|
420
|
+
## Safe Local Reset
|
|
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:
|
|
214
425
|
|
|
215
426
|
```bash
|
|
216
|
-
#
|
|
217
|
-
npx mongofire
|
|
427
|
+
# Interactive CLI — confirms before wiping
|
|
428
|
+
npx mongofire reset-local
|
|
429
|
+
```
|
|
218
430
|
|
|
219
|
-
|
|
220
|
-
|
|
431
|
+
```js
|
|
432
|
+
// Programmatic
|
|
433
|
+
const { dropped } = await mongofire.resetLocal();
|
|
434
|
+
```
|
|
221
435
|
|
|
222
|
-
|
|
223
|
-
npx mongofire status
|
|
436
|
+
Both drop all local data and MongoFire state so the next startup re-bootstraps from Atlas cleanly.
|
|
224
437
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
438
|
+
---
|
|
439
|
+
|
|
440
|
+
## CLI Reference
|
|
441
|
+
|
|
442
|
+
```bash
|
|
443
|
+
npx mongofire init # Interactive setup wizard
|
|
444
|
+
npx mongofire init --force # Overwrite existing files
|
|
445
|
+
npx mongofire init --esm # Force ESM output
|
|
446
|
+
npx mongofire init --cjs # Force CommonJS output
|
|
447
|
+
npx mongofire config # Update an existing config
|
|
448
|
+
npx mongofire status # Show pending sync counts
|
|
449
|
+
npx mongofire clean # Delete old records (interactive)
|
|
450
|
+
npx mongofire clean --days=14 # Delete records older than 14 days
|
|
451
|
+
npx mongofire conflicts # View and resolve conflicts
|
|
452
|
+
npx mongofire reconcile # Recover writes lost from crashes
|
|
453
|
+
npx mongofire reconcile --no-full-scan # Fast scan (Phase 1 only)
|
|
454
|
+
npx mongofire reconcile --collection=orders # Single collection
|
|
455
|
+
npx mongofire reset-local # Safely wipe local DB and re-bootstrap
|
|
228
456
|
```
|
|
229
457
|
|
|
458
|
+
| Command | Description | TTY required? | Key flags |
|
|
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.
|
|
469
|
+
|
|
230
470
|
---
|
|
231
471
|
|
|
232
472
|
## TypeScript
|
|
233
473
|
|
|
234
|
-
Full TypeScript support is included:
|
|
235
|
-
|
|
236
474
|
```ts
|
|
237
|
-
import mongofire, {
|
|
475
|
+
import mongofire, { SyncConfig, SyncResult, ConflictData } from "mongofire";
|
|
238
476
|
|
|
239
477
|
const config: SyncConfig = {
|
|
240
|
-
collections: [
|
|
478
|
+
collections: ["orders", "products"],
|
|
241
479
|
atlasUri: process.env.ATLAS_URI,
|
|
242
480
|
realtime: true,
|
|
243
481
|
};
|
|
244
482
|
|
|
245
483
|
await mongofire.start(config);
|
|
246
484
|
|
|
247
|
-
mongofire.on(
|
|
248
|
-
console.log(`
|
|
485
|
+
mongofire.on("sync", (result: SyncResult) => {
|
|
486
|
+
console.log(`up:${result.uploaded} down:${result.downloaded}`);
|
|
249
487
|
});
|
|
250
488
|
|
|
251
|
-
mongofire.on(
|
|
252
|
-
console.warn(`Conflict
|
|
489
|
+
mongofire.on("conflict", (c: ConflictData) => {
|
|
490
|
+
console.warn(`Conflict: ${c.collection}/${c.docId} op:${c.op}`);
|
|
253
491
|
});
|
|
254
492
|
```
|
|
255
493
|
|
|
256
494
|
---
|
|
257
495
|
|
|
258
|
-
##
|
|
496
|
+
## Environment Variables
|
|
259
497
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
6. **Offline** — All reads/writes work locally. Changes queue up and upload automatically when Atlas reconnects
|
|
498
|
+
| Variable | Default | Description |
|
|
499
|
+
| ---------------------------------- | ------- | ---------------------------------------------------- |
|
|
500
|
+
| `MONGOFIRE_DEBUG` | unset | Set to `1` for full error stack traces |
|
|
501
|
+
| `MONGOFIRE_VERIFY_REMOTE` | `0` | Set to `1` to checksum-verify each uploaded document |
|
|
502
|
+
| `MONGOFIRE_COLLECTION_CONCURRENCY` | `4` | Collections synced in parallel (capped at 32) |
|
|
266
503
|
|
|
267
504
|
---
|
|
268
505
|
|
|
269
506
|
## Collection Name Rules
|
|
270
507
|
|
|
271
|
-
|
|
272
|
-
- Start with a letter or digit
|
|
273
|
-
- Contain only letters, digits, underscores (`_`), hyphens (`-`), or dots (`.`)
|
|
274
|
-
- **Not** contain a colon (`:`)
|
|
275
|
-
- **Not** start with `_mf_` (reserved for MongoFire internal use)
|
|
276
|
-
|
|
277
|
-
Invalid names throw a clear error at startup.
|
|
278
|
-
|
|
279
|
-
---
|
|
508
|
+
Names must:
|
|
280
509
|
|
|
281
|
-
|
|
510
|
+
- Start with a letter or digit
|
|
511
|
+
- Contain only letters, digits, `_`, `-`, or `.`
|
|
512
|
+
- **Not** contain `:` — causes internal key collisions
|
|
513
|
+
- **Not** start with `_mf_` — reserved prefix
|
|
282
514
|
|
|
283
|
-
|
|
284
|
-
|---|---|---|
|
|
285
|
-
| `MONGOFIRE_VERIFY_REMOTE` | `0` | Set to `1` to verify each uploaded doc with a checksum round-trip |
|
|
286
|
-
| `MONGOFIRE_COLLECTION_CONCURRENCY` | `4` | Number of collections synced in parallel |
|
|
515
|
+
Invalid names are rejected at startup with a clear error message.
|
|
287
516
|
|
|
288
517
|
---
|
|
289
518
|
|