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.
Files changed (94) hide show
  1. package/README.md +63 -0
  2. package/dist/livedata_server.js +9 -0
  3. package/dist/meteor/binary-heap/max_heap.js +186 -0
  4. package/dist/meteor/binary-heap/min_heap.js +17 -0
  5. package/dist/meteor/binary-heap/min_max_heap.js +48 -0
  6. package/dist/meteor/callback-hook/hook.js +78 -0
  7. package/dist/meteor/ddp/crossbar.js +136 -0
  8. package/dist/meteor/ddp/heartbeat.js +77 -0
  9. package/dist/meteor/ddp/livedata_server.js +403 -0
  10. package/dist/meteor/ddp/method-invocation.js +72 -0
  11. package/dist/meteor/ddp/random-stream.js +100 -0
  12. package/dist/meteor/ddp/session-collection-view.js +106 -0
  13. package/dist/meteor/ddp/session-document-view.js +82 -0
  14. package/dist/meteor/ddp/session.js +570 -0
  15. package/dist/meteor/ddp/stream_server.js +181 -0
  16. package/dist/meteor/ddp/subscription.js +347 -0
  17. package/dist/meteor/ddp/utils.js +104 -0
  18. package/dist/meteor/ddp/writefence.js +111 -0
  19. package/dist/meteor/diff-sequence/diff.js +257 -0
  20. package/dist/meteor/ejson/ejson.js +569 -0
  21. package/dist/meteor/ejson/stringify.js +119 -0
  22. package/dist/meteor/ejson/utils.js +42 -0
  23. package/dist/meteor/id-map/id_map.js +92 -0
  24. package/dist/meteor/mongo/caching_change_observer.js +94 -0
  25. package/dist/meteor/mongo/doc_fetcher.js +53 -0
  26. package/dist/meteor/mongo/geojson_utils.js +41 -0
  27. package/dist/meteor/mongo/live_connection.js +264 -0
  28. package/dist/meteor/mongo/live_cursor.js +57 -0
  29. package/dist/meteor/mongo/minimongo_common.js +2002 -0
  30. package/dist/meteor/mongo/minimongo_matcher.js +217 -0
  31. package/dist/meteor/mongo/minimongo_sorter.js +268 -0
  32. package/dist/meteor/mongo/observe_driver_utils.js +73 -0
  33. package/dist/meteor/mongo/observe_multiplexer.js +228 -0
  34. package/dist/meteor/mongo/oplog-observe-driver.js +919 -0
  35. package/dist/meteor/mongo/oplog_tailing.js +352 -0
  36. package/dist/meteor/mongo/oplog_v2_converter.js +126 -0
  37. package/dist/meteor/mongo/polling_observe_driver.js +195 -0
  38. package/dist/meteor/mongo/synchronous-cursor.js +261 -0
  39. package/dist/meteor/mongo/synchronous-queue.js +110 -0
  40. package/dist/meteor/ordered-dict/ordered_dict.js +198 -0
  41. package/dist/meteor/random/AbstractRandomGenerator.js +92 -0
  42. package/dist/meteor/random/AleaRandomGenerator.js +90 -0
  43. package/dist/meteor/random/NodeRandomGenerator.js +42 -0
  44. package/dist/meteor/random/createAleaGenerator.js +32 -0
  45. package/dist/meteor/random/createRandom.js +22 -0
  46. package/dist/meteor/random/main.js +12 -0
  47. package/livedata_server.ts +3 -0
  48. package/meteor/LICENSE +28 -0
  49. package/meteor/binary-heap/max_heap.ts +225 -0
  50. package/meteor/binary-heap/min_heap.ts +15 -0
  51. package/meteor/binary-heap/min_max_heap.ts +53 -0
  52. package/meteor/callback-hook/hook.ts +85 -0
  53. package/meteor/ddp/crossbar.ts +148 -0
  54. package/meteor/ddp/heartbeat.ts +97 -0
  55. package/meteor/ddp/livedata_server.ts +473 -0
  56. package/meteor/ddp/method-invocation.ts +86 -0
  57. package/meteor/ddp/random-stream.ts +102 -0
  58. package/meteor/ddp/session-collection-view.ts +119 -0
  59. package/meteor/ddp/session-document-view.ts +92 -0
  60. package/meteor/ddp/session.ts +708 -0
  61. package/meteor/ddp/stream_server.ts +204 -0
  62. package/meteor/ddp/subscription.ts +392 -0
  63. package/meteor/ddp/utils.ts +119 -0
  64. package/meteor/ddp/writefence.ts +130 -0
  65. package/meteor/diff-sequence/diff.ts +295 -0
  66. package/meteor/ejson/ejson.ts +601 -0
  67. package/meteor/ejson/stringify.ts +122 -0
  68. package/meteor/ejson/utils.ts +38 -0
  69. package/meteor/id-map/id_map.ts +84 -0
  70. package/meteor/mongo/caching_change_observer.ts +120 -0
  71. package/meteor/mongo/doc_fetcher.ts +52 -0
  72. package/meteor/mongo/geojson_utils.ts +42 -0
  73. package/meteor/mongo/live_connection.ts +302 -0
  74. package/meteor/mongo/live_cursor.ts +79 -0
  75. package/meteor/mongo/minimongo_common.ts +2440 -0
  76. package/meteor/mongo/minimongo_matcher.ts +275 -0
  77. package/meteor/mongo/minimongo_sorter.ts +331 -0
  78. package/meteor/mongo/observe_driver_utils.ts +79 -0
  79. package/meteor/mongo/observe_multiplexer.ts +256 -0
  80. package/meteor/mongo/oplog-observe-driver.ts +1049 -0
  81. package/meteor/mongo/oplog_tailing.ts +414 -0
  82. package/meteor/mongo/oplog_v2_converter.ts +124 -0
  83. package/meteor/mongo/polling_observe_driver.ts +247 -0
  84. package/meteor/mongo/synchronous-cursor.ts +293 -0
  85. package/meteor/mongo/synchronous-queue.ts +119 -0
  86. package/meteor/ordered-dict/ordered_dict.ts +229 -0
  87. package/meteor/random/AbstractRandomGenerator.ts +99 -0
  88. package/meteor/random/AleaRandomGenerator.ts +96 -0
  89. package/meteor/random/NodeRandomGenerator.ts +37 -0
  90. package/meteor/random/createAleaGenerator.ts +31 -0
  91. package/meteor/random/createRandom.ts +19 -0
  92. package/meteor/random/main.ts +8 -0
  93. package/package.json +30 -0
  94. package/tsconfig.json +10 -0
@@ -0,0 +1,302 @@
1
+ import { Hook } from "../callback-hook/hook";
2
+ import { MongoClient, Db } from "mongodb";
3
+ import { CursorDescription } from "./live_cursor";
4
+ import { stringify } from "../ejson/ejson";
5
+ import { _createSynchronousCursor } from "./synchronous-cursor";
6
+ import { OplogHandle } from "./oplog_tailing";
7
+ import { DocFetcher } from "./doc_fetcher";
8
+ import { ObserveHandle, ObserveMultiplexer } from "./observe_multiplexer";
9
+ import { PollingObserveDriver } from "./polling_observe_driver";
10
+ import { OplogObserveDriver } from "./oplog-observe-driver";
11
+ import { MinimongoMatcher } from "./minimongo_matcher";
12
+ import MinimongoSorter from "./minimongo_sorter";
13
+
14
+ export class LiveMongoConnection {
15
+
16
+ private client: MongoClient;
17
+ public db: Db;
18
+
19
+ public _oplogHandle: OplogHandle = null;
20
+ public _docFetcher: DocFetcher = null;
21
+
22
+ private _observeMultiplexers: Record<string, any> = {};
23
+ private _onFailoverHook: Hook = new Hook();
24
+
25
+ constructor(url: string, options?) {
26
+ options = options || {};
27
+
28
+ var mongoOptions = {
29
+ ignoreUndefined: true,
30
+ maxPoolSize: undefined
31
+ };
32
+
33
+ // Internally the oplog connections specify their own maxPoolSize
34
+ // which we don't want to overwrite with any user defined value
35
+ if (options.hasOwnProperty("maxPoolSize")) {
36
+ // If we just set this for "server", replSet will override it. If we just
37
+ // set it for replSet, it will be ignored if we're not using a replSet.
38
+ mongoOptions.maxPoolSize = options.maxPoolSize;
39
+ }
40
+
41
+ this.client = new MongoClient(url, mongoOptions);
42
+ this.db = this.client.db();
43
+
44
+ this.client.on('serverDescriptionChanged', event => {
45
+ // When the connection is no longer against the primary node, execute all
46
+ // failover hooks. This is important for the driver as it has to re-pool the
47
+ // query when it happens.
48
+ if (
49
+ event.previousDescription.type !== 'RSPrimary' &&
50
+ event.newDescription.type === 'RSPrimary'
51
+ ) {
52
+ this._onFailoverHook.each(callback => {
53
+ callback();
54
+ return true;
55
+ });
56
+ }
57
+ });
58
+
59
+ if (options.oplogUrl) {
60
+ this._oplogHandle = new OplogHandle(options.oplogUrl, this.db.databaseName);
61
+ this._docFetcher = new DocFetcher(this.db);
62
+ }
63
+ }
64
+
65
+ close() {
66
+ var self = this;
67
+
68
+ if (!self.db)
69
+ throw Error("close called before Connection created?");
70
+
71
+ // XXX probably untested
72
+ var oplogHandle = self._oplogHandle;
73
+ self._oplogHandle = null;
74
+ if (oplogHandle)
75
+ oplogHandle.stop();
76
+
77
+ // Use Future.wrap so that errors get thrown. This happens to
78
+ // work even outside a fiber since the 'close' method is not
79
+ // actually asynchronous.
80
+ self.client.close(true);
81
+ }
82
+
83
+ // Tails the cursor described by cursorDescription, most likely on the
84
+ // oplog. Calls docCallback with each document found. Ignores errors and just
85
+ // restarts the tail on error.
86
+ //
87
+ // If timeoutMS is set, then if we don't get a new document every timeoutMS,
88
+ // kill and restart the cursor. This is primarily a workaround for #8598.
89
+ tail<T>(cursorDescription: CursorDescription<T>, docCallback: (doc: T) => void, timeoutMS?: number) {
90
+ if (!cursorDescription.options.tailable)
91
+ throw new Error("Can only tail a tailable cursor");
92
+
93
+ var cursor = _createSynchronousCursor(this.db, cursorDescription);
94
+
95
+ var stopped = false;
96
+ var lastTS: number;
97
+ const loop = async () => {
98
+ var doc = null;
99
+ while (true) {
100
+ if (stopped)
101
+ return;
102
+ try {
103
+ doc = await cursor._nextObjectPromiseWithTimeout(timeoutMS);
104
+ } catch (err) {
105
+ // There's no good way to figure out if this was actually an error from
106
+ // Mongo, or just client-side (including our own timeout error). Ah
107
+ // well. But either way, we need to retry the cursor (unless the failure
108
+ // was because the observe got stopped).
109
+ doc = null;
110
+ }
111
+ // Since we awaited a promise above, we need to check again to see if
112
+ // we've been stopped before calling the callback.
113
+ if (stopped)
114
+ return;
115
+ if (doc) {
116
+ // If a tailable cursor contains a "ts" field, use it to recreate the
117
+ // cursor on error. ("ts" is a standard that Mongo uses internally for
118
+ // the oplog, and there's a special flag that lets you do binary search
119
+ // on it instead of needing to use an index.)
120
+ lastTS = doc.ts;
121
+ docCallback(doc);
122
+ } else {
123
+ const newSelector = { ...cursorDescription.selector };
124
+ if (lastTS) {
125
+ (newSelector as any).ts = { $gt: lastTS };
126
+ }
127
+ const newDescription = new CursorDescription(
128
+ cursorDescription.collectionName,
129
+ newSelector,
130
+ cursorDescription.options
131
+ );
132
+ cursor = _createSynchronousCursor(this.db, newDescription);
133
+ // Mongo failover takes many seconds. Retry in a bit. (Without this
134
+ // setTimeout, we peg the CPU at 100% and never notice the actual
135
+ // failover.
136
+ setTimeout(loop, 100);
137
+ break;
138
+ }
139
+ }
140
+ };
141
+
142
+ setImmediate(loop);
143
+
144
+ return {
145
+ stop: function () {
146
+ stopped = true;
147
+ cursor.close();
148
+ }
149
+ };
150
+ }
151
+
152
+ _observeChanges(cursorDescription: CursorDescription<any>, ordered: boolean, callbacks: any, nonMutatingCallbacks: boolean) {
153
+ var self = this;
154
+
155
+ if (cursorDescription.options.tailable) {
156
+ return self._observeChangesTailable(cursorDescription, ordered, callbacks);
157
+ }
158
+
159
+ // You may not filter out _id when observing changes, because the id is a core
160
+ // part of the observeChanges API.
161
+ const fieldsOptions = cursorDescription.options.projection;
162
+ if (fieldsOptions && (fieldsOptions._id === 0 || fieldsOptions._id === false))
163
+ throw Error("You may not observe a cursor with {fields: {_id: 0}}");
164
+
165
+ var observeKey = stringify(Object.assign({ ordered: ordered }, cursorDescription));
166
+
167
+ var multiplexer: ObserveMultiplexer, observeDriver;
168
+ var firstHandle = false;
169
+
170
+ // Find a matching ObserveMultiplexer, or create a new one. This next block is
171
+ // guaranteed to not yield (and it doesn't call anything that can observe a
172
+ // new query), so no other calls to this function can interleave with it.
173
+ //Meteor._noYieldsAllowed(function () {
174
+ if (self._observeMultiplexers.hasOwnProperty(observeKey)) {
175
+ multiplexer = self._observeMultiplexers[observeKey];
176
+ } else {
177
+ firstHandle = true;
178
+ // Create a new ObserveMultiplexer.
179
+ multiplexer = new ObserveMultiplexer({
180
+ ordered: ordered,
181
+ onStop: function () {
182
+ delete self._observeMultiplexers[observeKey];
183
+ observeDriver.stop();
184
+ }
185
+ });
186
+ self._observeMultiplexers[observeKey] = multiplexer;
187
+ }
188
+ //});
189
+
190
+ var observeHandle = new ObserveHandle(
191
+ multiplexer,
192
+ callbacks,
193
+ nonMutatingCallbacks
194
+ );
195
+
196
+ if (firstHandle) {
197
+ let matcher: MinimongoMatcher;
198
+ let sorter: MinimongoSorter;
199
+
200
+ // At a bare minimum, using the oplog requires us to have an oplog, to
201
+ // want unordered callbacks, and to not want a callback on the polls
202
+ // that won't happen.
203
+ const basicPrerequisites = self._oplogHandle && !ordered && !callbacks._testOnlyPollCallback;
204
+
205
+ let selectorIsCompilable = false;
206
+ // We need to be able to compile the selector. Fall back to polling for
207
+ // some newfangled $selector that minimongo doesn't support yet.
208
+ try {
209
+ matcher = new MinimongoMatcher(cursorDescription.selector);
210
+ selectorIsCompilable = true;
211
+ } catch (e) {
212
+ }
213
+
214
+ const supportedByOplog = OplogObserveDriver.cursorSupported(cursorDescription, matcher);
215
+
216
+ let cursorIsSortable = false;
217
+ // And we need to be able to compile the sort, if any. eg, can't be
218
+ // {$natural: 1}.
219
+ if (!cursorDescription.options.sort)
220
+ cursorIsSortable = true;
221
+ try {
222
+ sorter = new MinimongoSorter(cursorDescription.options.sort);
223
+ cursorIsSortable = true;
224
+ } catch (e) {
225
+ }
226
+
227
+ const canUseOplog = basicPrerequisites && selectorIsCompilable && cursorIsSortable && supportedByOplog;
228
+
229
+ var driverClass = canUseOplog ? OplogObserveDriver : PollingObserveDriver;
230
+ observeDriver = new driverClass({
231
+ cursorDescription: cursorDescription,
232
+ mongoHandle: self,
233
+ multiplexer: multiplexer,
234
+ ordered: ordered,
235
+ matcher, // ignored by polling
236
+ sorter // ignored by polling
237
+ });
238
+ }
239
+
240
+ // Blocks until the initial adds have been sent.
241
+ multiplexer.addHandleAndSendInitialAdds(observeHandle);
242
+
243
+ return observeHandle;
244
+ }
245
+
246
+ // observeChanges for tailable cursors on capped collections.
247
+ //
248
+ // Some differences from normal cursors:
249
+ // - Will never produce anything other than 'added' or 'addedBefore'. If you
250
+ // do update a document that has already been produced, this will not notice
251
+ // it.
252
+ // - If you disconnect and reconnect from Mongo, it will essentially restart
253
+ // the query, which will lead to duplicate results. This is pretty bad,
254
+ // but if you include a field called 'ts' which is inserted as
255
+ // new MongoInternals.MongoTimestamp(0, 0) (which is initialized to the
256
+ // current Mongo-style timestamp), we'll be able to find the place to
257
+ // restart properly. (This field is specifically understood by Mongo with an
258
+ // optimization which allows it to find the right place to start without
259
+ // an index on ts. It's how the oplog works.)
260
+ // - No callbacks are triggered synchronously with the call (there's no
261
+ // differentiation between "initial data" and "later changes"; everything
262
+ // that matches the query gets sent asynchronously).
263
+ // - De-duplication is not implemented.
264
+ // - Does not yet interact with the write fence. Probably, this should work by
265
+ // ignoring removes (which don't work on capped collections) and updates
266
+ // (which don't affect tailable cursors), and just keeping track of the ID
267
+ // of the inserted object, and closing the write fence once you get to that
268
+ // ID (or timestamp?). This doesn't work well if the document doesn't match
269
+ // the query, though. On the other hand, the write fence can close
270
+ // immediately if it does not match the query. So if we trust minimongo
271
+ // enough to accurately evaluate the query against the write fence, we
272
+ // should be able to do this... Of course, minimongo doesn't even support
273
+ // Mongo Timestamps yet.
274
+ _observeChangesTailable(cursorDescription: CursorDescription<any>, ordered: boolean, callbacks: any) {
275
+ var self = this;
276
+
277
+ // Tailable cursors only ever call added/addedBefore callbacks, so it's an
278
+ // error if you didn't provide them.
279
+ if ((ordered && !callbacks.addedBefore) ||
280
+ (!ordered && !callbacks.added)) {
281
+ throw new Error("Can't observe an " + (ordered ? "ordered" : "unordered")
282
+ + " tailable cursor without a "
283
+ + (ordered ? "addedBefore" : "added") + " callback");
284
+ }
285
+
286
+ return self.tail(cursorDescription, function (doc) {
287
+ var id = doc._id;
288
+ delete doc._id;
289
+ // The ts is an implementation detail. Hide it.
290
+ delete doc.ts;
291
+ if (ordered) {
292
+ callbacks.addedBefore(id, doc, null);
293
+ } else {
294
+ callbacks.added(id, doc);
295
+ }
296
+ });
297
+ }
298
+
299
+ _onFailover(callback: Function) {
300
+ return this._onFailoverHook.register(callback);
301
+ }
302
+ }
@@ -0,0 +1,79 @@
1
+ import { Subscription } from "../ddp/subscription";
2
+ import MongoDB, { WithId } from "mongodb";
3
+ import { LiveMongoConnection } from "./live_connection";
4
+ import { Random } from "../random/main";
5
+
6
+ interface CustomFindOptions<T> extends MongoDB.FindOptions<WithId<T>> {
7
+ pollingThrottleMs?: number;
8
+ pollingIntervalMs?: number;
9
+ transform?: (doc: T) => T;
10
+ maxTimeMs?: number;
11
+ disableOplog?: boolean;
12
+ }
13
+
14
+ export class CursorDescription<T> {
15
+ public selector: MongoDB.Filter<WithId<T>>;
16
+ public options: CustomFindOptions<WithId<T>>;
17
+ constructor(public collectionName: string, selector: MongoDB.Filter<WithId<T>>, options?: CustomFindOptions<WithId<T>>) {
18
+ var self = this;
19
+ self.collectionName = collectionName;
20
+ self.selector = _rewriteSelector(selector);
21
+ self.options = options || {};
22
+ }
23
+ }
24
+
25
+ export class LiveCursor<T> {
26
+ public cursorDescription: CursorDescription<T>;
27
+
28
+ constructor (public mongo: LiveMongoConnection, collectionName: string, selector: MongoDB.Filter<WithId<T>>, options: CustomFindOptions<WithId<T>>) {
29
+ this.cursorDescription = new CursorDescription(collectionName, selector, options);
30
+ }
31
+
32
+ _publishCursor(sub: Subscription) {
33
+ const observeHandle = this.mongo._observeChanges(
34
+ this.cursorDescription,
35
+ false,
36
+ {
37
+ added: (id: string, fields: Partial<T>) => {
38
+ sub.added(this.cursorDescription.collectionName, id, fields);
39
+ },
40
+ changed: (id: string, fields: Partial<T>) => {
41
+ sub.changed(this.cursorDescription.collectionName, id, fields);
42
+ },
43
+ removed: (id: string) => {
44
+ sub.removed(this.cursorDescription.collectionName, id);
45
+ },
46
+ },
47
+ // Publications don't mutate the documents
48
+ // This is tested by the `livedata - publish callbacks clone` test
49
+ true
50
+ );
51
+
52
+ // We don't call sub.ready() here: it gets called in livedata_server, after
53
+ // possibly calling _publishCursor on multiple returned cursors.
54
+
55
+ // register stop callback (expects lambda w/ no args).
56
+ sub.onStop(function() {
57
+ observeHandle.stop();
58
+ });
59
+
60
+ // return the observeHandle in case it needs to be stopped early
61
+ return observeHandle;
62
+ }
63
+ }
64
+
65
+
66
+ function _rewriteSelector<T>(selector: MongoDB.Filter<WithId<T>>) {
67
+ if (Array.isArray(selector)) {
68
+ // This is consistent with the Mongo console itself; if we don't do this
69
+ // check passing an empty array ends up selecting all items
70
+ throw new Error("Mongo selector can't be an array.");
71
+ }
72
+
73
+ if (!selector || ('_id' in selector && !selector._id)) {
74
+ // can't match anything
75
+ return { _id: Random.id() } as MongoDB.Filter<WithId<T>>;
76
+ }
77
+
78
+ return selector;
79
+ }