mongo-realtime 1.1.3 → 1.2.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/README.md +40 -25
- package/index.js +142 -80
- package/package.json +27 -26
package/README.md
CHANGED
|
@@ -29,18 +29,19 @@ npm install mongo-realtime
|
|
|
29
29
|
```javascript
|
|
30
30
|
const express = require("express");
|
|
31
31
|
const http = require("http");
|
|
32
|
-
const mongoose = require("mongoose");
|
|
33
32
|
const MongoRealtime = require("mongo-realtime");
|
|
34
33
|
|
|
35
34
|
const app = express();
|
|
36
35
|
const server = http.createServer(app);
|
|
37
36
|
|
|
38
|
-
mongoose.connect("mongodb://localhost:27017/mydb").then((c) => {
|
|
39
|
-
console.log("Connected to db", c.connection.name);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
37
|
MongoRealtime.init({
|
|
43
|
-
|
|
38
|
+
dbUri: "mongodb://localhost:27017/mydb",
|
|
39
|
+
onDbConnect: (conn) => {
|
|
40
|
+
console.log("Connected to db", conn.name);
|
|
41
|
+
},
|
|
42
|
+
onDbError: (err) => {
|
|
43
|
+
console.log(err.message);
|
|
44
|
+
},
|
|
44
45
|
server: server,
|
|
45
46
|
ignore: ["posts"], // ignore 'posts' collection
|
|
46
47
|
onSocket: (socket) => {
|
|
@@ -67,17 +68,20 @@ Initializes the socket system and MongoDB Change Streams.
|
|
|
67
68
|
|
|
68
69
|
\* means required
|
|
69
70
|
|
|
70
|
-
| Parameter | Type
|
|
71
|
-
| ------------------------ |
|
|
72
|
-
| `options.
|
|
73
|
-
| `options.
|
|
74
|
-
| `options.
|
|
75
|
-
| `options.
|
|
76
|
-
| `options.
|
|
77
|
-
| `options.
|
|
78
|
-
| `options.
|
|
79
|
-
| `options.
|
|
80
|
-
| `options.
|
|
71
|
+
| Parameter | Type | Description |
|
|
72
|
+
| ------------------------ | ----------------- | -------------------------------------------------------------------------------- |
|
|
73
|
+
| `options.dbUri` | `String`\* | Database URI |
|
|
74
|
+
| `options.dbOptions` | `Object` | Mongoose connection options |
|
|
75
|
+
| `options.onDbConnect` | `Function` | Callback on successful database connection |
|
|
76
|
+
| `options.onDbError` | `Function` | Callback on database connection error |
|
|
77
|
+
| `options.server` | `http.Server`\* | HTTP server to attach Socket.IO |
|
|
78
|
+
| `options.authentify` | `Function` | Function to authenticate socket connections. Should return true if authenticated |
|
|
79
|
+
| `options.middlewares` | `Array[Function]` | Array of Socket.IO middlewares |
|
|
80
|
+
| `options.onSocket` | `Function` | Callback on socket connection |
|
|
81
|
+
| `options.offSocket` | `Function` | Callback on socket disconnection |
|
|
82
|
+
| `options.watch` | `Array[String]` | Collections to only watch. Listen to all when is empty |
|
|
83
|
+
| `options.ignore` | `Array[String]` | Collections to only ignore. Overrides watch array |
|
|
84
|
+
| `options.autoListStream` | `Array[String]` | Collections to automatically stream to clients. Default is all |
|
|
81
85
|
|
|
82
86
|
#### Static Properties and Methods
|
|
83
87
|
|
|
@@ -160,7 +164,7 @@ Each event contains the full MongoDB change object:
|
|
|
160
164
|
|
|
161
165
|
```javascript
|
|
162
166
|
MongoRealtime.init({
|
|
163
|
-
|
|
167
|
+
dbUri: "mongodb://localhost:27017/mydb",
|
|
164
168
|
server: server,
|
|
165
169
|
onSocket: (socket) => {
|
|
166
170
|
socket.on("subscribe:users", () => {
|
|
@@ -209,7 +213,7 @@ MongoRealtime.io.to("users-room").emit("custom-event", data);
|
|
|
209
213
|
|
|
210
214
|
```javascript
|
|
211
215
|
MongoRealtime.init({
|
|
212
|
-
|
|
216
|
+
dbUri: "mongodb://localhost:27017/mydb",
|
|
213
217
|
server: server,
|
|
214
218
|
onSocket: (socket) => {
|
|
215
219
|
socket.on("error", (error) => {
|
|
@@ -228,6 +232,15 @@ MongoRealtime.init({
|
|
|
228
232
|
|
|
229
233
|
### Socket Authentication
|
|
230
234
|
|
|
235
|
+
You can provide an `authentify` function in the init options to authenticate socket connections.\
|
|
236
|
+
The function receives the token (from `socket.handshake.auth.token` or `socket.handshake.headers.authorization`) and the socket object.\
|
|
237
|
+
When setted, it rejects connections based on this logic:
|
|
238
|
+
|
|
239
|
+
- Token not provided -> error `NO_TOKEN_PROVIDED`
|
|
240
|
+
- Token invalid or returns `false` -> error `UNAUTHORIZED`
|
|
241
|
+
- Any other error -> error `AUTH_ERROR`
|
|
242
|
+
- Return `true` to accept the connection
|
|
243
|
+
|
|
231
244
|
```javascript
|
|
232
245
|
function authenticateSocket(token, socket) {
|
|
233
246
|
const verify = AuthService.verifyToken(token);
|
|
@@ -239,7 +252,7 @@ function authenticateSocket(token, socket) {
|
|
|
239
252
|
}
|
|
240
253
|
|
|
241
254
|
MongoRealtime.init({
|
|
242
|
-
|
|
255
|
+
dbUri: "mongodb://localhost:27017/mydb",
|
|
243
256
|
server: server,
|
|
244
257
|
authentify: authenticateSocket,
|
|
245
258
|
middlewares: [
|
|
@@ -264,7 +277,7 @@ On init, when `safeListStream` is `true`(default), two list streams can't have t
|
|
|
264
277
|
|
|
265
278
|
```javascript
|
|
266
279
|
MongoRealtime.init({
|
|
267
|
-
|
|
280
|
+
dbUri: "mongodb://localhost:27017/mydb",
|
|
268
281
|
server: server,
|
|
269
282
|
autoListStream: ["users"], // automatically stream users collection only
|
|
270
283
|
});
|
|
@@ -291,16 +304,16 @@ To avoid all these issues, you can set `safeListStream` to `false` in the init o
|
|
|
291
304
|
|
|
292
305
|
```javascript
|
|
293
306
|
MongoRealtime.init({
|
|
294
|
-
|
|
307
|
+
dbUri: "mongodb://localhost:27017/mydb",
|
|
295
308
|
server: server,
|
|
296
309
|
autoListStream: [], // stream no collection automatically (you can add your own filtered streams later)
|
|
297
310
|
});
|
|
298
311
|
// or
|
|
299
312
|
MongoRealtime.init({
|
|
300
|
-
|
|
313
|
+
dbUri: "mongodb://localhost:27017/mydb",
|
|
301
314
|
server: server,
|
|
302
315
|
safeListStream: false, // disable safe mode (you can override existing streams)
|
|
303
|
-
// Still stream all collections automatically but you can override them
|
|
316
|
+
// Still stream all collections automatically but you can override them
|
|
304
317
|
}):
|
|
305
318
|
|
|
306
319
|
MongoRealtime.addListStream("posts", "posts", (doc) => !!doc.title); // client can listen to db:stream:posts
|
|
@@ -311,7 +324,7 @@ MongoRealtime.addListStream("users", "users", (doc) => !!doc.email); // will not
|
|
|
311
324
|
|
|
312
325
|
```javascript
|
|
313
326
|
MongoRealtime.init({
|
|
314
|
-
|
|
327
|
+
dbUri: "mongodb://localhost:27017/mydb",
|
|
315
328
|
server: server,
|
|
316
329
|
authentify: (token, socket) => {
|
|
317
330
|
try {
|
|
@@ -379,6 +392,8 @@ MongoRealtime.addListStream(
|
|
|
379
392
|
|
|
380
393
|
### MongoDB must be in Replica Set mode
|
|
381
394
|
|
|
395
|
+
To use Change Streams, MongoDB must be running as a replica set. For local development, you can initiate a single-node replica set:
|
|
396
|
+
|
|
382
397
|
```bash
|
|
383
398
|
mongod --replSet rs0
|
|
384
399
|
|
package/index.js
CHANGED
|
@@ -1,14 +1,7 @@
|
|
|
1
|
+
const mongoose = require("mongoose");
|
|
1
2
|
const { Server } = require("socket.io");
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const out = {};
|
|
5
|
-
for (let k of Object.keys(obj).sort()) {
|
|
6
|
-
const v = obj[k];
|
|
7
|
-
out[k] = typeof v == "object" && !Array.isArray(v) ? sortObj(v) : v;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
return out;
|
|
11
|
-
}
|
|
3
|
+
const { version } = require("./package.json");
|
|
4
|
+
const chalk = require("chalk");
|
|
12
5
|
|
|
13
6
|
/**
|
|
14
7
|
* @typedef {Object} ChangeStreamDocument
|
|
@@ -38,7 +31,8 @@ function sortObj(obj = {}) {
|
|
|
38
31
|
|
|
39
32
|
class MongoRealtime {
|
|
40
33
|
/** @type {import("socket.io").Server} */ static io;
|
|
41
|
-
/** @type {import("mongoose").Connection} */ static connection
|
|
34
|
+
/** @type {import("mongoose").Connection} */ static connection =
|
|
35
|
+
mongoose.connection;
|
|
42
36
|
/** @type {Record<String, [(change:ChangeStreamDocument)=>void]>} */ static #listeners =
|
|
43
37
|
{};
|
|
44
38
|
/** @type {Record<String, [Object]>} */
|
|
@@ -53,13 +47,56 @@ class MongoRealtime {
|
|
|
53
47
|
|
|
54
48
|
static #safeListStream = true;
|
|
55
49
|
|
|
50
|
+
static version = version;
|
|
51
|
+
|
|
52
|
+
static #check(fn, err) {
|
|
53
|
+
const result = fn();
|
|
54
|
+
if (!result) {
|
|
55
|
+
let src = fn.toString().trim();
|
|
56
|
+
|
|
57
|
+
let match =
|
|
58
|
+
src.match(/=>\s*([^{};]+)$/) ||
|
|
59
|
+
src.match(/return\s+([^;}]*)/) ||
|
|
60
|
+
src.match(/{([^}]*)}/);
|
|
61
|
+
|
|
62
|
+
const expr = err ?? (match ? match[1].trim() : src);
|
|
63
|
+
|
|
64
|
+
throw new Error(`MongoRealtime failed to check "${expr}"`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
static #log(message, type = 0) {
|
|
69
|
+
const text = `[REALTIME] ${message}`;
|
|
70
|
+
switch (type) {
|
|
71
|
+
case 1:
|
|
72
|
+
console.log(chalk.bold.hex('#11AA60FF')(text));
|
|
73
|
+
break;
|
|
74
|
+
case 2:
|
|
75
|
+
console.log(chalk.bold.bgHex("#257993")(text));
|
|
76
|
+
break;
|
|
77
|
+
case 3:
|
|
78
|
+
console.log(chalk.bold.yellow(text));
|
|
79
|
+
break;
|
|
80
|
+
case 4:
|
|
81
|
+
console.log(chalk.bold.red(text));
|
|
82
|
+
break;
|
|
83
|
+
|
|
84
|
+
default:
|
|
85
|
+
console.log(text);
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
56
90
|
/**
|
|
57
91
|
* Initializes the socket system.
|
|
58
92
|
*
|
|
59
93
|
* @param {Object} options
|
|
60
|
-
* @param {
|
|
94
|
+
* @param {String} options.dbUri - Database URI
|
|
95
|
+
* @param {mongoose.ConnectOptions | undefined} options.dbOptions - Database connect options
|
|
61
96
|
* @param {(token:String, socket: import("socket.io").Socket) => boolean | Promise<boolean>} options.authentify - Auth function that should return true if `token` is valid
|
|
62
97
|
* @param {[( socket: import("socket.io").Socket, next: (err?: ExtendedError) => void) => void]} options.middlewares - Register mmiddlewares on incoming socket
|
|
98
|
+
* @param {(conn:mongoose.Connection) => void} options.onDbConnect - Callback triggered when a socket connects
|
|
99
|
+
* @param {(err:Error) => void} options.onDbError - Callback triggered when a socket connects
|
|
63
100
|
* @param {(socket: import("socket.io").Socket) => void} options.onSocket - Callback triggered when a socket connects
|
|
64
101
|
* @param {(socket: import("socket.io").Socket, reason: import("socket.io").DisconnectReason) => void} options.offSocket - Callback triggered when a socket disconnects
|
|
65
102
|
* @param {import("http").Server} options.server - HTTP server to attach Socket.IO to
|
|
@@ -69,81 +106,30 @@ class MongoRealtime {
|
|
|
69
106
|
* @param {bool} options.safeListStream - If true(default), declaring an existing streamId will throw an error
|
|
70
107
|
*
|
|
71
108
|
*/
|
|
72
|
-
static init({
|
|
73
|
-
|
|
109
|
+
static async init({
|
|
110
|
+
dbUri,
|
|
111
|
+
dbOptions,
|
|
74
112
|
server,
|
|
113
|
+
onDbConnect,
|
|
114
|
+
onDbError,
|
|
75
115
|
authentify,
|
|
76
|
-
middlewares = [],
|
|
77
|
-
autoListStream,
|
|
78
116
|
onSocket,
|
|
79
117
|
offSocket,
|
|
80
118
|
safeListStream = true,
|
|
119
|
+
autoListStream,
|
|
120
|
+
middlewares = [],
|
|
81
121
|
watch = [],
|
|
82
122
|
ignore = [],
|
|
83
123
|
}) {
|
|
124
|
+
console.clear();
|
|
125
|
+
this.#log(`MongoRealtime version (${this.version})`, 2);
|
|
84
126
|
if (this.io) this.io.close();
|
|
85
|
-
this
|
|
86
|
-
this
|
|
87
|
-
this.#safeListStream = !!safeListStream;
|
|
88
|
-
|
|
89
|
-
watch = watch.map((s) => s.toLowerCase());
|
|
90
|
-
ignore = ignore.map((s) => s.toLowerCase());
|
|
127
|
+
this.#check(() => dbUri);
|
|
128
|
+
this.#check(() => server);
|
|
91
129
|
|
|
92
|
-
this.io
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
const token =
|
|
96
|
-
socket.handshake.auth.token ||
|
|
97
|
-
socket.handshake.headers.authorization;
|
|
98
|
-
if (!token) return next(new Error("NO_TOKEN_PROVIDED"));
|
|
99
|
-
|
|
100
|
-
const authorized = await authentify(token, socket);
|
|
101
|
-
if (authorized === true) return next(); // exactly returns true
|
|
102
|
-
|
|
103
|
-
return next(new Error("UNAUTHORIZED"));
|
|
104
|
-
} catch (error) {
|
|
105
|
-
return next(new Error("AUTH_ERROR"));
|
|
106
|
-
}
|
|
107
|
-
} else {
|
|
108
|
-
return next();
|
|
109
|
-
}
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
for (let middleware of middlewares) {
|
|
113
|
-
this.io.use(middleware);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
this.io.on("connection", (socket) => {
|
|
117
|
-
if (onSocket) onSocket(socket);
|
|
118
|
-
|
|
119
|
-
socket.on("db:stream[register]", async (streamId, registerId) => {
|
|
120
|
-
const stream = this.#streams[streamId];
|
|
121
|
-
if (!stream) return;
|
|
122
|
-
const coll = stream.collection;
|
|
123
|
-
|
|
124
|
-
if (!this.#cache[coll]) {
|
|
125
|
-
this.#cache[coll] = await connection.db
|
|
126
|
-
.collection(coll)
|
|
127
|
-
.find({})
|
|
128
|
-
.toArray();
|
|
129
|
-
}
|
|
130
|
-
const filterResults = await Promise.allSettled(
|
|
131
|
-
this.#cache[coll].map((doc) => stream.filter(doc))
|
|
132
|
-
);
|
|
133
|
-
|
|
134
|
-
const filtered = this.#cache[coll].filter(
|
|
135
|
-
(_, i) => filterResults[i] && filterResults[i].value
|
|
136
|
-
);
|
|
137
|
-
this.io.emit(`db:stream[register][${registerId}]`, filtered);
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
socket.on("disconnect", (r) => {
|
|
141
|
-
if (offSocket) offSocket(socket, r);
|
|
142
|
-
});
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
connection.once("open", async () => {
|
|
146
|
-
this.collections = (await connection.listCollections()).map(
|
|
130
|
+
this.io = new Server(server);
|
|
131
|
+
this.connection.once("open", async () => {
|
|
132
|
+
this.collections = (await this.connection.listCollections()).map(
|
|
147
133
|
(c) => c.name
|
|
148
134
|
);
|
|
149
135
|
|
|
@@ -165,7 +151,7 @@ class MongoRealtime {
|
|
|
165
151
|
];
|
|
166
152
|
}
|
|
167
153
|
|
|
168
|
-
const changeStream = connection.watch(pipeline, {
|
|
154
|
+
const changeStream = this.connection.watch(pipeline, {
|
|
169
155
|
fullDocument: "updateLookup",
|
|
170
156
|
fullDocumentBeforeChange: "whenAvailable",
|
|
171
157
|
});
|
|
@@ -184,14 +170,15 @@ class MongoRealtime {
|
|
|
184
170
|
const coll = change.ns.coll;
|
|
185
171
|
|
|
186
172
|
if (!this.#cache[coll]) {
|
|
187
|
-
this.#cache[coll] = await connection.db
|
|
173
|
+
this.#cache[coll] = await this.connection.db
|
|
188
174
|
.collection(coll)
|
|
189
175
|
.find({})
|
|
176
|
+
.sort({ _id: -1 })
|
|
190
177
|
.toArray();
|
|
191
178
|
} else
|
|
192
179
|
switch (change.operationType) {
|
|
193
180
|
case "insert":
|
|
194
|
-
this.#cache[coll].
|
|
181
|
+
this.#cache[coll].unshift(change.fullDocument); // add to top;
|
|
195
182
|
break;
|
|
196
183
|
|
|
197
184
|
case "update":
|
|
@@ -260,6 +247,81 @@ class MongoRealtime {
|
|
|
260
247
|
}
|
|
261
248
|
});
|
|
262
249
|
});
|
|
250
|
+
|
|
251
|
+
try {
|
|
252
|
+
await mongoose.connect(dbUri, dbOptions);
|
|
253
|
+
this.#log(`Connected to db '${mongoose.connection.name}'`, 1);
|
|
254
|
+
onDbConnect?.call(this, mongoose.connection);
|
|
255
|
+
} catch (error) {
|
|
256
|
+
onDbError?.call(this, error);
|
|
257
|
+
this.#log("Failed to init",4);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
this.#check(() => mongoose.connection.db, "No database found");
|
|
262
|
+
|
|
263
|
+
this.#safeListStream = !!safeListStream;
|
|
264
|
+
|
|
265
|
+
watch = watch.map((s) => s.toLowerCase());
|
|
266
|
+
ignore = ignore.map((s) => s.toLowerCase());
|
|
267
|
+
|
|
268
|
+
this.io.use(async (socket, next) => {
|
|
269
|
+
if (!!authentify) {
|
|
270
|
+
try {
|
|
271
|
+
const token =
|
|
272
|
+
socket.handshake.auth.token ||
|
|
273
|
+
socket.handshake.headers.authorization;
|
|
274
|
+
if (!token) return next(new Error("NO_TOKEN_PROVIDED"));
|
|
275
|
+
|
|
276
|
+
const authorized = await authentify(token, socket);
|
|
277
|
+
if (authorized === true) return next(); // exactly returns true
|
|
278
|
+
|
|
279
|
+
return next(new Error("UNAUTHORIZED"));
|
|
280
|
+
} catch (error) {
|
|
281
|
+
return next(new Error("AUTH_ERROR"));
|
|
282
|
+
}
|
|
283
|
+
} else {
|
|
284
|
+
return next();
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
for (let middleware of middlewares) {
|
|
289
|
+
this.io.use(middleware);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
this.io.on("connection", (socket) => {
|
|
293
|
+
socket.emit("version", version);
|
|
294
|
+
socket.on("db:stream[register]", async (streamId, registerId) => {
|
|
295
|
+
const stream = this.#streams[streamId];
|
|
296
|
+
if (!stream) return;
|
|
297
|
+
const coll = stream.collection;
|
|
298
|
+
if (!this.#cache[coll]) {
|
|
299
|
+
this.#cache[coll] = await this.connection.db
|
|
300
|
+
.collection(coll)
|
|
301
|
+
.find({})
|
|
302
|
+
.sort({ _id: -1 })
|
|
303
|
+
.toArray();
|
|
304
|
+
}
|
|
305
|
+
const filterResults = await Promise.allSettled(
|
|
306
|
+
this.#cache[coll].map((doc) => stream.filter(doc))
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
const filtered = this.#cache[coll].filter(
|
|
310
|
+
(_, i) => filterResults[i] && filterResults[i].value
|
|
311
|
+
);
|
|
312
|
+
this.io.emit(`db:stream[register][${registerId}]`, filtered);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
socket.on("disconnect", (r) => {
|
|
316
|
+
if (offSocket) offSocket(socket, r);
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
if (onSocket) onSocket(socket);
|
|
320
|
+
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
this.#log(`Initialized`,1);
|
|
263
325
|
}
|
|
264
326
|
|
|
265
327
|
/**
|
package/package.json
CHANGED
|
@@ -1,26 +1,27 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "mongo-realtime",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"main": "index.js",
|
|
5
|
-
"scripts": {},
|
|
6
|
-
"keywords": [
|
|
7
|
-
"mongo",
|
|
8
|
-
"mongoose",
|
|
9
|
-
"replica",
|
|
10
|
-
"stream",
|
|
11
|
-
"realtime",
|
|
12
|
-
"mongodb",
|
|
13
|
-
"socket",
|
|
14
|
-
"db"
|
|
15
|
-
],
|
|
16
|
-
"author": "D3R50N",
|
|
17
|
-
"license": "MIT",
|
|
18
|
-
"repository": {
|
|
19
|
-
"url": "git+https://github.com/D3R50N/mongo-realtime.git"
|
|
20
|
-
},
|
|
21
|
-
"description": "A Node.js package that combines Socket.IO and MongoDB Change Streams to deliver real-time database updates to your WebSocket clients.",
|
|
22
|
-
"dependencies": {
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
|
|
26
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "mongo-realtime",
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"main": "index.js",
|
|
5
|
+
"scripts": {},
|
|
6
|
+
"keywords": [
|
|
7
|
+
"mongo",
|
|
8
|
+
"mongoose",
|
|
9
|
+
"replica",
|
|
10
|
+
"stream",
|
|
11
|
+
"realtime",
|
|
12
|
+
"mongodb",
|
|
13
|
+
"socket",
|
|
14
|
+
"db"
|
|
15
|
+
],
|
|
16
|
+
"author": "D3R50N",
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"repository": {
|
|
19
|
+
"url": "git+https://github.com/D3R50N/mongo-realtime.git"
|
|
20
|
+
},
|
|
21
|
+
"description": "A Node.js package that combines Socket.IO and MongoDB Change Streams to deliver real-time database updates to your WebSocket clients.",
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"chalk": "^4.1.2",
|
|
24
|
+
"mongoose": "^8.17.0",
|
|
25
|
+
"socket.io": "^4.8.1"
|
|
26
|
+
}
|
|
27
|
+
}
|