mongo-realtime 1.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.
Files changed (3) hide show
  1. package/README.md +239 -0
  2. package/index.js +102 -0
  3. package/package.json +26 -0
package/README.md ADDED
@@ -0,0 +1,239 @@
1
+ # Mongo Realtime
2
+
3
+ A Node.js package that combines Socket.IO and MongoDB Change Streams to deliver real-time database updates to your WebSocket clients.
4
+
5
+ ## 🚀 Features
6
+
7
+ - **Real-time updates**: Automatically detects changes in MongoDB and broadcasts them via Socket.IO
8
+ - **Granular events**: Emits specific events by operation type, collection, and document
9
+ - **Connection management**: Customizable callbacks for socket connections/disconnections
10
+ - **TypeScript compatible**: JSDoc annotations for better development experience
11
+
12
+ ## 📦 Installation
13
+
14
+ ```bash
15
+ npm install mongo-realtime
16
+ ```
17
+
18
+ ## Setup
19
+
20
+ ### Prerequisites
21
+
22
+ - MongoDB running as a replica set (required for Change Streams)
23
+ - Node.js HTTP server (See below how to configure an HTTP server with Express)
24
+
25
+ ### Example setup
26
+
27
+ ```javascript
28
+ const express = require('express');
29
+ const http = require('http');
30
+ const mongoose = require('mongoose');
31
+ const MongoRealtime = require('mongo-realtime');
32
+
33
+ const app = express();
34
+ const server = http.createServer(app);
35
+
36
+ mongoose.connect('mongodb://localhost:27017/mydb').then((c) => {
37
+ console.log("Connected to db",c.connection.name);
38
+ });
39
+
40
+ MongoRealtime.init({
41
+ connection: mongoose.connection,
42
+ server: server,
43
+ ignore: ["posts"], // ignore 'posts' collection
44
+ onSocket: (socket) => {
45
+ console.log(`Client connected: ${socket.id}`);
46
+ socket.emit('welcome', { message: 'Connection successful!' });
47
+ },
48
+ offSocket: (socket, reason) => {
49
+ console.log(`Client disconnected: ${socket.id}, reason: ${reason}`);
50
+ }
51
+ });
52
+
53
+ server.listen(3000, () => {
54
+ console.log('Server started on port 3000');
55
+ });
56
+ ```
57
+
58
+ ## 📋 API
59
+
60
+ ### `MongoRealtime.init(options)`
61
+
62
+ Initializes the socket system and MongoDB Change Streams.
63
+
64
+ #### Parameters
65
+
66
+ \* means required
67
+
68
+ | Parameter | Type | Description |
69
+ |-----------|------|-------------|
70
+ | `options.connection` | `mongoose.Connection`* | Active Mongoose connection |
71
+ | `options.server` | `http.Server`* | HTTP server to attach Socket.IO |
72
+ | `options.onSocket` | `Function` | Callback on socket connection |
73
+ | `options.offSocket` | `Function` | Callback on socket disconnection |
74
+ | `options.watch` | `Array[String]` | Collections to only watch. Listen to all when is empty |
75
+ | `options.ignore` | `Array[String]` | Collections to only ignore. Overrides watch array |
76
+
77
+ #### Static Properties
78
+
79
+ - `MongoRealtime.io`: Socket.IO server instance
80
+ - `MongoRealtime.connection`: MongoDB connection
81
+ - `MongoRealtime.sockets`: Array of connected sockets
82
+
83
+ ## 🎯 Emitted Events
84
+
85
+ The package automatically emits six types of events for each database change:
86
+
87
+ ### Event Types
88
+
89
+ | Event | Description | Example |
90
+ |-------|-------------|---------|
91
+ | `db:change` | All changes | Any collection change |
92
+ | `db:{type}` | By operation type | `db:insert`, `db:update`, `db:delete` |
93
+ | `db:change:{collection}` | By collection | `db:change:users`, `db:change:posts` |
94
+ | `db:{type}:{collection}` | Type + collection | `db:insert:users`, `db:update:posts` |
95
+ | `db:change:{collection}:{id}` | Specific document | `db:change:users:507f1f77bcf86cd799439011` |
96
+ | `db:{type}:{collection}:{id}` | Type + document | `db:insert:users:507f1f77bcf86cd799439011` |
97
+
98
+ ### Event Payload Structure
99
+
100
+ Each event contains the full MongoDB change object:
101
+
102
+ ```javascript
103
+ {
104
+ "_id": {...},
105
+ "operationType": "insert|update|delete|replace",
106
+ "documentKey": { "_id": "..." },
107
+ "ns": { "db": "mydb", "coll": "users" },
108
+ "fullDocument": {...},
109
+ "fullDocumentBeforeChange": {...}
110
+ }
111
+ ```
112
+
113
+ ## 🔨 Usage Examples
114
+
115
+ ### Server-side - Listening to specific events
116
+
117
+ ```javascript
118
+ MongoRealtime.init({
119
+ connection: connection,
120
+ server: server,
121
+ onSocket: (socket) => {
122
+ socket.on('subscribe:users', () => {
123
+ socket.join('users-room');
124
+ });
125
+ },
126
+
127
+ });
128
+
129
+ MongoRealtime.io.to('users-room').emit('custom-event', data);
130
+ ```
131
+
132
+ ### Client-side - Receiving updates
133
+
134
+ ```html
135
+ <!DOCTYPE html>
136
+ <html>
137
+ <head>
138
+ <script src="/socket.io/socket.io.js"></script>
139
+ </head>
140
+ <body>
141
+ <script>
142
+ const socket = io();
143
+
144
+ socket.on('db:change', (change) => {
145
+ console.log('Detected change:', change);
146
+ });
147
+
148
+ socket.on('db:insert:users', (change) => {
149
+ console.log('New user:', change.fullDocument);
150
+ });
151
+
152
+ const userId = '507f1f77bcf86cd799439011';
153
+ socket.on(`db:update:users:${userId}`, (change) => {
154
+ console.log('Updated user:', change.fullDocument);
155
+ });
156
+
157
+ socket.on('db:delete', (change) => {
158
+ console.log('Deleted document:', change.documentKey);
159
+ });
160
+ </script>
161
+ </body>
162
+ </html>
163
+ ```
164
+
165
+ ## Error Handling
166
+
167
+ ```javascript
168
+ MongoRealtime.init({
169
+ connection: mongoose.connection,
170
+ server: server,
171
+ onSocket: (socket) => {
172
+ socket.on('error', (error) => {
173
+ console.error('Socket error:', error);
174
+ });
175
+ },
176
+ offSocket: (socket, reason) => {
177
+ if (reason === 'transport error') {
178
+ console.log('Transport error detected');
179
+ }
180
+ }
181
+ });
182
+ ```
183
+
184
+ ## 🔒 Security
185
+
186
+ ### Socket Authentication
187
+
188
+ ```javascript
189
+ function authenticateSocket(socket){
190
+ socket.on('authenticate', (token) => {
191
+ if (isValidToken(token)) {
192
+ socket.authenticated = true;
193
+ socket.emit('authenticated');
194
+ } else {
195
+ socket.disconnect();
196
+ }
197
+ });
198
+
199
+ socket.use((packet, next) => {
200
+ if (socket.authenticated) {
201
+ next();
202
+ } else {
203
+ next(new Error('Unauthenticated'));
204
+ }
205
+ });
206
+ }
207
+
208
+ MongoRealtime.init({
209
+ connection: mongoose.connection,
210
+ server: server,
211
+ onSocket: authenticateSocket,
212
+ offSocket: (socket, reason) => {
213
+ console.log(`Socket ${socket.id} disconnected: ${reason}`);
214
+ }
215
+ });
216
+ ```
217
+
218
+ ## 📚 Dependencies
219
+
220
+ - `socket.io`: WebSocket management
221
+ - `mongoose`: MongoDB ODM with Change Streams support
222
+
223
+ ## 🐛 Troubleshooting
224
+
225
+ ### MongoDB must be in Replica Set mode
226
+
227
+ ```bash
228
+ mongod --replSet rs0
229
+
230
+ rs.initiate()
231
+ ```
232
+
233
+ ## 📄 License
234
+
235
+ MIT
236
+
237
+ ## 🤝 Contributing
238
+
239
+ Contributions are welcome! Feel free to open an issue or submit a pull request.****
package/index.js ADDED
@@ -0,0 +1,102 @@
1
+ const { Server } = require("socket.io");
2
+
3
+ class MongoRealtime {
4
+ /** @type {import("socket.io").Server} */ static io;
5
+ /** @type {import("mongoose").Connection} */ static connection;
6
+ /** @type {[import("socket.io").Socket]} */ static sockets = [];
7
+
8
+ /**
9
+ * Initializes the socket system.
10
+ *
11
+ * @param {Object} options
12
+ * @param {import("mongoose").Connection} options.connection - Active Mongoose connection
13
+ * @param {(socket: import("socket.io").Socket) => void} options.onSocket - Callback triggered when a socket connects
14
+ * @param {(socket: import("socket.io").Socket, reason: import("socket.io").DisconnectReason) => void} options.offSocket - Callback triggered when a socket disconnects
15
+ * @param {import("http").Server} options.server - HTTP server to attach Socket.IO to
16
+ * @param {[String]} options.watch - Collections to watch. If empty, will watch all collections
17
+ * @param {[String]} options.ignore - Collections to ignore. Can override `watch`
18
+ *
19
+ */
20
+ static init({
21
+ connection,
22
+ server,
23
+ onSocket,
24
+ offSocket,
25
+ watch = [],
26
+ ignore = [],
27
+ }) {
28
+ if (this.io)
29
+ this.io.close(() => {
30
+ this.sockets = [];
31
+ });
32
+ this.io = new Server(server);
33
+ this.connection = connection;
34
+
35
+ watch = watch.map((s) => s.toLowerCase());
36
+ ignore = ignore.map((s) => s.toLowerCase());
37
+
38
+ this.io.on("connection", (socket) => {
39
+ this.sockets = [...this.io.sockets.sockets.values()];
40
+ if (onSocket) onSocket(socket);
41
+
42
+ socket.on("disconnect", (r) => {
43
+ this.sockets = [...this.io.sockets.sockets.values()];
44
+ if (offSocket) offSocket(socket, r);
45
+ });
46
+ });
47
+
48
+ connection.once("open", () => {
49
+ let pipeline = [];
50
+ if (watch.length !== 0 && ignore.length === 0) {
51
+ pipeline = [{ $match: { "ns.coll": { $in: watch } } }];
52
+ } else if (watch.length === 0 && ignore.length !== 0) {
53
+ pipeline = [{ $match: { "ns.coll": { $nin: ignore } } }];
54
+ } else if (watch.length !== 0 && ignore.length !== 0) {
55
+ pipeline = [
56
+ {
57
+ $match: {
58
+ $and: [
59
+ { "ns.coll": { $in: watch } },
60
+ { "ns.coll": { $nin: ignore } },
61
+ ],
62
+ },
63
+ },
64
+ ];
65
+ }
66
+
67
+ const changeStream = connection.watch(pipeline, {
68
+ fullDocument: "updateLookup",
69
+ fullDocumentBeforeChange: "whenAvailable",
70
+ });
71
+
72
+ changeStream.on("change", (change) => {
73
+ const colName = change.ns.coll.toLowerCase();
74
+ const type = change.operationType;
75
+ const id = change.documentKey?._id;
76
+
77
+ const e_change = "db:change";
78
+ const e_change_type = `db:${type}`;
79
+ const e_change_col = `${e_change}:${colName}`;
80
+ const e_change_type_col = `${e_change_type}:${colName}`;
81
+
82
+ const events = [
83
+ e_change,
84
+ e_change_type,
85
+ e_change_col,
86
+ e_change_type_col,
87
+ ];
88
+
89
+ if (id) {
90
+ const e_change_doc = `${e_change_col}:${id}`;
91
+ const e_change_type_doc = `${e_change_type_col}:${id}`;
92
+ events.push(e_change_doc, e_change_type_doc);
93
+ }
94
+ for (let e of events) {
95
+ this.io.emit(e, change);
96
+ }
97
+ });
98
+ });
99
+ }
100
+ }
101
+
102
+ module.exports = MongoRealtime;
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "mongo-realtime",
3
+ "version": "1.0.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": "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
+ "mongoose": "^8.17.0",
24
+ "socket.io": "^4.8.1"
25
+ }
26
+ }