mongo-realtime 2.0.0 → 2.0.2

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 (2) hide show
  1. package/index.js +126 -18
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -35,7 +35,7 @@ class MongoRealtime {
35
35
  mongoose.connection;
36
36
  /** @type {Record<String, [(change:ChangeStreamDocument)=>void]>} */ static #listeners =
37
37
  {};
38
-
38
+
39
39
  static sockets = () => [...this.io.sockets.sockets.values()];
40
40
 
41
41
  /**@type {Record<String, {collection:String,filter: (doc:Object)=>Promise<boolean>}>} */
@@ -114,6 +114,8 @@ class MongoRealtime {
114
114
  * @param {[String]} options.watch - Collections to watch. If empty, will watch all collections
115
115
  * @param {[String]} options.ignore - Collections to ignore. Can override `watch`
116
116
  * @param {bool} options.debug - Enable debug mode
117
+ * @param {number} options.cacheDelay - Cache delay in minutes. Put 0 if no cache
118
+ * @param {number} options.allowDbOperations - If true, you can use find and update operations.
117
119
  *
118
120
  */
119
121
  static async init({
@@ -130,6 +132,8 @@ class MongoRealtime {
130
132
  middlewares = [],
131
133
  watch = [],
132
134
  ignore = [],
135
+ cacheDelay = 5,
136
+ allowDbOperations = true,
133
137
  }) {
134
138
  this.#log(`MongoRealtime version (${this.version})`, 2);
135
139
 
@@ -188,14 +192,15 @@ class MongoRealtime {
188
192
  changeStream.on("change", async (change) => {
189
193
  const coll = change.ns.coll;
190
194
  const colName = coll.toLowerCase();
191
- const doc = change.fullDocument;
195
+ const id = change.documentKey?._id.toString();
196
+ const doc = change.fullDocument??{_id:id};
197
+
192
198
 
193
199
  this.#debugLog(`Collection '${colName}' changed`);
194
200
 
195
201
  change.col = colName;
196
202
 
197
203
  const type = change.operationType;
198
- const id = change.documentKey?._id.toString();
199
204
 
200
205
  for (const k in this.#streams) {
201
206
  const stream = this.#streams[k];
@@ -203,24 +208,23 @@ class MongoRealtime {
203
208
 
204
209
  Promise.resolve(stream.filter(doc)).then((ok) => {
205
210
  if (ok) {
206
- this.io.emit(`realtime:${k}`, {
207
- results: [doc],
208
- coll: coll,
209
- count: 1,
210
- total: 1,
211
- remaining: 0,
212
- });
211
+ const data = { added: [], removed: [] };
212
+ if (change.operationType == "delete") data.removed.push(doc);
213
+ else data.added.push(doc);
214
+
215
+
216
+ this.io.emit(`realtime:${k}`, data);
213
217
  }
214
218
  });
215
219
  }
216
220
  for (let k in this.#data) {
217
221
  if (!k.startsWith(`${coll}-`) || !this.#data[k].result[id]) continue;
218
- doc._id = doc._id.toString();
219
222
  switch (change.operationType) {
220
223
  case "delete":
221
224
  delete this.#data[k].result[id];
222
225
  break;
223
- default:
226
+ default:
227
+ doc._id = id;
224
228
  this.#data[k].result[id] = doc;
225
229
  }
226
230
  }
@@ -322,6 +326,7 @@ class MongoRealtime {
322
326
  .collection(coll)
323
327
  .estimatedDocumentCount();
324
328
 
329
+
325
330
  const length = ids.length;
326
331
  const range = [length, Math.min(total, length + limit)];
327
332
  const now = new Date();
@@ -350,7 +355,7 @@ class MongoRealtime {
350
355
 
351
356
  ids.push(...result.map((d) => d._id));
352
357
 
353
- const delayInMin = 1;
358
+ const delayInMin = cacheDelay;
354
359
  const expiration = new Date(now.getTime() + delayInMin * 60 * 1000);
355
360
  const resultMap = result.reduce((acc, item) => {
356
361
  item._id = item._id.toString();
@@ -386,17 +391,105 @@ class MongoRealtime {
386
391
  .map((item) => item.doc);
387
392
 
388
393
  const data = {
389
- results: filtered,
390
- coll,
391
- count: filtered.length,
392
- total,
393
- remaining: total - ids.length,
394
+ added: filtered,
395
+ removed: [],
394
396
  };
395
397
 
396
398
  socket.emit(`realtime:${streamId}:${registerId}`, data);
397
399
  } while (ids.length < total);
398
400
  }
399
401
  );
402
+ if (allowDbOperations) {
403
+ socket.on("realtime:count", async ({ coll, query }, ack) => {
404
+ if (!coll) return ack(0);
405
+ query ??= {};
406
+ const c = this.connection.db.collection(coll);
407
+ const hasQuery = notEmpty(query);
408
+ const count = hasQuery
409
+ ? await c.countDocuments(query)
410
+ : await c.estimatedDocumentCount();
411
+ ack(count);
412
+ });
413
+
414
+ socket.on(
415
+ "realtime:find",
416
+ async (
417
+ { coll, query, limit, sortBy, project, one, skip, id },
418
+ ack
419
+ ) => {
420
+ if (!coll) return ack(null);
421
+ const c = this.connection.db.collection(coll);
422
+
423
+ if (id) {
424
+ ack(await c.findOne({ _id: toObjectId(id) }));
425
+ return;
426
+ }
427
+
428
+ query ??= {};
429
+ one = one == true;
430
+
431
+ if (query["_id"]) {
432
+ query["_id"] = toObjectId(query["_id"]);
433
+ }
434
+
435
+ const options = {
436
+ sort: sortBy,
437
+ projection: project,
438
+ skip: skip,
439
+ limit: limit,
440
+ };
441
+
442
+ if (one) {
443
+ ack(await c.findOne(query, options));
444
+ return;
445
+ }
446
+
447
+ let cursor = c.find(query, options);
448
+ ack(await cursor.toArray());
449
+ }
450
+ );
451
+
452
+ socket.on(
453
+ "realtime:update",
454
+ async (
455
+ { coll, query, limit, sortBy, project, one, skip, id, update },
456
+ ack
457
+ ) => {
458
+ if (!coll || !notEmpty(update)) return ack(0);
459
+ const c = this.connection.db.collection(coll);
460
+
461
+ if (id) {
462
+ ack(
463
+ (await c.updateOne({ _id: toObjectId(id) }, update))
464
+ .modifiedCount
465
+ );
466
+ return;
467
+ }
468
+
469
+ query ??= {};
470
+ one = one == true;
471
+
472
+ if (query["_id"]) {
473
+ query["_id"] = toObjectId(query["_id"]);
474
+ }
475
+
476
+ const options = {
477
+ sort: sortBy,
478
+ projection: project,
479
+ skip: skip,
480
+ limit: limit,
481
+ };
482
+
483
+ if (one) {
484
+ ack((await c.updateOne(query, update, options)).modifiedCount);
485
+ return;
486
+ }
487
+
488
+ let cursor = await c.updateMany(query, update, options);
489
+ ack(cursor.modifiedCount);
490
+ }
491
+ );
492
+ }
400
493
 
401
494
  socket.on("disconnect", (r) => {
402
495
  if (offSocket) offSocket(socket, r);
@@ -483,4 +576,19 @@ class MongoRealtime {
483
576
  }
484
577
  }
485
578
 
579
+ // utils
580
+ function notEmpty(obj) {
581
+ obj ??= {};
582
+ return Object.keys(obj).length > 0;
583
+ }
584
+ /** @param {String} id */
585
+ function toObjectId(id) {
586
+ if (typeof id != "string") return id;
587
+ try {
588
+ return mongoose.Types.ObjectId.createFromHexString(id);
589
+ } catch (_) {
590
+ return new mongoose.Types.ObjectId(id); //use deprecated if fail
591
+ }
592
+ }
593
+
486
594
  module.exports = MongoRealtime;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mongo-realtime",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "main": "index.js",
5
5
  "scripts": {},
6
6
  "keywords": [