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,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleError = exports.checkError = exports.isInfOrNaN = exports.isArguments = exports.convertMapToObject = exports.hasOwn = exports.lengthOf = exports.keysOf = exports.isObject = exports.isFunction = void 0;
4
+ const isFunction = (fn) => typeof fn === 'function';
5
+ exports.isFunction = isFunction;
6
+ const isObject = (fn) => typeof fn === 'object';
7
+ exports.isObject = isObject;
8
+ const keysOf = (obj) => Object.keys(obj);
9
+ exports.keysOf = keysOf;
10
+ const lengthOf = (obj) => Object.keys(obj).length;
11
+ exports.lengthOf = lengthOf;
12
+ const hasOwn = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop);
13
+ exports.hasOwn = hasOwn;
14
+ const convertMapToObject = (map) => Array.from(map).reduce((acc, [key, value]) => {
15
+ // reassign to not create new object
16
+ acc[key] = value;
17
+ return acc;
18
+ }, {});
19
+ exports.convertMapToObject = convertMapToObject;
20
+ function isArguments(obj) {
21
+ return obj != null && (0, exports.hasOwn)(obj, 'callee');
22
+ }
23
+ exports.isArguments = isArguments;
24
+ const isInfOrNaN = obj => Number.isNaN(obj) || obj === Infinity || obj === -Infinity;
25
+ exports.isInfOrNaN = isInfOrNaN;
26
+ exports.checkError = {
27
+ maxStack: (msgError) => new RegExp('Maximum call stack size exceeded', 'g').test(msgError),
28
+ };
29
+ function handleError(fn, ...args) {
30
+ try {
31
+ return fn.apply(this, args);
32
+ }
33
+ catch (error) {
34
+ const isMaxStack = exports.checkError.maxStack(error.message);
35
+ if (isMaxStack) {
36
+ throw new Error('Converting circular structure to JSON');
37
+ }
38
+ throw error;
39
+ }
40
+ }
41
+ exports.handleError = handleError;
42
+ ;
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.IdMap = void 0;
27
+ const EJSON = __importStar(require("../ejson/ejson"));
28
+ class IdMap {
29
+ constructor(idStringify, idParse) {
30
+ this._map = new Map();
31
+ this._idStringify = idStringify || JSON.stringify;
32
+ this._idParse = idParse || JSON.parse;
33
+ }
34
+ // Some of these methods are designed to match methods on OrderedDict, since
35
+ // (eg) ObserveMultiplex and _CachingChangeObserver use them interchangeably.
36
+ // (Conceivably, this should be replaced with "UnorderedDict" with a specific
37
+ // set of methods that overlap between the two.)
38
+ get(id) {
39
+ const key = this._idStringify(id);
40
+ return this._map.get(key);
41
+ }
42
+ set(id, value) {
43
+ const key = this._idStringify(id);
44
+ this._map.set(key, value);
45
+ }
46
+ remove(id) {
47
+ const key = this._idStringify(id);
48
+ this._map.delete(key);
49
+ }
50
+ has(id) {
51
+ const key = this._idStringify(id);
52
+ return this._map.has(key);
53
+ }
54
+ empty() {
55
+ return this._map.size === 0;
56
+ }
57
+ clear() {
58
+ this._map.clear();
59
+ }
60
+ // Iterates over the items in the map. Return `false` to break the loop.
61
+ forEach(iterator) {
62
+ // don't use _.each, because we can't break out of it.
63
+ for (let [key, value] of this._map) {
64
+ const breakIfFalse = iterator.call(null, value, this._idParse(key));
65
+ if (breakIfFalse === false) {
66
+ return;
67
+ }
68
+ }
69
+ }
70
+ size() {
71
+ return this._map.size;
72
+ }
73
+ setDefault(id, def) {
74
+ const key = this._idStringify(id);
75
+ if (this._map.has(key)) {
76
+ return this._map.get(key);
77
+ }
78
+ this._map.set(key, def);
79
+ return def;
80
+ }
81
+ // Assumes that values are EJSON-cloneable, and that we don't need to clone
82
+ // IDs (ie, that nobody is going to mutate an ObjectId).
83
+ clone() {
84
+ const clone = new IdMap(this._idStringify, this._idParse);
85
+ // copy directly to avoid stringify/parse overhead
86
+ this._map.forEach(function (value, key) {
87
+ clone._map.set(key, EJSON.clone(value));
88
+ });
89
+ return clone;
90
+ }
91
+ }
92
+ exports.IdMap = IdMap;
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+ // _CachingChangeObserver is an object which receives observeChanges callbacks
3
+ // and keeps a cache of the current cursor state up to date in this.docs. Users
4
+ // of this class should read the docs field but not modify it. You should pass
5
+ // the "applyChange" field as the callbacks to the underlying observeChanges
6
+ // call. Optionally, you can specify your own observeChanges callbacks which are
7
+ // invoked immediately before the docs field is updated; this object is made
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports._CachingChangeObserver = void 0;
10
+ const diff_1 = require("../diff-sequence/diff");
11
+ const ejson_1 = require("../ejson/ejson");
12
+ const ordered_dict_1 = require("../ordered-dict/ordered_dict");
13
+ // available as `this` to those callbacks.
14
+ class _CachingChangeObserver {
15
+ constructor(options = {}) {
16
+ const orderedFromCallbacks = (options.callbacks &&
17
+ !!(options.callbacks.addedBefore || options.callbacks.movedBefore));
18
+ if (options.hasOwnProperty('ordered')) {
19
+ this.ordered = options.ordered;
20
+ if (options.callbacks && options.ordered !== orderedFromCallbacks) {
21
+ throw Error('ordered option doesn\'t match callbacks');
22
+ }
23
+ }
24
+ else if (options.callbacks) {
25
+ this.ordered = orderedFromCallbacks;
26
+ }
27
+ else {
28
+ throw Error('must provide ordered or callbacks');
29
+ }
30
+ const callbacks = options.callbacks || {};
31
+ if (this.ordered) {
32
+ this.docs = new ordered_dict_1.OrderedDict();
33
+ this.applyChange = {
34
+ addedBefore: (id, fields, before) => {
35
+ // Take a shallow copy since the top-level properties can be changed
36
+ const doc = Object.assign({}, fields);
37
+ doc._id = id;
38
+ if (callbacks.addedBefore) {
39
+ callbacks.addedBefore.call(this, id, (0, ejson_1.clone)(fields), before);
40
+ }
41
+ // This line triggers if we provide added with movedBefore.
42
+ if (callbacks.added) {
43
+ callbacks.added.call(this, id, (0, ejson_1.clone)(fields));
44
+ }
45
+ // XXX could `before` be a falsy ID? Technically
46
+ // idStringify seems to allow for them -- though
47
+ // OrderedDict won't call stringify on a falsy arg.
48
+ this.docs.putBefore(id, doc, before || null);
49
+ },
50
+ movedBefore: (id, before) => {
51
+ const doc = this.docs.get(id);
52
+ if (callbacks.movedBefore) {
53
+ callbacks.movedBefore.call(this, id, before);
54
+ }
55
+ this.docs.moveBefore(id, before || null);
56
+ },
57
+ };
58
+ }
59
+ else {
60
+ this.docs = new Map();
61
+ this.applyChange = {
62
+ added: (id, fields) => {
63
+ // Take a shallow copy since the top-level properties can be changed
64
+ const doc = Object.assign({}, fields);
65
+ if (callbacks.added) {
66
+ callbacks.added.call(this, id, (0, ejson_1.clone)(fields));
67
+ }
68
+ doc._id = id;
69
+ this.docs.set(id, doc);
70
+ },
71
+ };
72
+ }
73
+ // The methods in _IdMap and OrderedDict used by these callbacks are
74
+ // identical.
75
+ this.applyChange.changed = (id, fields) => {
76
+ const doc = this.docs.get(id);
77
+ if (!doc) {
78
+ throw new Error(`Unknown id for changed: ${id}`);
79
+ }
80
+ if (callbacks.changed) {
81
+ callbacks.changed.call(this, id, (0, ejson_1.clone)(fields));
82
+ }
83
+ diff_1.DiffSequence.applyChanges(doc, fields);
84
+ };
85
+ this.applyChange.removed = id => {
86
+ if (callbacks.removed) {
87
+ callbacks.removed.call(this, id);
88
+ }
89
+ this.docs.delete(id);
90
+ };
91
+ }
92
+ }
93
+ exports._CachingChangeObserver = _CachingChangeObserver;
94
+ ;
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DocFetcher = void 0;
4
+ const ejson_1 = require("../ejson/ejson");
5
+ class DocFetcher {
6
+ constructor(db) {
7
+ this.db = db;
8
+ this._callbacksForOp = new Map();
9
+ }
10
+ // Fetches document "id" from collectionName, returning it or null if not
11
+ // found.
12
+ //
13
+ // If you make multiple calls to fetch() with the same op reference,
14
+ // DocFetcher may assume that they all return the same document. (It does
15
+ // not check to see if collectionName/id match.)
16
+ //
17
+ // You may assume that callback is never called synchronously (and in fact
18
+ // OplogObserveDriver does so).
19
+ async fetch(collectionName, id, op, callback) {
20
+ const self = this;
21
+ // If there's already an in-progress fetch for this cache key, yield until
22
+ // it's done and return whatever it returns.
23
+ if (self._callbacksForOp.has(op)) {
24
+ self._callbacksForOp.get(op).push(callback);
25
+ return;
26
+ }
27
+ const callbacks = [callback];
28
+ self._callbacksForOp.set(op, callbacks);
29
+ try {
30
+ var doc = await self.db.collection(collectionName).findOne({ _id: id }) || null;
31
+ // Return doc to all relevant callbacks. Note that this array can
32
+ // continue to grow during callback execution.
33
+ while (callbacks.length > 0) {
34
+ // Clone the document so that the various calls to fetch don't return
35
+ // objects that are intertwingled with each other. Clone before
36
+ // popping the future, so that if clone throws, the error gets passed
37
+ // to the next callback.
38
+ callbacks.pop()(null, (0, ejson_1.clone)(doc));
39
+ }
40
+ }
41
+ catch (e) {
42
+ while (callbacks.length > 0) {
43
+ callbacks.pop()(e);
44
+ }
45
+ }
46
+ finally {
47
+ // XXX consider keeping the doc around for a period of time before
48
+ // removing from the cache
49
+ self._callbacksForOp.delete(op);
50
+ }
51
+ }
52
+ }
53
+ exports.DocFetcher = DocFetcher;
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.geometryWithinRadius = exports.pointDistance = exports.numberToRadius = void 0;
4
+ function numberToRadius(number) {
5
+ return number * Math.PI / 180;
6
+ }
7
+ exports.numberToRadius = numberToRadius;
8
+ // from http://www.movable-type.co.uk/scripts/latlong.html
9
+ function pointDistance(pt1, pt2) {
10
+ var lon1 = pt1.coordinates[0], lat1 = pt1.coordinates[1], lon2 = pt2.coordinates[0], lat2 = pt2.coordinates[1], dLat = numberToRadius(lat2 - lat1), dLon = numberToRadius(lon2 - lon1), a = Math.pow(Math.sin(dLat / 2), 2) + Math.cos(numberToRadius(lat1))
11
+ * Math.cos(numberToRadius(lat2)) * Math.pow(Math.sin(dLon / 2), 2), c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
12
+ // Earth radius is 6371 km
13
+ return (6371 * c) * 1000; // returns meters
14
+ }
15
+ exports.pointDistance = pointDistance;
16
+ // checks if geometry lies entirely within a circle
17
+ // works with Point, LineString, Polygon
18
+ function geometryWithinRadius(geometry, center, radius) {
19
+ if (geometry.type == 'Point') {
20
+ return pointDistance(geometry, center) <= radius;
21
+ }
22
+ else if (geometry.type == 'LineString' || geometry.type == 'Polygon') {
23
+ var point = {};
24
+ var coordinates;
25
+ if (geometry.type == 'Polygon') {
26
+ // it's enough to check the exterior ring of the Polygon
27
+ coordinates = geometry.coordinates[0];
28
+ }
29
+ else {
30
+ coordinates = geometry.coordinates;
31
+ }
32
+ for (var i in coordinates) {
33
+ point.coordinates = coordinates[i];
34
+ if (pointDistance(point, center) > radius) {
35
+ return false;
36
+ }
37
+ }
38
+ }
39
+ return true;
40
+ }
41
+ exports.geometryWithinRadius = geometryWithinRadius;
@@ -0,0 +1,264 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.LiveMongoConnection = void 0;
7
+ const hook_1 = require("../callback-hook/hook");
8
+ const mongodb_1 = require("mongodb");
9
+ const live_cursor_1 = require("./live_cursor");
10
+ const ejson_1 = require("../ejson/ejson");
11
+ const synchronous_cursor_1 = require("./synchronous-cursor");
12
+ const oplog_tailing_1 = require("./oplog_tailing");
13
+ const doc_fetcher_1 = require("./doc_fetcher");
14
+ const observe_multiplexer_1 = require("./observe_multiplexer");
15
+ const polling_observe_driver_1 = require("./polling_observe_driver");
16
+ const oplog_observe_driver_1 = require("./oplog-observe-driver");
17
+ const minimongo_matcher_1 = require("./minimongo_matcher");
18
+ const minimongo_sorter_1 = __importDefault(require("./minimongo_sorter"));
19
+ class LiveMongoConnection {
20
+ constructor(url, options) {
21
+ this._oplogHandle = null;
22
+ this._docFetcher = null;
23
+ this._observeMultiplexers = {};
24
+ this._onFailoverHook = new hook_1.Hook();
25
+ options = options || {};
26
+ var mongoOptions = {
27
+ ignoreUndefined: true,
28
+ maxPoolSize: undefined
29
+ };
30
+ // Internally the oplog connections specify their own maxPoolSize
31
+ // which we don't want to overwrite with any user defined value
32
+ if (options.hasOwnProperty("maxPoolSize")) {
33
+ // If we just set this for "server", replSet will override it. If we just
34
+ // set it for replSet, it will be ignored if we're not using a replSet.
35
+ mongoOptions.maxPoolSize = options.maxPoolSize;
36
+ }
37
+ this.client = new mongodb_1.MongoClient(url, mongoOptions);
38
+ this.db = this.client.db();
39
+ this.client.on('serverDescriptionChanged', event => {
40
+ // When the connection is no longer against the primary node, execute all
41
+ // failover hooks. This is important for the driver as it has to re-pool the
42
+ // query when it happens.
43
+ if (event.previousDescription.type !== 'RSPrimary' &&
44
+ event.newDescription.type === 'RSPrimary') {
45
+ this._onFailoverHook.each(callback => {
46
+ callback();
47
+ return true;
48
+ });
49
+ }
50
+ });
51
+ if (options.oplogUrl) {
52
+ this._oplogHandle = new oplog_tailing_1.OplogHandle(options.oplogUrl, this.db.databaseName);
53
+ this._docFetcher = new doc_fetcher_1.DocFetcher(this.db);
54
+ }
55
+ }
56
+ close() {
57
+ var self = this;
58
+ if (!self.db)
59
+ throw Error("close called before Connection created?");
60
+ // XXX probably untested
61
+ var oplogHandle = self._oplogHandle;
62
+ self._oplogHandle = null;
63
+ if (oplogHandle)
64
+ oplogHandle.stop();
65
+ // Use Future.wrap so that errors get thrown. This happens to
66
+ // work even outside a fiber since the 'close' method is not
67
+ // actually asynchronous.
68
+ self.client.close(true);
69
+ }
70
+ // Tails the cursor described by cursorDescription, most likely on the
71
+ // oplog. Calls docCallback with each document found. Ignores errors and just
72
+ // restarts the tail on error.
73
+ //
74
+ // If timeoutMS is set, then if we don't get a new document every timeoutMS,
75
+ // kill and restart the cursor. This is primarily a workaround for #8598.
76
+ tail(cursorDescription, docCallback, timeoutMS) {
77
+ if (!cursorDescription.options.tailable)
78
+ throw new Error("Can only tail a tailable cursor");
79
+ var cursor = (0, synchronous_cursor_1._createSynchronousCursor)(this.db, cursorDescription);
80
+ var stopped = false;
81
+ var lastTS;
82
+ const loop = async () => {
83
+ var doc = null;
84
+ while (true) {
85
+ if (stopped)
86
+ return;
87
+ try {
88
+ doc = await cursor._nextObjectPromiseWithTimeout(timeoutMS);
89
+ }
90
+ catch (err) {
91
+ // There's no good way to figure out if this was actually an error from
92
+ // Mongo, or just client-side (including our own timeout error). Ah
93
+ // well. But either way, we need to retry the cursor (unless the failure
94
+ // was because the observe got stopped).
95
+ doc = null;
96
+ }
97
+ // Since we awaited a promise above, we need to check again to see if
98
+ // we've been stopped before calling the callback.
99
+ if (stopped)
100
+ return;
101
+ if (doc) {
102
+ // If a tailable cursor contains a "ts" field, use it to recreate the
103
+ // cursor on error. ("ts" is a standard that Mongo uses internally for
104
+ // the oplog, and there's a special flag that lets you do binary search
105
+ // on it instead of needing to use an index.)
106
+ lastTS = doc.ts;
107
+ docCallback(doc);
108
+ }
109
+ else {
110
+ const newSelector = Object.assign({}, cursorDescription.selector);
111
+ if (lastTS) {
112
+ newSelector.ts = { $gt: lastTS };
113
+ }
114
+ const newDescription = new live_cursor_1.CursorDescription(cursorDescription.collectionName, newSelector, cursorDescription.options);
115
+ cursor = (0, synchronous_cursor_1._createSynchronousCursor)(this.db, newDescription);
116
+ // Mongo failover takes many seconds. Retry in a bit. (Without this
117
+ // setTimeout, we peg the CPU at 100% and never notice the actual
118
+ // failover.
119
+ setTimeout(loop, 100);
120
+ break;
121
+ }
122
+ }
123
+ };
124
+ setImmediate(loop);
125
+ return {
126
+ stop: function () {
127
+ stopped = true;
128
+ cursor.close();
129
+ }
130
+ };
131
+ }
132
+ _observeChanges(cursorDescription, ordered, callbacks, nonMutatingCallbacks) {
133
+ var self = this;
134
+ if (cursorDescription.options.tailable) {
135
+ return self._observeChangesTailable(cursorDescription, ordered, callbacks);
136
+ }
137
+ // You may not filter out _id when observing changes, because the id is a core
138
+ // part of the observeChanges API.
139
+ const fieldsOptions = cursorDescription.options.projection;
140
+ if (fieldsOptions && (fieldsOptions._id === 0 || fieldsOptions._id === false))
141
+ throw Error("You may not observe a cursor with {fields: {_id: 0}}");
142
+ var observeKey = (0, ejson_1.stringify)(Object.assign({ ordered: ordered }, cursorDescription));
143
+ var multiplexer, observeDriver;
144
+ var firstHandle = false;
145
+ // Find a matching ObserveMultiplexer, or create a new one. This next block is
146
+ // guaranteed to not yield (and it doesn't call anything that can observe a
147
+ // new query), so no other calls to this function can interleave with it.
148
+ //Meteor._noYieldsAllowed(function () {
149
+ if (self._observeMultiplexers.hasOwnProperty(observeKey)) {
150
+ multiplexer = self._observeMultiplexers[observeKey];
151
+ }
152
+ else {
153
+ firstHandle = true;
154
+ // Create a new ObserveMultiplexer.
155
+ multiplexer = new observe_multiplexer_1.ObserveMultiplexer({
156
+ ordered: ordered,
157
+ onStop: function () {
158
+ delete self._observeMultiplexers[observeKey];
159
+ observeDriver.stop();
160
+ }
161
+ });
162
+ self._observeMultiplexers[observeKey] = multiplexer;
163
+ }
164
+ //});
165
+ var observeHandle = new observe_multiplexer_1.ObserveHandle(multiplexer, callbacks, nonMutatingCallbacks);
166
+ if (firstHandle) {
167
+ let matcher;
168
+ let sorter;
169
+ // At a bare minimum, using the oplog requires us to have an oplog, to
170
+ // want unordered callbacks, and to not want a callback on the polls
171
+ // that won't happen.
172
+ const basicPrerequisites = self._oplogHandle && !ordered && !callbacks._testOnlyPollCallback;
173
+ let selectorIsCompilable = false;
174
+ // We need to be able to compile the selector. Fall back to polling for
175
+ // some newfangled $selector that minimongo doesn't support yet.
176
+ try {
177
+ matcher = new minimongo_matcher_1.MinimongoMatcher(cursorDescription.selector);
178
+ selectorIsCompilable = true;
179
+ }
180
+ catch (e) {
181
+ }
182
+ const supportedByOplog = oplog_observe_driver_1.OplogObserveDriver.cursorSupported(cursorDescription, matcher);
183
+ let cursorIsSortable = false;
184
+ // And we need to be able to compile the sort, if any. eg, can't be
185
+ // {$natural: 1}.
186
+ if (!cursorDescription.options.sort)
187
+ cursorIsSortable = true;
188
+ try {
189
+ sorter = new minimongo_sorter_1.default(cursorDescription.options.sort);
190
+ cursorIsSortable = true;
191
+ }
192
+ catch (e) {
193
+ }
194
+ const canUseOplog = basicPrerequisites && selectorIsCompilable && cursorIsSortable && supportedByOplog;
195
+ var driverClass = canUseOplog ? oplog_observe_driver_1.OplogObserveDriver : polling_observe_driver_1.PollingObserveDriver;
196
+ observeDriver = new driverClass({
197
+ cursorDescription: cursorDescription,
198
+ mongoHandle: self,
199
+ multiplexer: multiplexer,
200
+ ordered: ordered,
201
+ matcher,
202
+ sorter // ignored by polling
203
+ });
204
+ }
205
+ // Blocks until the initial adds have been sent.
206
+ multiplexer.addHandleAndSendInitialAdds(observeHandle);
207
+ return observeHandle;
208
+ }
209
+ // observeChanges for tailable cursors on capped collections.
210
+ //
211
+ // Some differences from normal cursors:
212
+ // - Will never produce anything other than 'added' or 'addedBefore'. If you
213
+ // do update a document that has already been produced, this will not notice
214
+ // it.
215
+ // - If you disconnect and reconnect from Mongo, it will essentially restart
216
+ // the query, which will lead to duplicate results. This is pretty bad,
217
+ // but if you include a field called 'ts' which is inserted as
218
+ // new MongoInternals.MongoTimestamp(0, 0) (which is initialized to the
219
+ // current Mongo-style timestamp), we'll be able to find the place to
220
+ // restart properly. (This field is specifically understood by Mongo with an
221
+ // optimization which allows it to find the right place to start without
222
+ // an index on ts. It's how the oplog works.)
223
+ // - No callbacks are triggered synchronously with the call (there's no
224
+ // differentiation between "initial data" and "later changes"; everything
225
+ // that matches the query gets sent asynchronously).
226
+ // - De-duplication is not implemented.
227
+ // - Does not yet interact with the write fence. Probably, this should work by
228
+ // ignoring removes (which don't work on capped collections) and updates
229
+ // (which don't affect tailable cursors), and just keeping track of the ID
230
+ // of the inserted object, and closing the write fence once you get to that
231
+ // ID (or timestamp?). This doesn't work well if the document doesn't match
232
+ // the query, though. On the other hand, the write fence can close
233
+ // immediately if it does not match the query. So if we trust minimongo
234
+ // enough to accurately evaluate the query against the write fence, we
235
+ // should be able to do this... Of course, minimongo doesn't even support
236
+ // Mongo Timestamps yet.
237
+ _observeChangesTailable(cursorDescription, ordered, callbacks) {
238
+ var self = this;
239
+ // Tailable cursors only ever call added/addedBefore callbacks, so it's an
240
+ // error if you didn't provide them.
241
+ if ((ordered && !callbacks.addedBefore) ||
242
+ (!ordered && !callbacks.added)) {
243
+ throw new Error("Can't observe an " + (ordered ? "ordered" : "unordered")
244
+ + " tailable cursor without a "
245
+ + (ordered ? "addedBefore" : "added") + " callback");
246
+ }
247
+ return self.tail(cursorDescription, function (doc) {
248
+ var id = doc._id;
249
+ delete doc._id;
250
+ // The ts is an implementation detail. Hide it.
251
+ delete doc.ts;
252
+ if (ordered) {
253
+ callbacks.addedBefore(id, doc, null);
254
+ }
255
+ else {
256
+ callbacks.added(id, doc);
257
+ }
258
+ });
259
+ }
260
+ _onFailover(callback) {
261
+ return this._onFailoverHook.register(callback);
262
+ }
263
+ }
264
+ exports.LiveMongoConnection = LiveMongoConnection;
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LiveCursor = exports.CursorDescription = void 0;
4
+ const main_1 = require("../random/main");
5
+ class CursorDescription {
6
+ constructor(collectionName, selector, options) {
7
+ this.collectionName = collectionName;
8
+ var self = this;
9
+ self.collectionName = collectionName;
10
+ self.selector = _rewriteSelector(selector);
11
+ self.options = options || {};
12
+ }
13
+ }
14
+ exports.CursorDescription = CursorDescription;
15
+ class LiveCursor {
16
+ constructor(mongo, collectionName, selector, options) {
17
+ this.mongo = mongo;
18
+ this.cursorDescription = new CursorDescription(collectionName, selector, options);
19
+ }
20
+ _publishCursor(sub) {
21
+ const observeHandle = this.mongo._observeChanges(this.cursorDescription, false, {
22
+ added: (id, fields) => {
23
+ sub.added(this.cursorDescription.collectionName, id, fields);
24
+ },
25
+ changed: (id, fields) => {
26
+ sub.changed(this.cursorDescription.collectionName, id, fields);
27
+ },
28
+ removed: (id) => {
29
+ sub.removed(this.cursorDescription.collectionName, id);
30
+ },
31
+ },
32
+ // Publications don't mutate the documents
33
+ // This is tested by the `livedata - publish callbacks clone` test
34
+ true);
35
+ // We don't call sub.ready() here: it gets called in livedata_server, after
36
+ // possibly calling _publishCursor on multiple returned cursors.
37
+ // register stop callback (expects lambda w/ no args).
38
+ sub.onStop(function () {
39
+ observeHandle.stop();
40
+ });
41
+ // return the observeHandle in case it needs to be stopped early
42
+ return observeHandle;
43
+ }
44
+ }
45
+ exports.LiveCursor = LiveCursor;
46
+ function _rewriteSelector(selector) {
47
+ if (Array.isArray(selector)) {
48
+ // This is consistent with the Mongo console itself; if we don't do this
49
+ // check passing an empty array ends up selecting all items
50
+ throw new Error("Mongo selector can't be an array.");
51
+ }
52
+ if (!selector || ('_id' in selector && !selector._id)) {
53
+ // can't match anything
54
+ return { _id: main_1.Random.id() };
55
+ }
56
+ return selector;
57
+ }