mongo-realtime 2.0.3 → 3.0.0
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/.env.example +5 -0
- package/README.md +362 -438
- package/package.json +18 -6
- package/src/env.js +87 -0
- package/src/index.js +15 -0
- package/src/query.js +308 -0
- package/src/server.js +928 -0
- package/index.js +0 -595
- package/logo.png +0 -0
package/README.md
CHANGED
|
@@ -1,438 +1,362 @@
|
|
|
1
|
-
# Mongo Realtime
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
###
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
},
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
-
|
|
281
|
-
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
return true;
|
|
364
|
-
} catch (error) {
|
|
365
|
-
return false;
|
|
366
|
-
}
|
|
367
|
-
},
|
|
368
|
-
onSocket: (socket) => {
|
|
369
|
-
// setup a personal stream for each connected user
|
|
370
|
-
MongoRealtime.addStream(
|
|
371
|
-
`userPost:${socket.uid}`,
|
|
372
|
-
"posts",
|
|
373
|
-
(doc) => doc._id == socket.uid
|
|
374
|
-
);
|
|
375
|
-
},
|
|
376
|
-
offSocket: (socket) => {
|
|
377
|
-
// clean up when user disconnects
|
|
378
|
-
MongoRealtime.removeStream(`userPost:${socket.uid}`);
|
|
379
|
-
},
|
|
380
|
-
});
|
|
381
|
-
|
|
382
|
-
// ...
|
|
383
|
-
// or activate stream from a controller or middleware
|
|
384
|
-
app.get("/my-posts", (req, res) => {
|
|
385
|
-
const { user } = req;
|
|
386
|
-
MongoRealtime.addStream(
|
|
387
|
-
`userPosts:${user._id}`,
|
|
388
|
-
"posts",
|
|
389
|
-
(doc) => doc.authorId === user._id
|
|
390
|
-
);
|
|
391
|
-
|
|
392
|
-
res.send("Stream activated");
|
|
393
|
-
});
|
|
394
|
-
```
|
|
395
|
-
|
|
396
|
-
### Usecase with async filter
|
|
397
|
-
|
|
398
|
-
```javascript
|
|
399
|
-
// MongoRealtime.init({...});
|
|
400
|
-
|
|
401
|
-
MongoRealtime.addStream("authorizedUsers", "users", async (doc) => {
|
|
402
|
-
const isAdmin = await UserService.isAdmin(doc._id);
|
|
403
|
-
return isAdmin && doc.email.endsWith("@mydomain.com");
|
|
404
|
-
});
|
|
405
|
-
|
|
406
|
-
MongoRealtime.addStream(
|
|
407
|
-
"bestPosts",
|
|
408
|
-
"posts",
|
|
409
|
-
async (doc) => doc.likes > (await PostService.getLikesThreshold())
|
|
410
|
-
);
|
|
411
|
-
```
|
|
412
|
-
|
|
413
|
-
## 📚 Dependencies
|
|
414
|
-
|
|
415
|
-
- `socket.io`: WebSocket management
|
|
416
|
-
- `mongoose`: MongoDB ODM with Change Streams support
|
|
417
|
-
|
|
418
|
-
## 🐛 Troubleshooting
|
|
419
|
-
|
|
420
|
-
### MongoDB must be in Replica Set mode
|
|
421
|
-
|
|
422
|
-
To use Change Streams, MongoDB must be running as a replica set. For local development, you can initiate a single-node replica set:
|
|
423
|
-
|
|
424
|
-
```bash
|
|
425
|
-
mongod --replSet rs0
|
|
426
|
-
|
|
427
|
-
rs.initiate()
|
|
428
|
-
```
|
|
429
|
-
|
|
430
|
-
For any other issues, open an issue on the GitHub repository.
|
|
431
|
-
|
|
432
|
-
## 📄 License
|
|
433
|
-
|
|
434
|
-
MIT
|
|
435
|
-
|
|
436
|
-
## 🤝 Contributing
|
|
437
|
-
|
|
438
|
-
Contributions are welcome! Feel free to open an issue or submit a pull request.
|
|
1
|
+
# Mongo Realtime
|
|
2
|
+
|
|
3
|
+
MongoRealTime exposes MongoDB Change Streams over native WebSockets.\
|
|
4
|
+
Clients can subscribe to live updates on query results, perform CRUD operations, and send custom commands to the server.
|
|
5
|
+
|
|
6
|
+
## 📦 Installation
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm install mongo-realtime
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Quick start
|
|
13
|
+
|
|
14
|
+
```js
|
|
15
|
+
const { MongoRealTimeServer } = require("mongo-realtime");
|
|
16
|
+
|
|
17
|
+
const server = new MongoRealTimeServer({
|
|
18
|
+
mongoUri: "mongodb://localhost:27017/mydb",
|
|
19
|
+
dbName: "mydb",
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// Register a handler for custom commands sent with `realtime:emit`.
|
|
23
|
+
server.on("calculate", (payload) => {
|
|
24
|
+
return payload.reduce((sum, value) => sum + value, 0);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
await server.start();
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Server configuration
|
|
31
|
+
|
|
32
|
+
The constructor accepts these options:
|
|
33
|
+
|
|
34
|
+
- `host` - Host for the built-in HTTP server (`0.0.0.0` by default).
|
|
35
|
+
- `port` - Port for the built-in HTTP server (`3000` by default).
|
|
36
|
+
- `path` - WebSocket upgrade path (`/` by default).
|
|
37
|
+
- `mongoUri` - MongoDB connection URI.
|
|
38
|
+
- `dbName` - MongoDB database name.
|
|
39
|
+
- `cacheTtlMs` - Query result cache TTL in milliseconds (`300000` by default).
|
|
40
|
+
- `authenticate` - Optional async function to validate incoming socket connections.
|
|
41
|
+
- `server` - Optional existing HTTP server to attach the WebSocket endpoint.
|
|
42
|
+
- `mongoClient` - Optional existing `MongoClient` instance.
|
|
43
|
+
- `db` - Optional existing MongoDB `Db` instance.
|
|
44
|
+
- `logger` - Optional `{ info?, warn? }` logger object.
|
|
45
|
+
|
|
46
|
+
When `server` is omitted, the package creates and owns an HTTP server.
|
|
47
|
+
|
|
48
|
+
## Environment variables
|
|
49
|
+
|
|
50
|
+
The package can also read configuration from `.env`:
|
|
51
|
+
|
|
52
|
+
- `HOST`
|
|
53
|
+
- `PORT`
|
|
54
|
+
- `WS_PATH`
|
|
55
|
+
- `MONGODB_URI` or `MONGO_URI`
|
|
56
|
+
- `MONGODB_DB_NAME` or `MONGO_DB`
|
|
57
|
+
- `CACHE_TTL_MS`
|
|
58
|
+
- `CACHE_TTL_SECONDS`
|
|
59
|
+
|
|
60
|
+
## WebSocket protocol
|
|
61
|
+
|
|
62
|
+
### Supported message types
|
|
63
|
+
|
|
64
|
+
From client to server:
|
|
65
|
+
|
|
66
|
+
- `realtime:subscribe`
|
|
67
|
+
- `realtime:unsubscribe`
|
|
68
|
+
- `realtime:fetch`
|
|
69
|
+
- `realtime:insert`
|
|
70
|
+
- `realtime:update`
|
|
71
|
+
- `realtime:delete`
|
|
72
|
+
- `realtime:emit`
|
|
73
|
+
|
|
74
|
+
### `realtime:subscribe`
|
|
75
|
+
|
|
76
|
+
Subscribe to live matching documents and receive an initial result set.
|
|
77
|
+
|
|
78
|
+
```js
|
|
79
|
+
socket.send(
|
|
80
|
+
JSON.stringify({
|
|
81
|
+
type: "realtime:subscribe",
|
|
82
|
+
collection: "users",
|
|
83
|
+
filter: { active: true },
|
|
84
|
+
sort: { createdAt: -1 },
|
|
85
|
+
limit: 50,
|
|
86
|
+
queryId: "my-query-id",
|
|
87
|
+
}),
|
|
88
|
+
);
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
The server replies with:
|
|
92
|
+
|
|
93
|
+
```js
|
|
94
|
+
{
|
|
95
|
+
type: 'realtime:initial',
|
|
96
|
+
collection: 'users',
|
|
97
|
+
queryId: 'my-query-id',
|
|
98
|
+
documents: [ ... ],
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Live changes are delivered as separate events:
|
|
103
|
+
|
|
104
|
+
- `realtime:insert`
|
|
105
|
+
- `realtime:update`
|
|
106
|
+
- `realtime:delete`
|
|
107
|
+
|
|
108
|
+
### `realtime:fetch`
|
|
109
|
+
|
|
110
|
+
Fetch the current document set without keeping a live subscription.
|
|
111
|
+
|
|
112
|
+
```js
|
|
113
|
+
socket.send(
|
|
114
|
+
JSON.stringify({
|
|
115
|
+
type: "realtime:fetch",
|
|
116
|
+
collection: "users",
|
|
117
|
+
filter: { active: true },
|
|
118
|
+
sort: { createdAt: -1 },
|
|
119
|
+
limit: 50,
|
|
120
|
+
queryId: "fetch-1",
|
|
121
|
+
}),
|
|
122
|
+
);
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### `realtime:unsubscribe`
|
|
126
|
+
|
|
127
|
+
Stop a live subscription by its `queryId`:
|
|
128
|
+
|
|
129
|
+
```js
|
|
130
|
+
socket.send(
|
|
131
|
+
JSON.stringify({
|
|
132
|
+
type: "realtime:unsubscribe",
|
|
133
|
+
queryId: "my-query-id",
|
|
134
|
+
}),
|
|
135
|
+
);
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### `realtime:insert`
|
|
139
|
+
|
|
140
|
+
Insert a new document into a collection:
|
|
141
|
+
|
|
142
|
+
```js
|
|
143
|
+
socket.send(
|
|
144
|
+
JSON.stringify({
|
|
145
|
+
type: "realtime:insert",
|
|
146
|
+
collection: "users",
|
|
147
|
+
document: { name: "Alice", active: true },
|
|
148
|
+
}),
|
|
149
|
+
);
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### `realtime:update`
|
|
153
|
+
|
|
154
|
+
Update matching documents in a collection:
|
|
155
|
+
|
|
156
|
+
```js
|
|
157
|
+
socket.send(
|
|
158
|
+
JSON.stringify({
|
|
159
|
+
type: "realtime:update",
|
|
160
|
+
collection: "users",
|
|
161
|
+
filter: { _id: "507f1f77bcf86cd799439011" },
|
|
162
|
+
update: { $set: { active: false } },
|
|
163
|
+
}),
|
|
164
|
+
);
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
The server accepts both operator-style updates (`$set`, `$inc`, etc.) and replacement-style updates.
|
|
168
|
+
|
|
169
|
+
### `realtime:delete`
|
|
170
|
+
|
|
171
|
+
Delete matching documents from a collection:
|
|
172
|
+
|
|
173
|
+
```js
|
|
174
|
+
socket.send(
|
|
175
|
+
JSON.stringify({
|
|
176
|
+
type: "realtime:delete",
|
|
177
|
+
collection: "users",
|
|
178
|
+
filter: { active: false },
|
|
179
|
+
}),
|
|
180
|
+
);
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### `realtime:emit`
|
|
184
|
+
|
|
185
|
+
Send a custom command to the server and receive a response.
|
|
186
|
+
|
|
187
|
+
```js
|
|
188
|
+
socket.send(
|
|
189
|
+
JSON.stringify({
|
|
190
|
+
type: "realtime:emit",
|
|
191
|
+
event: "calculate",
|
|
192
|
+
payload: [1, 2, 3],
|
|
193
|
+
requestId: "request-1",
|
|
194
|
+
}),
|
|
195
|
+
);
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Server responses:
|
|
199
|
+
|
|
200
|
+
```js
|
|
201
|
+
{
|
|
202
|
+
type: 'realtime:emit:result',
|
|
203
|
+
event: 'calculate',
|
|
204
|
+
requestId: 'request-1',
|
|
205
|
+
data: 6,
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
or:
|
|
210
|
+
|
|
211
|
+
```js
|
|
212
|
+
{
|
|
213
|
+
type: 'realtime:emit:error',
|
|
214
|
+
event: 'calculate',
|
|
215
|
+
requestId: 'request-1',
|
|
216
|
+
error: 'No handler registered for event "calculate".',
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Server internal events
|
|
221
|
+
|
|
222
|
+
The server emit internal events that can be listened inside the backend code using `server.on('MY_INTERNAL_EVENT', handler)`.\
|
|
223
|
+
`MY_INTERNAL_EVENT` follows these patterns:
|
|
224
|
+
|
|
225
|
+
- `db:OPERATION_TYPE` : For any operation type on any collection
|
|
226
|
+
- `db:OPERATION_TYPE:COLLECTION_NAME` : For a specific operation type on a specific collection
|
|
227
|
+
- `db:OPERATION_TYPE:COLLECTION_NAME:DOCUMENT_ID` : For a specific operation type on a specific document
|
|
228
|
+
|
|
229
|
+
`OPERATION_TYPE` can be `insert`, `update`, `delete` or `change` (matches all).\
|
|
230
|
+
`COLLECTION_NAME` is the name of the collection, e.g. `users`.\
|
|
231
|
+
`DOCUMENT_ID` is the string representation of the document's `_id`, e.g. `507f1f77bcf86cd799439011`.
|
|
232
|
+
|
|
233
|
+
Example:
|
|
234
|
+
|
|
235
|
+
```js
|
|
236
|
+
server.on("db:insert:users", (change) => {
|
|
237
|
+
console.log("A new user was inserted:", change.document);
|
|
238
|
+
});
|
|
239
|
+
server.on("db:update:orders:507f1f77bcf86cd799439011", (change) => {
|
|
240
|
+
console.log(
|
|
241
|
+
"Order 507f1f77bcf86cd799439011 was updated:",
|
|
242
|
+
change.updateDescription,
|
|
243
|
+
);
|
|
244
|
+
});
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Error responses
|
|
248
|
+
|
|
249
|
+
If a request fails, the server sends:
|
|
250
|
+
|
|
251
|
+
```js
|
|
252
|
+
{
|
|
253
|
+
type: 'realtime:error',
|
|
254
|
+
error: 'Error message',
|
|
255
|
+
queryId?: 'my-query-id',
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## Message payloads from server
|
|
260
|
+
|
|
261
|
+
Live change messages follow this shape:
|
|
262
|
+
|
|
263
|
+
```js
|
|
264
|
+
{
|
|
265
|
+
type: 'realtime:insert' | 'realtime:update' | 'realtime:delete',
|
|
266
|
+
collection: 'users',
|
|
267
|
+
document: { ... } | null,
|
|
268
|
+
before?: { ... },
|
|
269
|
+
documentId?: '507f1f77bcf86cd799439011',
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
## Query filters
|
|
274
|
+
|
|
275
|
+
Supported filter operators include:
|
|
276
|
+
|
|
277
|
+
- `$eq`, `$ne`, `$gt`, `$gte`, `$lt`, `$lte`
|
|
278
|
+
- `$in`, `$nin`
|
|
279
|
+
- `$exists`
|
|
280
|
+
- `$regex`
|
|
281
|
+
- `$and`, `$or`, `$nor`
|
|
282
|
+
|
|
283
|
+
Nested document paths are supported. `_id` strings are automatically converted to `ObjectId` when possible.
|
|
284
|
+
|
|
285
|
+
## API
|
|
286
|
+
|
|
287
|
+
### `new MongoRealTimeServer(options)`
|
|
288
|
+
|
|
289
|
+
Creates a new server instance.
|
|
290
|
+
|
|
291
|
+
### `server.start()`
|
|
292
|
+
|
|
293
|
+
Connects to MongoDB, attaches WebSocket handlers, and starts listening if the package owns the HTTP server.
|
|
294
|
+
|
|
295
|
+
### `server.stop()`
|
|
296
|
+
|
|
297
|
+
Closes active subscriptions, connected sockets, and owned resources.
|
|
298
|
+
|
|
299
|
+
### `server.on(eventName, handler)`
|
|
300
|
+
|
|
301
|
+
Registers a handler for `realtime:emit` messages.
|
|
302
|
+
|
|
303
|
+
### `server.collection(name)`
|
|
304
|
+
|
|
305
|
+
Returns a MongoDB collection handle for direct access.
|
|
306
|
+
|
|
307
|
+
## Authentication
|
|
308
|
+
|
|
309
|
+
Provide an `authenticate` function to validate WebSocket connections. The incoming payload is read from the `auth` request header and parsed as JSON when possible.
|
|
310
|
+
|
|
311
|
+
Example:
|
|
312
|
+
|
|
313
|
+
```js
|
|
314
|
+
const server = new MongoRealTimeServer({
|
|
315
|
+
authenticate: async (authData) => {
|
|
316
|
+
// Perform your authentication logic here, e.g. check a token or session.
|
|
317
|
+
// In this example, we simply check if the authData matches a hardcoded token.
|
|
318
|
+
return authData === "my-auth-token";
|
|
319
|
+
},
|
|
320
|
+
});
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
## Example: attach to Express
|
|
324
|
+
|
|
325
|
+
```js
|
|
326
|
+
const http = require("node:http");
|
|
327
|
+
const express = require("express");
|
|
328
|
+
const { MongoRealTimeServer } = require("mongo-realtime");
|
|
329
|
+
|
|
330
|
+
const app = express();
|
|
331
|
+
const httpServer = http.createServer(app);
|
|
332
|
+
|
|
333
|
+
const realtimeServer = new MongoRealTimeServer({
|
|
334
|
+
server: httpServer, // needs to be the raw HTTP server, not the Express app
|
|
335
|
+
path: "/", // WebSocket path
|
|
336
|
+
mongoUri: "mongodb://localhost:27017/mydb",
|
|
337
|
+
dbName: "mydb",
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
await new Promise((resolve, reject) => {
|
|
341
|
+
httpServer.once("error", reject);
|
|
342
|
+
httpServer.listen(3000, "0.0.0.0", resolve);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
await realtimeServer.start(); // start the MongoRealTimeServer after the HTTP server is listening
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
## Notes
|
|
349
|
+
|
|
350
|
+
- MongoDB must run as a replica set for Change Streams.
|
|
351
|
+
- The package now uses native WebSockets (`ws`), no longer Socket.IO.
|
|
352
|
+
- Query results are cached for `cacheTtlMs` milliseconds when using `subscribe` or `fetch`.
|
|
353
|
+
|
|
354
|
+
## Dependencies
|
|
355
|
+
|
|
356
|
+
- `mongodb`
|
|
357
|
+
- `ws`
|
|
358
|
+
- `dotenv`
|
|
359
|
+
|
|
360
|
+
## License
|
|
361
|
+
|
|
362
|
+
MIT
|