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.
- package/index.js +126 -18
- 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
|
|
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
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
390
|
-
|
|
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;
|