mongo-realtime 1.1.2 → 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 +143 -74
- 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,74 +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());
|
|
91
|
-
|
|
92
|
-
this.io.use(async (socket, next) => {
|
|
93
|
-
if (!!authentify) {
|
|
94
|
-
try {
|
|
95
|
-
const token =
|
|
96
|
-
socket.handshake.auth.token ||
|
|
97
|
-
socket.handshake.headers.authorization;
|
|
98
|
-
if (!token) return next(new Error("NO_TOKEN_PROVIDED"));
|
|
127
|
+
this.#check(() => dbUri);
|
|
128
|
+
this.#check(() => server);
|
|
99
129
|
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
this.io.emit(`db:stream[register][${registerId}]`, this.#cache[coll]);
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
socket.on("disconnect", (r) => {
|
|
134
|
-
if (offSocket) offSocket(socket, r);
|
|
135
|
-
});
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
connection.once("open", async () => {
|
|
139
|
-
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(
|
|
140
133
|
(c) => c.name
|
|
141
134
|
);
|
|
142
135
|
|
|
@@ -158,7 +151,7 @@ class MongoRealtime {
|
|
|
158
151
|
];
|
|
159
152
|
}
|
|
160
153
|
|
|
161
|
-
const changeStream = connection.watch(pipeline, {
|
|
154
|
+
const changeStream = this.connection.watch(pipeline, {
|
|
162
155
|
fullDocument: "updateLookup",
|
|
163
156
|
fullDocumentBeforeChange: "whenAvailable",
|
|
164
157
|
});
|
|
@@ -177,14 +170,15 @@ class MongoRealtime {
|
|
|
177
170
|
const coll = change.ns.coll;
|
|
178
171
|
|
|
179
172
|
if (!this.#cache[coll]) {
|
|
180
|
-
this.#cache[coll] = await connection.db
|
|
173
|
+
this.#cache[coll] = await this.connection.db
|
|
181
174
|
.collection(coll)
|
|
182
175
|
.find({})
|
|
176
|
+
.sort({ _id: -1 })
|
|
183
177
|
.toArray();
|
|
184
178
|
} else
|
|
185
179
|
switch (change.operationType) {
|
|
186
180
|
case "insert":
|
|
187
|
-
this.#cache[coll].
|
|
181
|
+
this.#cache[coll].unshift(change.fullDocument); // add to top;
|
|
188
182
|
break;
|
|
189
183
|
|
|
190
184
|
case "update":
|
|
@@ -253,6 +247,81 @@ class MongoRealtime {
|
|
|
253
247
|
}
|
|
254
248
|
});
|
|
255
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);
|
|
256
325
|
}
|
|
257
326
|
|
|
258
327
|
/**
|
|
@@ -302,7 +371,7 @@ class MongoRealtime {
|
|
|
302
371
|
collection,
|
|
303
372
|
filter,
|
|
304
373
|
};
|
|
305
|
-
}
|
|
374
|
+
}
|
|
306
375
|
|
|
307
376
|
/**
|
|
308
377
|
* @param {String} streamId - StreamId of the stream
|
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
|
+
}
|