mongodb-livedata-server 0.0.1
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 +63 -0
- package/dist/livedata_server.js +9 -0
- package/dist/meteor/binary-heap/max_heap.js +186 -0
- package/dist/meteor/binary-heap/min_heap.js +17 -0
- package/dist/meteor/binary-heap/min_max_heap.js +48 -0
- package/dist/meteor/callback-hook/hook.js +78 -0
- package/dist/meteor/ddp/crossbar.js +136 -0
- package/dist/meteor/ddp/heartbeat.js +77 -0
- package/dist/meteor/ddp/livedata_server.js +403 -0
- package/dist/meteor/ddp/method-invocation.js +72 -0
- package/dist/meteor/ddp/random-stream.js +100 -0
- package/dist/meteor/ddp/session-collection-view.js +106 -0
- package/dist/meteor/ddp/session-document-view.js +82 -0
- package/dist/meteor/ddp/session.js +570 -0
- package/dist/meteor/ddp/stream_server.js +181 -0
- package/dist/meteor/ddp/subscription.js +347 -0
- package/dist/meteor/ddp/utils.js +104 -0
- package/dist/meteor/ddp/writefence.js +111 -0
- package/dist/meteor/diff-sequence/diff.js +257 -0
- package/dist/meteor/ejson/ejson.js +569 -0
- package/dist/meteor/ejson/stringify.js +119 -0
- package/dist/meteor/ejson/utils.js +42 -0
- package/dist/meteor/id-map/id_map.js +92 -0
- package/dist/meteor/mongo/caching_change_observer.js +94 -0
- package/dist/meteor/mongo/doc_fetcher.js +53 -0
- package/dist/meteor/mongo/geojson_utils.js +41 -0
- package/dist/meteor/mongo/live_connection.js +264 -0
- package/dist/meteor/mongo/live_cursor.js +57 -0
- package/dist/meteor/mongo/minimongo_common.js +2002 -0
- package/dist/meteor/mongo/minimongo_matcher.js +217 -0
- package/dist/meteor/mongo/minimongo_sorter.js +268 -0
- package/dist/meteor/mongo/observe_driver_utils.js +73 -0
- package/dist/meteor/mongo/observe_multiplexer.js +228 -0
- package/dist/meteor/mongo/oplog-observe-driver.js +919 -0
- package/dist/meteor/mongo/oplog_tailing.js +352 -0
- package/dist/meteor/mongo/oplog_v2_converter.js +126 -0
- package/dist/meteor/mongo/polling_observe_driver.js +195 -0
- package/dist/meteor/mongo/synchronous-cursor.js +261 -0
- package/dist/meteor/mongo/synchronous-queue.js +110 -0
- package/dist/meteor/ordered-dict/ordered_dict.js +198 -0
- package/dist/meteor/random/AbstractRandomGenerator.js +92 -0
- package/dist/meteor/random/AleaRandomGenerator.js +90 -0
- package/dist/meteor/random/NodeRandomGenerator.js +42 -0
- package/dist/meteor/random/createAleaGenerator.js +32 -0
- package/dist/meteor/random/createRandom.js +22 -0
- package/dist/meteor/random/main.js +12 -0
- package/livedata_server.ts +3 -0
- package/meteor/LICENSE +28 -0
- package/meteor/binary-heap/max_heap.ts +225 -0
- package/meteor/binary-heap/min_heap.ts +15 -0
- package/meteor/binary-heap/min_max_heap.ts +53 -0
- package/meteor/callback-hook/hook.ts +85 -0
- package/meteor/ddp/crossbar.ts +148 -0
- package/meteor/ddp/heartbeat.ts +97 -0
- package/meteor/ddp/livedata_server.ts +473 -0
- package/meteor/ddp/method-invocation.ts +86 -0
- package/meteor/ddp/random-stream.ts +102 -0
- package/meteor/ddp/session-collection-view.ts +119 -0
- package/meteor/ddp/session-document-view.ts +92 -0
- package/meteor/ddp/session.ts +708 -0
- package/meteor/ddp/stream_server.ts +204 -0
- package/meteor/ddp/subscription.ts +392 -0
- package/meteor/ddp/utils.ts +119 -0
- package/meteor/ddp/writefence.ts +130 -0
- package/meteor/diff-sequence/diff.ts +295 -0
- package/meteor/ejson/ejson.ts +601 -0
- package/meteor/ejson/stringify.ts +122 -0
- package/meteor/ejson/utils.ts +38 -0
- package/meteor/id-map/id_map.ts +84 -0
- package/meteor/mongo/caching_change_observer.ts +120 -0
- package/meteor/mongo/doc_fetcher.ts +52 -0
- package/meteor/mongo/geojson_utils.ts +42 -0
- package/meteor/mongo/live_connection.ts +302 -0
- package/meteor/mongo/live_cursor.ts +79 -0
- package/meteor/mongo/minimongo_common.ts +2440 -0
- package/meteor/mongo/minimongo_matcher.ts +275 -0
- package/meteor/mongo/minimongo_sorter.ts +331 -0
- package/meteor/mongo/observe_driver_utils.ts +79 -0
- package/meteor/mongo/observe_multiplexer.ts +256 -0
- package/meteor/mongo/oplog-observe-driver.ts +1049 -0
- package/meteor/mongo/oplog_tailing.ts +414 -0
- package/meteor/mongo/oplog_v2_converter.ts +124 -0
- package/meteor/mongo/polling_observe_driver.ts +247 -0
- package/meteor/mongo/synchronous-cursor.ts +293 -0
- package/meteor/mongo/synchronous-queue.ts +119 -0
- package/meteor/ordered-dict/ordered_dict.ts +229 -0
- package/meteor/random/AbstractRandomGenerator.ts +99 -0
- package/meteor/random/AleaRandomGenerator.ts +96 -0
- package/meteor/random/NodeRandomGenerator.ts +37 -0
- package/meteor/random/createAleaGenerator.ts +31 -0
- package/meteor/random/createRandom.ts +19 -0
- package/meteor/random/main.ts +8 -0
- package/package.json +30 -0
- package/tsconfig.json +10 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { clone, equals } from "../ejson/ejson";
|
|
2
|
+
|
|
3
|
+
// A "crossbar" is a class that provides structured notification registration.
|
|
4
|
+
// See _match for the definition of how a notification matches a trigger.
|
|
5
|
+
// All notifications and triggers must have a string key named 'collection'.
|
|
6
|
+
|
|
7
|
+
export class _Crossbar {
|
|
8
|
+
private listenersByCollection: Record<string, any> = {};
|
|
9
|
+
private listenersByCollectionCount: Record<string, number> = {};
|
|
10
|
+
private nextId: number;
|
|
11
|
+
|
|
12
|
+
constructor(options) {
|
|
13
|
+
options = options || {};
|
|
14
|
+
|
|
15
|
+
this.nextId = 1;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// msg is a trigger or a notification
|
|
19
|
+
_collectionForMessage(msg: { collection?: any }) {
|
|
20
|
+
if (!msg.hasOwnProperty('collection')) {
|
|
21
|
+
return '';
|
|
22
|
+
} else if (typeof (msg.collection) === 'string') {
|
|
23
|
+
if (msg.collection === '')
|
|
24
|
+
throw Error("Message has empty collection!");
|
|
25
|
+
return msg.collection;
|
|
26
|
+
} else {
|
|
27
|
+
throw Error("Message has non-string collection!");
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Listen for notification that match 'trigger'. A notification
|
|
32
|
+
// matches if it has the key-value pairs in trigger as a
|
|
33
|
+
// subset. When a notification matches, call 'callback', passing
|
|
34
|
+
// the actual notification.
|
|
35
|
+
//
|
|
36
|
+
// Returns a listen handle, which is an object with a method
|
|
37
|
+
// stop(). Call stop() to stop listening.
|
|
38
|
+
//
|
|
39
|
+
// XXX It should be legal to call fire() from inside a listen()
|
|
40
|
+
// callback?
|
|
41
|
+
listen(trigger, callback) {
|
|
42
|
+
var self = this;
|
|
43
|
+
var id = self.nextId++;
|
|
44
|
+
|
|
45
|
+
var collection = self._collectionForMessage(trigger);
|
|
46
|
+
var record = { trigger: clone(trigger), callback: callback };
|
|
47
|
+
if (!self.listenersByCollection.hasOwnProperty(collection)) {
|
|
48
|
+
self.listenersByCollection[collection] = {};
|
|
49
|
+
self.listenersByCollectionCount[collection] = 0;
|
|
50
|
+
}
|
|
51
|
+
self.listenersByCollection[collection][id] = record;
|
|
52
|
+
self.listenersByCollectionCount[collection]++;
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
stop: function () {
|
|
56
|
+
delete self.listenersByCollection[collection][id];
|
|
57
|
+
self.listenersByCollectionCount[collection]--;
|
|
58
|
+
if (self.listenersByCollectionCount[collection] === 0) {
|
|
59
|
+
delete self.listenersByCollection[collection];
|
|
60
|
+
delete self.listenersByCollectionCount[collection];
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Fire the provided 'notification' (an object whose attribute
|
|
67
|
+
// values are all JSON-compatibile) -- inform all matching listeners
|
|
68
|
+
// (registered with listen()).
|
|
69
|
+
//
|
|
70
|
+
// If fire() is called inside a write fence, then each of the
|
|
71
|
+
// listener callbacks will be called inside the write fence as well.
|
|
72
|
+
//
|
|
73
|
+
// The listeners may be invoked in parallel, rather than serially.
|
|
74
|
+
fire(notification: Record<string, any>) {
|
|
75
|
+
var self = this;
|
|
76
|
+
|
|
77
|
+
var collection = self._collectionForMessage(notification);
|
|
78
|
+
|
|
79
|
+
if (!self.listenersByCollection.hasOwnProperty(collection)) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
var listenersForCollection = self.listenersByCollection[collection];
|
|
84
|
+
var callbackIds = [];
|
|
85
|
+
Object.entries<{ trigger: Record<string, any> }>(listenersForCollection).forEach(function ([id, l]) {
|
|
86
|
+
if (self._matches(notification, l.trigger)) {
|
|
87
|
+
callbackIds.push(id);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Listener callbacks can yield, so we need to first find all the ones that
|
|
92
|
+
// match in a single iteration over self.listenersByCollection (which can't
|
|
93
|
+
// be mutated during this iteration), and then invoke the matching
|
|
94
|
+
// callbacks, checking before each call to ensure they haven't stopped.
|
|
95
|
+
// Note that we don't have to check that
|
|
96
|
+
// self.listenersByCollection[collection] still === listenersForCollection,
|
|
97
|
+
// because the only way that stops being true is if listenersForCollection
|
|
98
|
+
// first gets reduced down to the empty object (and then never gets
|
|
99
|
+
// increased again).
|
|
100
|
+
for (const id of callbackIds) {
|
|
101
|
+
if (listenersForCollection.hasOwnProperty(id)) {
|
|
102
|
+
listenersForCollection[id].callback(notification);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// A notification matches a trigger if all keys that exist in both are equal.
|
|
108
|
+
//
|
|
109
|
+
// Examples:
|
|
110
|
+
// N:{collection: "C"} matches T:{collection: "C"}
|
|
111
|
+
// (a non-targeted write to a collection matches a
|
|
112
|
+
// non-targeted query)
|
|
113
|
+
// N:{collection: "C", id: "X"} matches T:{collection: "C"}
|
|
114
|
+
// (a targeted write to a collection matches a non-targeted query)
|
|
115
|
+
// N:{collection: "C"} matches T:{collection: "C", id: "X"}
|
|
116
|
+
// (a non-targeted write to a collection matches a
|
|
117
|
+
// targeted query)
|
|
118
|
+
// N:{collection: "C", id: "X"} matches T:{collection: "C", id: "X"}
|
|
119
|
+
// (a targeted write to a collection matches a targeted query targeted
|
|
120
|
+
// at the same document)
|
|
121
|
+
// N:{collection: "C", id: "X"} does not match T:{collection: "C", id: "Y"}
|
|
122
|
+
// (a targeted write to a collection does not match a targeted query
|
|
123
|
+
// targeted at a different document)
|
|
124
|
+
_matches(notification: Record<string, any>, trigger: Record<string, any>) {
|
|
125
|
+
// Most notifications that use the crossbar have a string `collection` and
|
|
126
|
+
// maybe an `id` that is a string or ObjectID. We're already dividing up
|
|
127
|
+
// triggers by collection, but let's fast-track "nope, different ID" (and
|
|
128
|
+
// avoid the overly generic EJSON.equals). This makes a noticeable
|
|
129
|
+
// performance difference; see https://github.com/meteor/meteor/pull/3697
|
|
130
|
+
if (typeof (notification.id) === 'string' && typeof (trigger.id) === 'string' && notification.id !== trigger.id) {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return Object.entries(trigger).every(([key, triggerValue]) => {
|
|
135
|
+
return !notification.hasOwnProperty(key) || equals(triggerValue, notification[key]);
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// The "invalidation crossbar" is a specific instance used by the DDP server to
|
|
142
|
+
// implement write fence notifications. Listener callbacks on this crossbar
|
|
143
|
+
// should call beginWrite on the current write fence before they return, if they
|
|
144
|
+
// want to delay the write fence from firing (ie, the DDP method-data-updated
|
|
145
|
+
// message from being sent).
|
|
146
|
+
export const _InvalidationCrossbar = new _Crossbar({
|
|
147
|
+
factName: "invalidation-crossbar-listeners"
|
|
148
|
+
});
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// Heartbeat options:
|
|
2
|
+
// heartbeatInterval: interval to send pings, in milliseconds.
|
|
3
|
+
// heartbeatTimeout: timeout to close the connection if a reply isn't
|
|
4
|
+
// received, in milliseconds.
|
|
5
|
+
// sendPing: function to call to send a ping on the connection.
|
|
6
|
+
// onTimeout: function to call to close the connection.
|
|
7
|
+
|
|
8
|
+
export class Heartbeat {
|
|
9
|
+
private heartbeatInterval: number;
|
|
10
|
+
private heartbeatTimeout: number;
|
|
11
|
+
private _sendPing: Function;
|
|
12
|
+
private _onTimeout: Function;
|
|
13
|
+
private _seenPacket: boolean;
|
|
14
|
+
private _heartbeatIntervalHandle: any;
|
|
15
|
+
private _heartbeatTimeoutHandle: any;
|
|
16
|
+
|
|
17
|
+
constructor(options) {
|
|
18
|
+
this.heartbeatInterval = options.heartbeatInterval;
|
|
19
|
+
this.heartbeatTimeout = options.heartbeatTimeout;
|
|
20
|
+
this._sendPing = options.sendPing;
|
|
21
|
+
this._onTimeout = options.onTimeout;
|
|
22
|
+
this._seenPacket = false;
|
|
23
|
+
|
|
24
|
+
this._heartbeatIntervalHandle = null;
|
|
25
|
+
this._heartbeatTimeoutHandle = null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
stop() {
|
|
29
|
+
this._clearHeartbeatIntervalTimer();
|
|
30
|
+
this._clearHeartbeatTimeoutTimer();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
start() {
|
|
34
|
+
this.stop();
|
|
35
|
+
this._startHeartbeatIntervalTimer();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
_startHeartbeatIntervalTimer() {
|
|
39
|
+
this._heartbeatIntervalHandle = setInterval(
|
|
40
|
+
() => this._heartbeatIntervalFired(),
|
|
41
|
+
this.heartbeatInterval
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
_startHeartbeatTimeoutTimer() {
|
|
46
|
+
this._heartbeatTimeoutHandle = setTimeout(
|
|
47
|
+
() => this._heartbeatTimeoutFired(),
|
|
48
|
+
this.heartbeatTimeout
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
_clearHeartbeatIntervalTimer() {
|
|
53
|
+
if (this._heartbeatIntervalHandle) {
|
|
54
|
+
clearInterval(this._heartbeatIntervalHandle);
|
|
55
|
+
this._heartbeatIntervalHandle = null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
_clearHeartbeatTimeoutTimer() {
|
|
60
|
+
if (this._heartbeatTimeoutHandle) {
|
|
61
|
+
clearTimeout(this._heartbeatTimeoutHandle);
|
|
62
|
+
this._heartbeatTimeoutHandle = null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// The heartbeat interval timer is fired when we should send a ping.
|
|
67
|
+
_heartbeatIntervalFired() {
|
|
68
|
+
// don't send ping if we've seen a packet since we last checked,
|
|
69
|
+
// *or* if we have already sent a ping and are awaiting a timeout.
|
|
70
|
+
// That shouldn't happen, but it's possible if
|
|
71
|
+
// `this.heartbeatInterval` is smaller than
|
|
72
|
+
// `this.heartbeatTimeout`.
|
|
73
|
+
if (! this._seenPacket && ! this._heartbeatTimeoutHandle) {
|
|
74
|
+
this._sendPing();
|
|
75
|
+
// Set up timeout, in case a pong doesn't arrive in time.
|
|
76
|
+
this._startHeartbeatTimeoutTimer();
|
|
77
|
+
}
|
|
78
|
+
this._seenPacket = false;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// The heartbeat timeout timer is fired when we sent a ping, but we
|
|
82
|
+
// timed out waiting for the pong.
|
|
83
|
+
_heartbeatTimeoutFired() {
|
|
84
|
+
this._heartbeatTimeoutHandle = null;
|
|
85
|
+
this._onTimeout();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
messageReceived() {
|
|
89
|
+
// Tell periodic checkin that we have seen a packet, and thus it
|
|
90
|
+
// does not need to send a ping this cycle.
|
|
91
|
+
this._seenPacket = true;
|
|
92
|
+
// If we were waiting for a pong, we got it.
|
|
93
|
+
if (this._heartbeatTimeoutHandle) {
|
|
94
|
+
this._clearHeartbeatTimeoutTimer();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
};
|