mongodb-livedata-server 0.0.13 → 0.1.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/README.md CHANGED
@@ -1,69 +1,70 @@
1
- MongoDB Live Data Server
2
- ========================
3
-
4
- This project is essentially a MongoDB live data driver (based either on polling or on Oplog tailing) combined with a DDP server, extracted
5
- out of [Meteor](https://github.com/meteor/meteor), with **Fibers** and **underscore** dependencies removed and code converted to Typescript.
6
-
7
- Live data is one of the root concepts of Meteor. Data is served via WebSockets via the DDP protocol and updated automatically whenever something changes in the database. Also, calling server methods via WebSocket is supported.
8
-
9
- Using Meteor locks you into the Meteor ecosystem, which has some problems (mostly for historical reasons). Using live data as a separate npm package might be preferable in many scenarios. Also, people who are trying to migrate from Meteor, might find this package useful as an intermediate step.
10
-
11
- ### Installation
12
-
13
- ```
14
- npm i mongodb-livedata-server
15
- ```
16
-
17
- ### Usage
18
-
19
- As a most common example, this is how you can use livedata with Express.js:
20
-
21
- ```js
22
- const { DDPServer, LiveCursor, LiveMongoConnection } = require('mongodb-livedata-server')
23
- const express = require('express')
24
- const app = express()
25
- const port = 3000
26
-
27
- app.get('/', (req, res) => {
28
- res.send('Hello World!')
29
- })
30
-
31
- const httpServer = app.listen(port, () => {
32
- console.log(`Example app listening on port ${port}`)
33
- })
34
-
35
- const liveMongoConnection = new LiveMongoConnection(process.env.MONGO_URL, {
36
- oplogUrl: process.env.MONGO_OPLOG_URL
37
- });
38
- const liveDataServer = new DDPServer({}, httpServer);
39
-
40
- liveDataServer.methods({
41
- "test-method": async (msg) => {
42
- console.log("Test msg: ", msg);
43
- return "hello! Current timestamp is: " + Date.now()
44
- }
45
- })
46
-
47
- liveDataServer.publish({
48
- "test-subscription": async () => {
49
- return new LiveCursor(liveMongoConnection, "test-collection", { category: "apples" });
50
- }
51
- })
52
-
53
- ```
54
-
55
- `liveDataServer.methods` and `liveDataServer.publish` have exactly same interface as [Meteor.methods](https://docs.meteor.com/api/methods.html#Meteor-methods) and [Meteor.publish](https://docs.meteor.com/api/pubsub.html#Meteor-publish) respectively, notice however that when publishing subscriptions, you must use `LiveCursor` rather than a normal MongoDB cursor.
56
-
57
- ### Important notes
58
-
59
- - The project is in alpha. Use at your own risk.
60
- - Neither method context nor subscription context have the `unblock` method anymore (because this package doesn't use Fibers)
61
- - Meteor syntax for MongoDB queries is not supported. Please always use MongoDB Node.js driver syntax. For example, instead of
62
- ```ts
63
- const doc = myCollection.findOne(id);
64
- ```
65
- use
66
- ```ts
67
- const doc = await myCollection.findOne({ _id: id });
68
- ```
69
- - Neither MongoDB.ObjectId nor it's Meteor.js alternative is supported at the moment. String ids only.
1
+ MongoDB Live Data Server
2
+ ========================
3
+
4
+ This project is essentially a MongoDB live data driver (based either on polling or on Oplog tailing) combined with a DDP server, extracted
5
+ out of [Meteor](https://github.com/meteor/meteor), with **Fibers** and **underscore** dependencies removed and code converted to Typescript.
6
+
7
+ Live data is one of the root concepts of Meteor. Data is served via WebSockets via the DDP protocol and updated automatically whenever something changes in the database. Also, calling server methods via WebSocket is supported.
8
+
9
+ Using Meteor locks you into the Meteor ecosystem, which has some problems (mostly for historical reasons). Using live data as a separate npm package might be preferable in many scenarios. Also, people who are trying to migrate from Meteor, might find this package useful as an intermediate step.
10
+
11
+ ### Installation
12
+
13
+ ```
14
+ npm i mongodb-livedata-server
15
+ ```
16
+
17
+ ### Usage
18
+
19
+ As a most common example, this is how you can use livedata with Express.js:
20
+
21
+ ```js
22
+ const { DDPServer, LiveCursor, LiveMongoConnection } = require('mongodb-livedata-server')
23
+ const express = require('express')
24
+ const app = express()
25
+ const port = 3000
26
+
27
+ app.get('/', (req, res) => {
28
+ res.send('Hello World!')
29
+ })
30
+
31
+ const httpServer = app.listen(port, () => {
32
+ console.log(`Example app listening on port ${port}`)
33
+ })
34
+
35
+ const liveMongoConnection = new LiveMongoConnection(process.env.MONGO_URL, {
36
+ oplogUrl: process.env.MONGO_OPLOG_URL
37
+ });
38
+ const liveDataServer = new DDPServer({}, httpServer);
39
+
40
+ liveDataServer.methods({
41
+ "test-method": async (msg) => {
42
+ console.log("Test msg: ", msg);
43
+ return "hello! Current timestamp is: " + Date.now()
44
+ }
45
+ })
46
+
47
+ liveDataServer.publish({
48
+ "test-subscription": async () => {
49
+ return new LiveCursor(liveMongoConnection, "test-collection", { category: "apples" });
50
+ }
51
+ })
52
+
53
+ ```
54
+
55
+ `liveDataServer.methods` and `liveDataServer.publish` have exactly same interface as [Meteor.methods](https://docs.meteor.com/api/methods.html#Meteor-methods) and [Meteor.publish](https://docs.meteor.com/api/pubsub.html#Meteor-publish) respectively, notice however that when publishing subscriptions, you must use `LiveCursor` rather than a normal MongoDB cursor.
56
+
57
+ ### Important notes
58
+
59
+ - The project is in alpha. Use at your own risk.
60
+ - Neither method context nor subscription context have the `unblock` method anymore (because this package doesn't use Fibers)
61
+ - Meteor syntax for MongoDB queries is not supported. Please always use MongoDB Node.js driver syntax. For example, instead of
62
+ ```ts
63
+ const doc = myCollection.findOne(id);
64
+ ```
65
+ use
66
+ ```ts
67
+ const doc = await myCollection.findOne({ _id: id });
68
+ ```
69
+ - Neither MongoDB.ObjectId nor it's Meteor.js alternative is supported at the moment. String ids only.
70
+ - Starting from 0.1.0, this library extends DDP with `init` message, which is used to avoid initial spam of `added` messages.
@@ -18,12 +18,12 @@ export declare class MaxHeap {
18
18
  _swap(idxA: any, idxB: any): void;
19
19
  get(id: any): any;
20
20
  set(id: any, value: any): void;
21
- remove(id: any): void;
21
+ delete(id: any): void;
22
22
  has(id: any): any;
23
23
  empty(): boolean;
24
24
  clear(): void;
25
25
  forEach(iterator: any): void;
26
- size(): number;
26
+ get size(): number;
27
27
  setDefault(id: any, def: any): any;
28
28
  clone(): MaxHeap;
29
29
  maxElementId(): any;
@@ -51,14 +51,14 @@ class MaxHeap {
51
51
  }
52
52
  }
53
53
  _downHeap(idx) {
54
- while (leftChildIdx(idx) < this.size()) {
54
+ while (leftChildIdx(idx) < this.size) {
55
55
  const left = leftChildIdx(idx);
56
56
  const right = rightChildIdx(idx);
57
57
  let largest = idx;
58
- if (left < this.size()) {
58
+ if (left < this.size) {
59
59
  largest = this._maxIndex(largest, left);
60
60
  }
61
- if (right < this.size()) {
61
+ if (right < this.size) {
62
62
  largest = this._maxIndex(largest, right);
63
63
  }
64
64
  if (largest === idx) {
@@ -121,7 +121,7 @@ class MaxHeap {
121
121
  this._upHeap(this._heap.length - 1);
122
122
  }
123
123
  }
124
- remove(id) {
124
+ delete(id) {
125
125
  if (this.has(id)) {
126
126
  const last = this._heap.length - 1;
127
127
  const idx = this._heapIdx.get(id);
@@ -143,7 +143,7 @@ class MaxHeap {
143
143
  return this._heapIdx.has(id);
144
144
  }
145
145
  empty() {
146
- return !this.size();
146
+ return this.size === 0;
147
147
  }
148
148
  clear() {
149
149
  this._heap = [];
@@ -153,7 +153,7 @@ class MaxHeap {
153
153
  forEach(iterator) {
154
154
  this._heap.forEach(obj => iterator(obj.value, obj.id));
155
155
  }
156
- size() {
156
+ get size() {
157
157
  return this._heap.length;
158
158
  }
159
159
  setDefault(id, def) {
@@ -168,7 +168,7 @@ class MaxHeap {
168
168
  return clone;
169
169
  }
170
170
  maxElementId() {
171
- return this.size() ? this._heap[0].id : null;
171
+ return this.size > 0 ? this._heap[0].id : null;
172
172
  }
173
173
  _selfCheck() {
174
174
  for (let i = 1; i < this._heap.length; i++) {
@@ -3,7 +3,7 @@ export declare class MinMaxHeap extends MaxHeap {
3
3
  private _minHeap;
4
4
  constructor(comparator: any, options?: MaxHeapOptions);
5
5
  set(id: string, value: any): void;
6
- remove(id: string): void;
6
+ delete(id: string): void;
7
7
  clear(): void;
8
8
  setDefault(id: string, def: any): any;
9
9
  clone(): MinMaxHeap;
@@ -24,9 +24,9 @@ class MinMaxHeap extends max_heap_1.MaxHeap {
24
24
  super.set(id, value);
25
25
  this._minHeap.set(id, value);
26
26
  }
27
- remove(id) {
28
- super.remove(id);
29
- this._minHeap.remove(id);
27
+ delete(id) {
28
+ super.delete(id);
29
+ this._minHeap.delete(id);
30
30
  }
31
31
  clear() {
32
32
  super.clear();
@@ -3,6 +3,7 @@ import { MethodInvocation } from "./method-invocation";
3
3
  import { StreamServerSocket } from "./stream_server";
4
4
  import { DDPSession, SessionConnectionHandle } from "./session";
5
5
  import { Server } from "http";
6
+ import { Hook } from "../callback-hook/hook";
6
7
  import { Subscription } from "./subscription";
7
8
  export declare const DDP: {
8
9
  _CurrentPublicationInvocation: Subscription;
@@ -17,9 +18,9 @@ interface PublicationStrategy {
17
18
  export declare class DDPServer {
18
19
  private options;
19
20
  private onConnectionHook;
20
- private onMessageHook;
21
+ onMessageHook: Hook;
21
22
  private publish_handlers;
22
- private universal_publish_handlers;
23
+ universal_publish_handlers: any[];
23
24
  private method_handlers;
24
25
  private _publicationStrategies;
25
26
  private sessions;
@@ -370,6 +370,7 @@ function wrapInternalException(exception, context) {
370
370
  if (exception.sanitizedError.isClientSafe)
371
371
  return exception.sanitizedError;
372
372
  }
373
+ console.error("Error " + context + ":", exception);
373
374
  return ddpError(500, "Internal server error");
374
375
  }
375
376
  exports.wrapInternalException = wrapInternalException;
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SessionCollectionView = void 0;
4
4
  const diff_1 = require("../diff-sequence/diff");
5
5
  const ejson_1 = require("../ejson/ejson");
6
- const id_map_1 = require("../id-map/id_map");
7
6
  const session_document_view_1 = require("./session-document-view");
8
7
  /**
9
8
  * Represents a client's view of a single collection
@@ -15,10 +14,10 @@ class SessionCollectionView {
15
14
  constructor(collectionName, callbacks) {
16
15
  this.collectionName = collectionName;
17
16
  this.callbacks = callbacks;
18
- this.documents = new id_map_1.IdMap();
17
+ this.documents = new Map();
19
18
  }
20
19
  isEmpty() {
21
- return this.documents.empty();
20
+ return this.documents.size === 0;
22
21
  }
23
22
  diff(previous) {
24
23
  diff_1.DiffSequence.diffMaps(previous.documents, this.documents, {
@@ -91,7 +90,7 @@ class SessionCollectionView {
91
90
  if (docView.existsIn.size === 0) {
92
91
  // it is gone from everyone
93
92
  self.callbacks.removed(self.collectionName, id);
94
- self.documents.remove(id);
93
+ self.documents.delete(id);
95
94
  }
96
95
  else {
97
96
  var changed = {};
@@ -1,7 +1,9 @@
1
1
  import DoubleEndedQueue from "double-ended-queue";
2
2
  import { StreamServerSocket } from "./stream_server";
3
3
  import { DDPServer } from "./livedata_server";
4
+ import { SessionCollectionView } from "./session-collection-view";
4
5
  import { SubscriptionHandle } from "./subscription";
6
+ import { OrderedDict } from "../ordered-dict/ordered_dict";
5
7
  export interface SessionConnectionHandle {
6
8
  id: string;
7
9
  close: Function;
@@ -20,7 +22,7 @@ interface DDPMessage {
20
22
  }
21
23
  export declare class DDPSession {
22
24
  id: string;
23
- server: any;
25
+ server: DDPServer;
24
26
  inQueue: DoubleEndedQueue<any>;
25
27
  userId: string | null;
26
28
  connectionHandle: SessionConnectionHandle;
@@ -41,6 +43,7 @@ export declare class DDPSession {
41
43
  constructor(server: DDPServer, version: string, socket: StreamServerSocket, options: any);
42
44
  sendReady(subscriptionIds: string[]): void;
43
45
  _canSend(collectionName: any): boolean;
46
+ sendInitialAdds(collectionName: string, docs: Map<string, any> | OrderedDict): void;
44
47
  sendAdded(collectionName: string, id: string, fields: Record<string, any>): void;
45
48
  sendChanged(collectionName: string, id: string, fields: Record<string, any>): void;
46
49
  sendRemoved(collectionName: string, id: string): void;
@@ -49,7 +52,8 @@ export declare class DDPSession {
49
52
  changed: any;
50
53
  removed: any;
51
54
  };
52
- getCollectionView(collectionName: string): any;
55
+ getCollectionView(collectionName: string): SessionCollectionView;
56
+ initialAdds(subscriptionHandle: SubscriptionHandle, collectionName: string, docs: Map<string, any> | OrderedDict): void;
53
57
  added(subscriptionHandle: SubscriptionHandle, collectionName: string, id: string, fields: Record<string, any>): void;
54
58
  removed(subscriptionHandle: SubscriptionHandle, collectionName: string, id: string): void;
55
59
  changed(subscriptionHandle: SubscriptionHandle, collectionName: string, id: string, fields: Record<string, any>): void;
@@ -14,7 +14,6 @@ const utils_1 = require("./utils");
14
14
  const diff_1 = require("../diff-sequence/diff");
15
15
  const session_collection_view_1 = require("./session-collection-view");
16
16
  const subscription_1 = require("./subscription");
17
- const id_map_1 = require("../id-map/id_map");
18
17
  class DDPSession {
19
18
  constructor(server, version, socket, options) {
20
19
  this.protocol_handlers = {
@@ -161,7 +160,7 @@ class DDPSession {
161
160
  self._namedSubs = new Map();
162
161
  self._universalSubs = [];
163
162
  self.userId = null;
164
- self.collectionViews = new id_map_1.IdMap();
163
+ self.collectionViews = new Map();
165
164
  // Set this to false to not send messages when collectionViews are
166
165
  // modified. This is done when rerunning subs in _setUserId and those messages
167
166
  // are calculated via a diff instead.
@@ -230,6 +229,13 @@ class DDPSession {
230
229
  _canSend(collectionName) {
231
230
  return this._isSending || !this.server.getPublicationStrategy(collectionName).useCollectionView;
232
231
  }
232
+ sendInitialAdds(collectionName, docs) {
233
+ if (this._canSend(collectionName)) {
234
+ const items = [];
235
+ docs.forEach(doc => items.push(doc));
236
+ this.send({ msg: "init", collection: collectionName, items });
237
+ }
238
+ }
233
239
  sendAdded(collectionName, id, fields) {
234
240
  if (this._canSend(collectionName))
235
241
  this.send({ msg: "added", collection: collectionName, id, fields });
@@ -266,6 +272,15 @@ class DDPSession {
266
272
  }
267
273
  return ret;
268
274
  }
275
+ initialAdds(subscriptionHandle, collectionName, docs) {
276
+ if (this.server.getPublicationStrategy(collectionName).useCollectionView) {
277
+ const view = this.getCollectionView(collectionName);
278
+ docs.forEach((doc, id) => view.added(subscriptionHandle, id, doc));
279
+ }
280
+ else {
281
+ this.sendInitialAdds(collectionName, docs);
282
+ }
283
+ }
269
284
  added(subscriptionHandle, collectionName, id, fields) {
270
285
  if (this.server.getPublicationStrategy(collectionName).useCollectionView) {
271
286
  const view = this.getCollectionView(collectionName);
@@ -280,7 +295,7 @@ class DDPSession {
280
295
  const view = this.getCollectionView(collectionName);
281
296
  view.removed(subscriptionHandle, id);
282
297
  if (view.isEmpty()) {
283
- this.collectionViews.remove(collectionName);
298
+ this.collectionViews.delete(collectionName);
284
299
  }
285
300
  }
286
301
  else {
@@ -316,7 +331,7 @@ class DDPSession {
316
331
  return;
317
332
  // Drop the merge box data immediately.
318
333
  self.inQueue = null;
319
- self.collectionViews = new id_map_1.IdMap();
334
+ self.collectionViews = new Map();
320
335
  if (self.heartbeat) {
321
336
  self.heartbeat.stop();
322
337
  self.heartbeat = null;
@@ -457,7 +472,7 @@ class DDPSession {
457
472
  // update the userId.
458
473
  self._isSending = false;
459
474
  var beforeCVs = self.collectionViews;
460
- self.collectionViews = new id_map_1.IdMap();
475
+ self.collectionViews = new Map();
461
476
  self.userId = userId;
462
477
  // _setUserId is normally called from a Meteor method with
463
478
  // DDP._CurrentMethodInvocation set. But DDP._CurrentMethodInvocation is not
@@ -48,7 +48,7 @@ class StreamServer {
48
48
  }
49
49
  else {
50
50
  serverOptions.faye_server_options = {
51
- extensions: [permessage_deflate_1.default.configure({})]
51
+ extensions: [permessage_deflate_1.default.configure({ maxWindowBits: 11, memLevel: 4 })]
52
52
  };
53
53
  }
54
54
  this.server = sockjs_1.default.createServer(serverOptions);
@@ -1,3 +1,4 @@
1
+ import { OrderedDict } from "../ordered-dict/ordered_dict";
1
2
  import { AsyncFunction } from "../types";
2
3
  import { DDPSession, SessionConnectionHandle } from "./session";
3
4
  export type SubscriptionHandle = `N${string}` | `U${string}`;
@@ -53,6 +54,7 @@ export declare class Subscription {
53
54
  */
54
55
  onStop(func: () => void): void;
55
56
  _isDeactivated(): boolean;
57
+ initialAdds(collectionName: string, documents: Map<string, any> | OrderedDict): void;
56
58
  /**
57
59
  * @summary Call inside the publish function. Informs the subscriber that a document has been added to the record set.
58
60
  * @locus Server
@@ -276,6 +276,19 @@ class Subscription {
276
276
  var self = this;
277
277
  return self._deactivated || self._session.inQueue === null;
278
278
  }
279
+ initialAdds(collectionName, documents) {
280
+ if (this._isDeactivated())
281
+ return;
282
+ if (this._session.server.getPublicationStrategy(collectionName).doAccountingForCollection) {
283
+ let ids = this._documents.get(collectionName);
284
+ if (ids == null) {
285
+ ids = new Set();
286
+ this._documents.set(collectionName, ids);
287
+ }
288
+ documents.forEach((_doc, id) => ids.add(id));
289
+ }
290
+ this._session.initialAdds(this._subscriptionHandle, collectionName, documents);
291
+ }
279
292
  /**
280
293
  * @summary Call inside the publish function. Informs the subscriber that a document has been added to the record set.
281
294
  * @locus Server
@@ -1,4 +1,3 @@
1
- import { IdMap } from "../id-map/id_map";
2
1
  interface DiffCallbacks {
3
2
  both: (key: string, left: any, right: any) => void;
4
3
  leftOnly: (key: string, value: any) => void;
@@ -6,11 +5,11 @@ interface DiffCallbacks {
6
5
  }
7
6
  interface DiffSequence {
8
7
  diffQueryChanges(ordered: boolean, oldResults: any[], newResults: any[], observer: any, options?: any): any;
9
- diffQueryChanges(ordered: boolean, oldResults: IdMap, newResults: IdMap, observer: any, options?: any): any;
8
+ diffQueryChanges(ordered: boolean, oldResults: Map<string, any>, newResults: Map<string, any>, observer: any, options?: any): any;
10
9
  diffQueryUnorderedChanges(oldResults: any, newResults: any, observer: any, options?: any): any;
11
10
  diffQueryOrderedChanges(old_results: any, new_results: any, observer: any, options?: any): any;
12
11
  diffObjects(left: Record<string, any>, right: Record<string, any>, callbacks: DiffCallbacks): any;
13
- diffMaps(left: IdMap, right: IdMap, callbacks: DiffCallbacks): any;
12
+ diffMaps(left: Map<string, any>, right: Map<string, any>, callbacks: DiffCallbacks): any;
14
13
  makeChangedFields(newDoc: Record<string, any>, oldDoc: Record<string, any>): any;
15
14
  applyChanges(doc: Record<string, any>, changeFields: Record<string, any>): void;
16
15
  }
@@ -1,8 +1,8 @@
1
- import { IdMap } from "../id-map/id_map";
2
1
  import { OrderedDict } from "../ordered-dict/ordered_dict";
3
2
  export declare class _CachingChangeObserver {
4
- docs: OrderedDict | IdMap;
3
+ docs: OrderedDict | Map<string, any>;
5
4
  applyChange: {
5
+ initialAdds?: (docs: OrderedDict | Map<string, any>) => void;
6
6
  added?: (id: string, fields: any) => void;
7
7
  changed?: (id: string, fields: any) => void;
8
8
  removed?: (id: string) => void;
@@ -11,7 +11,6 @@ export declare class _CachingChangeObserver {
11
11
  };
12
12
  private ordered;
13
13
  constructor(options?: {
14
- callbacks?: any;
15
14
  ordered?: boolean;
16
15
  });
17
16
  }
@@ -8,27 +8,11 @@
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports._CachingChangeObserver = void 0;
10
10
  const diff_1 = require("../diff-sequence/diff");
11
- const ejson_1 = require("../ejson/ejson");
12
- const id_map_1 = require("../id-map/id_map");
13
11
  const ordered_dict_1 = require("../ordered-dict/ordered_dict");
14
12
  // available as `this` to those callbacks.
15
13
  class _CachingChangeObserver {
16
14
  constructor(options = {}) {
17
- const orderedFromCallbacks = (options.callbacks &&
18
- !!(options.callbacks.addedBefore || options.callbacks.movedBefore));
19
- if (options.hasOwnProperty('ordered')) {
20
- this.ordered = options.ordered;
21
- if (options.callbacks && options.ordered !== orderedFromCallbacks) {
22
- throw Error('ordered option doesn\'t match callbacks');
23
- }
24
- }
25
- else if (options.callbacks) {
26
- this.ordered = orderedFromCallbacks;
27
- }
28
- else {
29
- throw Error('must provide ordered or callbacks');
30
- }
31
- const callbacks = options.callbacks || {};
15
+ this.ordered = options.ordered || false;
32
16
  if (this.ordered) {
33
17
  this.docs = new ordered_dict_1.OrderedDict();
34
18
  this.applyChange = {
@@ -36,13 +20,6 @@ class _CachingChangeObserver {
36
20
  // Take a shallow copy since the top-level properties can be changed
37
21
  const doc = Object.assign({}, fields);
38
22
  doc._id = id;
39
- if (callbacks.addedBefore) {
40
- callbacks.addedBefore.call(this, id, (0, ejson_1.clone)(fields), before);
41
- }
42
- // This line triggers if we provide added with movedBefore.
43
- if (callbacks.added) {
44
- callbacks.added.call(this, id, (0, ejson_1.clone)(fields));
45
- }
46
23
  // XXX could `before` be a falsy ID? Technically
47
24
  // idStringify seems to allow for them -- though
48
25
  // OrderedDict won't call stringify on a falsy arg.
@@ -50,27 +27,24 @@ class _CachingChangeObserver {
50
27
  },
51
28
  movedBefore: (id, before) => {
52
29
  const doc = this.docs.get(id);
53
- if (callbacks.movedBefore) {
54
- callbacks.movedBefore.call(this, id, before);
55
- }
56
30
  this.docs.moveBefore(id, before || null);
57
31
  },
58
32
  };
59
33
  }
60
34
  else {
61
- this.docs = new id_map_1.IdMap();
35
+ this.docs = new Map();
62
36
  this.applyChange = {
63
37
  added: (id, fields) => {
64
38
  // Take a shallow copy since the top-level properties can be changed
65
39
  const doc = Object.assign({}, fields);
66
- if (callbacks.added) {
67
- callbacks.added.call(this, id, (0, ejson_1.clone)(fields));
68
- }
69
40
  doc._id = id;
70
41
  this.docs.set(id, doc);
71
42
  },
72
43
  };
73
44
  }
45
+ this.applyChange.initialAdds = (docs) => {
46
+ this.docs = docs;
47
+ };
74
48
  // The methods in _IdMap and OrderedDict used by these callbacks are
75
49
  // identical.
76
50
  this.applyChange.changed = (id, fields) => {
@@ -78,16 +52,10 @@ class _CachingChangeObserver {
78
52
  if (!doc) {
79
53
  throw new Error(`Unknown id for changed: ${id}`);
80
54
  }
81
- if (callbacks.changed) {
82
- callbacks.changed.call(this, id, (0, ejson_1.clone)(fields));
83
- }
84
55
  diff_1.DiffSequence.applyChanges(doc, fields);
85
56
  };
86
57
  this.applyChange.removed = id => {
87
- if (callbacks.removed) {
88
- callbacks.removed.call(this, id);
89
- }
90
- this.docs.remove(id);
58
+ this.docs.delete(id);
91
59
  };
92
60
  }
93
61
  }
@@ -19,6 +19,9 @@ class LiveCursor {
19
19
  }
20
20
  async _publishCursor(sub) {
21
21
  const observeHandle = await this.mongo._observeChanges(this.cursorDescription, false, {
22
+ initialAdds: (docs) => {
23
+ sub.initialAdds(this.cursorDescription.collectionName, docs);
24
+ },
22
25
  added: (id, fields) => {
23
26
  sub.added(this.cursorDescription.collectionName, id, fields);
24
27
  },
@@ -1,9 +1,11 @@
1
+ import { OrderedDict } from "../ordered-dict/ordered_dict";
1
2
  export interface ObserveCallbacks {
2
3
  added: (id: string, fields: Record<string, any>) => void;
3
4
  changed: (id: string, fields: Record<string, any>) => void;
4
5
  removed: (id: string) => void;
5
6
  addedBefore?: (id: string, fields: Record<string, any>, before?: any) => void;
6
7
  movedBefore?: (id: string, fields: Record<string, any>, before?: any) => void;
8
+ initialAdds: (docs: Map<string, any> | OrderedDict) => void;
7
9
  _testOnlyPollCallback?: any;
8
10
  }
9
11
  export declare class ObserveMultiplexer {
@@ -27,12 +29,12 @@ export declare class ObserveMultiplexer {
27
29
  callbackNames(): string[];
28
30
  _ready(): boolean;
29
31
  _applyCallback(callbackName: string, args: any): Promise<void>;
30
- _sendAdds(handle: ObserveHandle): void;
31
32
  }
32
33
  export declare class ObserveHandle {
33
34
  private _multiplexer;
34
35
  nonMutatingCallbacks: boolean;
35
36
  _id: number;
37
+ _initialAdds: ObserveCallbacks["initialAdds"];
36
38
  _addedBefore: ObserveCallbacks["addedBefore"];
37
39
  _movedBefore: ObserveCallbacks["movedBefore"];
38
40
  _added: ObserveCallbacks["added"];