mongo-realtime 2.0.3 → 3.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.
- package/.env.example +5 -0
- package/README.md +362 -438
- package/package.json +18 -6
- package/src/env.js +87 -0
- package/src/index.js +15 -0
- package/src/query.js +308 -0
- package/src/server.js +928 -0
- package/index.js +0 -595
- package/logo.png +0 -0
package/index.js
DELETED
|
@@ -1,595 +0,0 @@
|
|
|
1
|
-
const mongoose = require("mongoose");
|
|
2
|
-
const { Server } = require("socket.io");
|
|
3
|
-
const { version } = require("./package.json");
|
|
4
|
-
const chalk = require("chalk");
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* @typedef {Object} ChangeStreamDocument
|
|
8
|
-
* @property {"insert"|"update"|"replace"|"delete"|"invalidate"|"drop"|"dropDatabase"|"rename"} operationType
|
|
9
|
-
* The type of operation that triggered the event.
|
|
10
|
-
*
|
|
11
|
-
* @property {Object} ns
|
|
12
|
-
* @property {string} ns.db - Database name
|
|
13
|
-
* @property {string} ns.coll - Collection name
|
|
14
|
-
*
|
|
15
|
-
* @property {Object} documentKey
|
|
16
|
-
* @property {import("bson").ObjectId|string} documentKey._id - The document’s identifier
|
|
17
|
-
*
|
|
18
|
-
* @property {Object} [fullDocument]
|
|
19
|
-
* The full document after the change (only present if `fullDocument: "updateLookup"` is enabled).
|
|
20
|
-
*
|
|
21
|
-
* @property {Object} [updateDescription]
|
|
22
|
-
* @property {Object.<string, any>} [updateDescription.updatedFields]
|
|
23
|
-
* Fields that were updated during an update operation.
|
|
24
|
-
* @property {string[]} [updateDescription.removedFields]
|
|
25
|
-
* Fields that were removed during an update operation.
|
|
26
|
-
*
|
|
27
|
-
* @property {Object} [rename] - Info about the collection rename (if operationType is "rename").
|
|
28
|
-
*
|
|
29
|
-
* @property {Date} [clusterTime] - Logical timestamp of the event.
|
|
30
|
-
*/
|
|
31
|
-
|
|
32
|
-
class MongoRealtime {
|
|
33
|
-
/** @type {import("socket.io").Server} */ static io;
|
|
34
|
-
/** @type {import("mongoose").Connection} */ static connection =
|
|
35
|
-
mongoose.connection;
|
|
36
|
-
/** @type {Record<String, [(change:ChangeStreamDocument)=>void]>} */ static #listeners =
|
|
37
|
-
{};
|
|
38
|
-
|
|
39
|
-
static sockets = () => [...this.io.sockets.sockets.values()];
|
|
40
|
-
|
|
41
|
-
/**@type {Record<String, {collection:String,filter: (doc:Object)=>Promise<boolean>}>} */
|
|
42
|
-
static #streams = {};
|
|
43
|
-
|
|
44
|
-
/**@type {Record<String, {expiration:Date,result:Record<String,{}> }>} */
|
|
45
|
-
static #data = {};
|
|
46
|
-
|
|
47
|
-
/** @type {[String]} - All DB collections */
|
|
48
|
-
static collections = [];
|
|
49
|
-
|
|
50
|
-
static #debug = false;
|
|
51
|
-
|
|
52
|
-
static version = version;
|
|
53
|
-
|
|
54
|
-
static #check(fn, err) {
|
|
55
|
-
const result = fn();
|
|
56
|
-
if (!result) {
|
|
57
|
-
let src = fn.toString().trim();
|
|
58
|
-
|
|
59
|
-
let match =
|
|
60
|
-
src.match(/=>\s*([^{};]+)$/) ||
|
|
61
|
-
src.match(/return\s+([^;}]*)/) ||
|
|
62
|
-
src.match(/{([^}]*)}/);
|
|
63
|
-
|
|
64
|
-
const expr = err ?? (match ? match[1].trim() : src);
|
|
65
|
-
|
|
66
|
-
throw new Error(`MongoRealtime expects "${expr}"`);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
static #log(message, type = 0) {
|
|
71
|
-
const text = `[REALTIME] ${message}`;
|
|
72
|
-
switch (type) {
|
|
73
|
-
case 1:
|
|
74
|
-
console.log(chalk.bold.hex("#11AA60FF")(text));
|
|
75
|
-
break;
|
|
76
|
-
case 2:
|
|
77
|
-
console.log(chalk.bold.bgHex("#257993")(text));
|
|
78
|
-
break;
|
|
79
|
-
case 3:
|
|
80
|
-
console.log(chalk.bold.yellow(text));
|
|
81
|
-
break;
|
|
82
|
-
case 4:
|
|
83
|
-
console.log(chalk.bold.red(text));
|
|
84
|
-
break;
|
|
85
|
-
|
|
86
|
-
case 5:
|
|
87
|
-
console.log(chalk.italic(text));
|
|
88
|
-
break;
|
|
89
|
-
|
|
90
|
-
default:
|
|
91
|
-
console.log(text);
|
|
92
|
-
break;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
static #debugLog(message) {
|
|
97
|
-
if (this.#debug) this.#log("[DEBUG] " + message, 5);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Initializes the socket system.
|
|
102
|
-
*
|
|
103
|
-
* @param {Object} options
|
|
104
|
-
* @param {String} options.dbUri - Database URI
|
|
105
|
-
* @param {mongoose.ConnectOptions | undefined} options.dbOptions - Database connect options
|
|
106
|
-
* @param {(token:String, socket: import("socket.io").Socket) => boolean | Promise<boolean>} options.authentify - Auth function that should return true if `token` is valid
|
|
107
|
-
* @param {[( socket: import("socket.io").Socket, next: (err?: ExtendedError) => void) => void]} options.middlewares - Register mmiddlewares on incoming socket
|
|
108
|
-
* @param {(conn:mongoose.Connection) => void} options.onDbConnect - Callback triggered when a socket connects
|
|
109
|
-
* @param {(err:Error) => void} options.onDbError - Callback triggered when a socket connects
|
|
110
|
-
* @param {(socket: import("socket.io").Socket) => void} options.onSocket - Callback triggered when a socket connects
|
|
111
|
-
* @param {(socket: import("socket.io").Socket, reason: import("socket.io").DisconnectReason) => void} options.offSocket - Callback triggered when a socket disconnects
|
|
112
|
-
* @param {import("http").Server} options.server - HTTP server to attach Socket.IO to
|
|
113
|
-
* @param {[String]} options.autoStream - Collections to stream automatically. If empty, will stream no collection. If null, will stream all collections.
|
|
114
|
-
* @param {[String]} options.watch - Collections to watch. If empty, will watch all collections
|
|
115
|
-
* @param {[String]} options.ignore - Collections to ignore. Can override `watch`
|
|
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.
|
|
119
|
-
* @param {mongoose} options.mongooseInstance - Running mongoose instance
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*/
|
|
123
|
-
static async init({
|
|
124
|
-
dbUri,
|
|
125
|
-
dbOptions,
|
|
126
|
-
server,
|
|
127
|
-
mongooseInstance,
|
|
128
|
-
onDbConnect,
|
|
129
|
-
onDbError,
|
|
130
|
-
authentify,
|
|
131
|
-
onSocket,
|
|
132
|
-
offSocket,
|
|
133
|
-
debug = false,
|
|
134
|
-
autoStream,
|
|
135
|
-
middlewares = [],
|
|
136
|
-
watch = [],
|
|
137
|
-
ignore = [],
|
|
138
|
-
cacheDelay = 5,
|
|
139
|
-
allowDbOperations = true,
|
|
140
|
-
}) {
|
|
141
|
-
this.#log(`MongoRealtime version (${this.version})`, 2);
|
|
142
|
-
|
|
143
|
-
if (this.io) this.io.close();
|
|
144
|
-
this.#check(() => dbUri);
|
|
145
|
-
this.#check(() => server);
|
|
146
|
-
this.#debug = debug;
|
|
147
|
-
|
|
148
|
-
this.io = new Server(server);
|
|
149
|
-
this.connection.once("open", async () => {
|
|
150
|
-
this.collections = (await this.connection.listCollections()).map(
|
|
151
|
-
(c) => c.name
|
|
152
|
-
);
|
|
153
|
-
this.#debugLog(
|
|
154
|
-
`${this.collections.length} collections found : ${this.collections.join(
|
|
155
|
-
", "
|
|
156
|
-
)}`
|
|
157
|
-
);
|
|
158
|
-
|
|
159
|
-
let pipeline = [];
|
|
160
|
-
if (watch.length !== 0 && ignore.length === 0) {
|
|
161
|
-
pipeline = [{ $match: { "ns.coll": { $in: watch } } }];
|
|
162
|
-
} else if (watch.length === 0 && ignore.length !== 0) {
|
|
163
|
-
pipeline = [{ $match: { "ns.coll": { $nin: ignore } } }];
|
|
164
|
-
} else if (watch.length !== 0 && ignore.length !== 0) {
|
|
165
|
-
pipeline = [
|
|
166
|
-
{
|
|
167
|
-
$match: {
|
|
168
|
-
$and: [
|
|
169
|
-
{ "ns.coll": { $in: watch } },
|
|
170
|
-
{ "ns.coll": { $nin: ignore } },
|
|
171
|
-
],
|
|
172
|
-
},
|
|
173
|
-
},
|
|
174
|
-
];
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const changeStream = this.connection.watch(pipeline, {
|
|
178
|
-
fullDocument: "updateLookup",
|
|
179
|
-
fullDocumentBeforeChange: "whenAvailable",
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
/** Setup main streams */
|
|
183
|
-
let collectionsToStream = [];
|
|
184
|
-
if (autoStream == null) collectionsToStream = this.collections;
|
|
185
|
-
else
|
|
186
|
-
collectionsToStream = this.collections.filter((c) =>
|
|
187
|
-
autoStream.includes(c)
|
|
188
|
-
);
|
|
189
|
-
for (let col of collectionsToStream) this.addStream(col, col);
|
|
190
|
-
this.#debugLog(
|
|
191
|
-
`Auto stream on collections : ${collectionsToStream.join(", ")}`
|
|
192
|
-
);
|
|
193
|
-
|
|
194
|
-
/** Emit listen events on change */
|
|
195
|
-
changeStream.on("change", async (change) => {
|
|
196
|
-
const coll = change.ns.coll;
|
|
197
|
-
const colName = coll.toLowerCase();
|
|
198
|
-
const id = change.documentKey?._id.toString();
|
|
199
|
-
const doc = change.fullDocument ?? { _id: id };
|
|
200
|
-
|
|
201
|
-
this.#debugLog(`Collection '${colName}' changed`);
|
|
202
|
-
|
|
203
|
-
change.col = colName;
|
|
204
|
-
|
|
205
|
-
const type = change.operationType;
|
|
206
|
-
|
|
207
|
-
const e_change = "db:change";
|
|
208
|
-
const e_change_type = `db:${type}`;
|
|
209
|
-
const e_change_col = `${e_change}:${colName}`;
|
|
210
|
-
const e_change_type_col = `${e_change_type}:${colName}`;
|
|
211
|
-
|
|
212
|
-
const events = [
|
|
213
|
-
e_change,
|
|
214
|
-
e_change_type,
|
|
215
|
-
e_change_col,
|
|
216
|
-
e_change_type_col,
|
|
217
|
-
];
|
|
218
|
-
|
|
219
|
-
if (id) {
|
|
220
|
-
change.docId = id;
|
|
221
|
-
const e_change_doc = `${e_change_col}:${id}`;
|
|
222
|
-
const e_change_type_doc = `${e_change_type_col}:${id}`;
|
|
223
|
-
events.push(e_change_doc, e_change_type_doc);
|
|
224
|
-
}
|
|
225
|
-
for (let e of events) {
|
|
226
|
-
this.io.emit(e, change);
|
|
227
|
-
this.notifyListeners(e, change);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
for (const k in this.#streams) {
|
|
231
|
-
const stream = this.#streams[k];
|
|
232
|
-
if (stream.collection != coll) continue;
|
|
233
|
-
|
|
234
|
-
Promise.resolve(stream.filter(doc)).then((ok) => {
|
|
235
|
-
if (ok) {
|
|
236
|
-
const data = { added: [], removed: [] };
|
|
237
|
-
if (change.operationType == "delete") data.removed.push(doc);
|
|
238
|
-
else data.added.push(doc);
|
|
239
|
-
|
|
240
|
-
this.io.emit(`realtime:${k}`, data);
|
|
241
|
-
}
|
|
242
|
-
});
|
|
243
|
-
}
|
|
244
|
-
for (let k in this.#data) {
|
|
245
|
-
if (!k.startsWith(`${coll}-`) || !this.#data[k].result[id]) continue;
|
|
246
|
-
switch (change.operationType) {
|
|
247
|
-
case "delete":
|
|
248
|
-
delete this.#data[k].result[id];
|
|
249
|
-
break;
|
|
250
|
-
default:
|
|
251
|
-
doc._id = id;
|
|
252
|
-
this.#data[k].result[id] = doc;
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
});
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
try {
|
|
259
|
-
await mongoose.connect(dbUri, dbOptions);
|
|
260
|
-
this.#log(`Connected to db '${mongoose.connection.name}'`, 1);
|
|
261
|
-
if (mongooseInstance) mongooseInstance.connection = mongoose.connection;
|
|
262
|
-
onDbConnect?.call(this, mongoose.connection);
|
|
263
|
-
} catch (error) {
|
|
264
|
-
onDbError?.call(this, error);
|
|
265
|
-
this.#log("Failed to init", 4);
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
this.#check(() => mongoose.connection.db, "No database found");
|
|
270
|
-
|
|
271
|
-
watch = watch.map((s) => s.toLowerCase());
|
|
272
|
-
ignore = ignore.map((s) => s.toLowerCase());
|
|
273
|
-
|
|
274
|
-
this.io.use(async (socket, next) => {
|
|
275
|
-
if (!!authentify) {
|
|
276
|
-
try {
|
|
277
|
-
const token =
|
|
278
|
-
socket.handshake.auth.token ||
|
|
279
|
-
socket.handshake.headers.authorization;
|
|
280
|
-
if (!token) return next(new Error("NO_TOKEN_PROVIDED"));
|
|
281
|
-
|
|
282
|
-
const authorized = await authentify(token, socket);
|
|
283
|
-
if (authorized === true) return next(); // exactly returns true
|
|
284
|
-
|
|
285
|
-
return next(new Error("UNAUTHORIZED"));
|
|
286
|
-
} catch (error) {
|
|
287
|
-
return next(new Error("AUTH_ERROR"));
|
|
288
|
-
}
|
|
289
|
-
} else {
|
|
290
|
-
return next();
|
|
291
|
-
}
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
for (let middleware of middlewares) {
|
|
295
|
-
this.io.use(middleware);
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
this.io.on("connection", (socket) => {
|
|
299
|
-
socket.emit("version", version);
|
|
300
|
-
|
|
301
|
-
socket.on(
|
|
302
|
-
"realtime",
|
|
303
|
-
async ({ streamId, limit, reverse, registerId }) => {
|
|
304
|
-
if (!streamId || !this.#streams[streamId]) return;
|
|
305
|
-
|
|
306
|
-
const stream = this.#streams[streamId];
|
|
307
|
-
const coll = stream.collection;
|
|
308
|
-
|
|
309
|
-
const default_limit = 100;
|
|
310
|
-
limit ??= default_limit;
|
|
311
|
-
try {
|
|
312
|
-
limit = parseInt(limit);
|
|
313
|
-
} catch (_) {
|
|
314
|
-
limit = default_limit;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
reverse = reverse == true;
|
|
318
|
-
registerId ??= "";
|
|
319
|
-
this.#debugLog(
|
|
320
|
-
`Socket '${socket.id}' registred for realtime '${coll}:${registerId}'. Limit ${limit}. Reversed ${reverse}`
|
|
321
|
-
);
|
|
322
|
-
|
|
323
|
-
let total;
|
|
324
|
-
const ids = [];
|
|
325
|
-
|
|
326
|
-
do {
|
|
327
|
-
total = await this.connection.db
|
|
328
|
-
.collection(coll)
|
|
329
|
-
.estimatedDocumentCount();
|
|
330
|
-
|
|
331
|
-
const length = ids.length;
|
|
332
|
-
const range = [length, Math.min(total, length + limit)];
|
|
333
|
-
const now = new Date();
|
|
334
|
-
|
|
335
|
-
let cachedKey = `${coll}-${range}`;
|
|
336
|
-
|
|
337
|
-
let cachedResult = this.#data[cachedKey];
|
|
338
|
-
if (cachedResult && cachedResult.expiration < now) {
|
|
339
|
-
delete this.#data[cachedKey];
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
cachedResult = this.#data[cachedKey];
|
|
343
|
-
|
|
344
|
-
const result = cachedResult
|
|
345
|
-
? Object.values(cachedResult.result)
|
|
346
|
-
: await this.connection.db
|
|
347
|
-
.collection(coll)
|
|
348
|
-
.find({
|
|
349
|
-
_id: {
|
|
350
|
-
$nin: ids,
|
|
351
|
-
},
|
|
352
|
-
})
|
|
353
|
-
.limit(limit)
|
|
354
|
-
.sort({ _id: reverse ? -1 : 1 })
|
|
355
|
-
.toArray();
|
|
356
|
-
|
|
357
|
-
ids.push(...result.map((d) => d._id));
|
|
358
|
-
|
|
359
|
-
const delayInMin = cacheDelay;
|
|
360
|
-
const expiration = new Date(now.getTime() + delayInMin * 60 * 1000);
|
|
361
|
-
const resultMap = result.reduce((acc, item) => {
|
|
362
|
-
item._id = item._id.toString();
|
|
363
|
-
acc[item._id] = item;
|
|
364
|
-
return acc;
|
|
365
|
-
}, {});
|
|
366
|
-
|
|
367
|
-
if (!cachedResult) {
|
|
368
|
-
this.#data[cachedKey] = {
|
|
369
|
-
expiration,
|
|
370
|
-
result: resultMap,
|
|
371
|
-
};
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
const filtered = (
|
|
375
|
-
await Promise.all(
|
|
376
|
-
result.map(async (doc) => {
|
|
377
|
-
try {
|
|
378
|
-
return {
|
|
379
|
-
doc,
|
|
380
|
-
ok: await stream.filter(doc),
|
|
381
|
-
};
|
|
382
|
-
} catch (e) {
|
|
383
|
-
return {
|
|
384
|
-
doc,
|
|
385
|
-
ok: false,
|
|
386
|
-
};
|
|
387
|
-
}
|
|
388
|
-
})
|
|
389
|
-
)
|
|
390
|
-
)
|
|
391
|
-
.filter((item) => item.ok)
|
|
392
|
-
.map((item) => item.doc);
|
|
393
|
-
|
|
394
|
-
const data = {
|
|
395
|
-
added: filtered,
|
|
396
|
-
removed: [],
|
|
397
|
-
};
|
|
398
|
-
|
|
399
|
-
socket.emit(`realtime:${streamId}:${registerId}`, data);
|
|
400
|
-
} while (ids.length < total);
|
|
401
|
-
}
|
|
402
|
-
);
|
|
403
|
-
if (allowDbOperations) {
|
|
404
|
-
socket.on("realtime:count", async ({ coll, query }, ack) => {
|
|
405
|
-
if (!coll) return ack(0);
|
|
406
|
-
query ??= {};
|
|
407
|
-
const c = this.connection.db.collection(coll);
|
|
408
|
-
const hasQuery = notEmpty(query);
|
|
409
|
-
const count = hasQuery
|
|
410
|
-
? await c.countDocuments(query)
|
|
411
|
-
: await c.estimatedDocumentCount();
|
|
412
|
-
ack(count);
|
|
413
|
-
});
|
|
414
|
-
|
|
415
|
-
socket.on(
|
|
416
|
-
"realtime:find",
|
|
417
|
-
async (
|
|
418
|
-
{ coll, query, limit, sortBy, project, one, skip, id },
|
|
419
|
-
ack
|
|
420
|
-
) => {
|
|
421
|
-
if (!coll) return ack(null);
|
|
422
|
-
const c = this.connection.db.collection(coll);
|
|
423
|
-
|
|
424
|
-
if (id) {
|
|
425
|
-
ack(await c.findOne({ _id: toObjectId(id) }));
|
|
426
|
-
return;
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
query ??= {};
|
|
430
|
-
one = one == true;
|
|
431
|
-
|
|
432
|
-
if (query["_id"]) {
|
|
433
|
-
query["_id"] = toObjectId(query["_id"]);
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
const options = {
|
|
437
|
-
sort: sortBy,
|
|
438
|
-
projection: project,
|
|
439
|
-
skip: skip,
|
|
440
|
-
limit: limit,
|
|
441
|
-
};
|
|
442
|
-
|
|
443
|
-
if (one) {
|
|
444
|
-
ack(await c.findOne(query, options));
|
|
445
|
-
return;
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
let cursor = c.find(query, options);
|
|
449
|
-
ack(await cursor.toArray());
|
|
450
|
-
}
|
|
451
|
-
);
|
|
452
|
-
|
|
453
|
-
socket.on(
|
|
454
|
-
"realtime:update",
|
|
455
|
-
async (
|
|
456
|
-
{ coll, query, limit, sortBy, project, one, skip, id, update },
|
|
457
|
-
ack
|
|
458
|
-
) => {
|
|
459
|
-
if (!coll || !notEmpty(update)) return ack(0);
|
|
460
|
-
const c = this.connection.db.collection(coll);
|
|
461
|
-
|
|
462
|
-
if (id) {
|
|
463
|
-
ack(
|
|
464
|
-
(await c.updateOne({ _id: toObjectId(id) }, update))
|
|
465
|
-
.modifiedCount
|
|
466
|
-
);
|
|
467
|
-
return;
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
query ??= {};
|
|
471
|
-
one = one == true;
|
|
472
|
-
|
|
473
|
-
if (query["_id"]) {
|
|
474
|
-
query["_id"] = toObjectId(query["_id"]);
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
const options = {
|
|
478
|
-
sort: sortBy,
|
|
479
|
-
projection: project,
|
|
480
|
-
skip: skip,
|
|
481
|
-
limit: limit,
|
|
482
|
-
};
|
|
483
|
-
|
|
484
|
-
if (one) {
|
|
485
|
-
ack((await c.updateOne(query, update, options)).modifiedCount);
|
|
486
|
-
return;
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
let cursor = await c.updateMany(query, update, options);
|
|
490
|
-
ack(cursor.modifiedCount);
|
|
491
|
-
}
|
|
492
|
-
);
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
socket.on("disconnect", (r) => {
|
|
496
|
-
if (offSocket) offSocket(socket, r);
|
|
497
|
-
});
|
|
498
|
-
|
|
499
|
-
if (onSocket) onSocket(socket);
|
|
500
|
-
});
|
|
501
|
-
|
|
502
|
-
this.#log(`Initialized`, 1);
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
/**
|
|
506
|
-
* Notify all event listeners
|
|
507
|
-
*
|
|
508
|
-
* @param {String} e - Name of the event
|
|
509
|
-
* @param {ChangeStreamDocument} change - Change Stream
|
|
510
|
-
*/
|
|
511
|
-
static notifyListeners(e, change) {
|
|
512
|
-
if (this.#listeners[e]) {
|
|
513
|
-
for (let c of this.#listeners[e]) {
|
|
514
|
-
c(change);
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
/**
|
|
520
|
-
* Subscribe to an event
|
|
521
|
-
*
|
|
522
|
-
* @param {String} key - Name of the event
|
|
523
|
-
* @param {(change:ChangeStreamDocument)=>void} cb - Callback
|
|
524
|
-
*/
|
|
525
|
-
static listen(key, cb) {
|
|
526
|
-
if (!this.#listeners[key]) this.#listeners[key] = [];
|
|
527
|
-
this.#listeners[key].push(cb);
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
/**
|
|
531
|
-
*
|
|
532
|
-
* @param {String} streamId - StreamId of the list stream
|
|
533
|
-
* @param {String} collection - Name of the collection to stream
|
|
534
|
-
* @param { (doc:Object )=>Promise<boolean>} filter - Collection filter
|
|
535
|
-
*
|
|
536
|
-
* Register a new list stream to listen
|
|
537
|
-
*/
|
|
538
|
-
static addStream(streamId, collection, filter) {
|
|
539
|
-
if (!streamId) throw new Error("Stream id is required");
|
|
540
|
-
if (!collection) throw new Error("Collection is required");
|
|
541
|
-
if (this.#streams[streamId] && this.collections.includes(streamId))
|
|
542
|
-
throw new Error(`streamId '${streamId}' cannot be a collection`);
|
|
543
|
-
|
|
544
|
-
filter ??= (_, __) => true;
|
|
545
|
-
|
|
546
|
-
this.#streams[streamId] = {
|
|
547
|
-
collection,
|
|
548
|
-
filter,
|
|
549
|
-
};
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
/**
|
|
553
|
-
* @param {String} streamId - StreamId of the stream
|
|
554
|
-
*
|
|
555
|
-
* Delete a registered stream
|
|
556
|
-
*/
|
|
557
|
-
static removeStream(streamId) {
|
|
558
|
-
delete this.#streams[streamId];
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
/**
|
|
562
|
-
* Remove one or all listeners of an event
|
|
563
|
-
*
|
|
564
|
-
* @param {String} key - Name of the event
|
|
565
|
-
* @param {(change:ChangeStreamDocument)=>void} cb - Callback
|
|
566
|
-
*/
|
|
567
|
-
static removeListener(key, cb) {
|
|
568
|
-
if (cb) this.#listeners[key] = this.#listeners[key].filter((c) => c != cb);
|
|
569
|
-
else this.#listeners[key] = [];
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
/**
|
|
573
|
-
* Unsubscribe to all events
|
|
574
|
-
*/
|
|
575
|
-
static removeAllListeners() {
|
|
576
|
-
this.#listeners = {};
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
// utils
|
|
581
|
-
function notEmpty(obj) {
|
|
582
|
-
obj ??= {};
|
|
583
|
-
return Object.keys(obj).length > 0;
|
|
584
|
-
}
|
|
585
|
-
/** @param {String} id */
|
|
586
|
-
function toObjectId(id) {
|
|
587
|
-
if (typeof id != "string") return id;
|
|
588
|
-
try {
|
|
589
|
-
return mongoose.Types.ObjectId.createFromHexString(id);
|
|
590
|
-
} catch (_) {
|
|
591
|
-
return new mongoose.Types.ObjectId(id); //use deprecated if fail
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
module.exports = MongoRealtime;
|
package/logo.png
DELETED
|
Binary file
|