mongodb-livedata-server 0.1.3 → 0.1.4
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/dist/livedata_server.d.ts +4 -4
- package/dist/livedata_server.js +11 -11
- package/dist/meteor/binary-heap/max_heap.d.ts +31 -31
- package/dist/meteor/binary-heap/max_heap.js +186 -186
- package/dist/meteor/binary-heap/min_heap.d.ts +6 -6
- package/dist/meteor/binary-heap/min_heap.js +17 -17
- package/dist/meteor/binary-heap/min_max_heap.d.ts +11 -11
- package/dist/meteor/binary-heap/min_max_heap.js +48 -48
- package/dist/meteor/callback-hook/hook.d.ts +11 -11
- package/dist/meteor/callback-hook/hook.js +78 -78
- package/dist/meteor/ddp/crossbar.d.ts +15 -15
- package/dist/meteor/ddp/crossbar.js +136 -136
- package/dist/meteor/ddp/heartbeat.d.ts +19 -19
- package/dist/meteor/ddp/heartbeat.js +77 -77
- package/dist/meteor/ddp/livedata_server.d.ts +141 -142
- package/dist/meteor/ddp/livedata_server.js +403 -403
- package/dist/meteor/ddp/method-invocation.d.ts +35 -35
- package/dist/meteor/ddp/method-invocation.js +72 -72
- package/dist/meteor/ddp/random-stream.d.ts +8 -8
- package/dist/meteor/ddp/random-stream.js +100 -100
- package/dist/meteor/ddp/session-collection-view.d.ts +20 -20
- package/dist/meteor/ddp/session-collection-view.js +106 -106
- package/dist/meteor/ddp/session-document-view.d.ts +8 -8
- package/dist/meteor/ddp/session-document-view.js +82 -82
- package/dist/meteor/ddp/session.d.ts +75 -75
- package/dist/meteor/ddp/session.js +590 -590
- package/dist/meteor/ddp/stream_server.d.ts +20 -21
- package/dist/meteor/ddp/stream_server.js +181 -181
- package/dist/meteor/ddp/subscription.d.ts +94 -94
- package/dist/meteor/ddp/subscription.js +370 -370
- package/dist/meteor/ddp/utils.d.ts +8 -8
- package/dist/meteor/ddp/utils.js +104 -104
- package/dist/meteor/ddp/writefence.d.ts +20 -20
- package/dist/meteor/ddp/writefence.js +111 -111
- package/dist/meteor/diff-sequence/diff.d.ts +17 -17
- package/dist/meteor/diff-sequence/diff.js +257 -257
- package/dist/meteor/ejson/ejson.d.ts +82 -82
- package/dist/meteor/ejson/ejson.js +568 -569
- package/dist/meteor/ejson/stringify.d.ts +2 -2
- package/dist/meteor/ejson/stringify.js +119 -119
- package/dist/meteor/ejson/utils.d.ts +12 -12
- package/dist/meteor/ejson/utils.js +42 -42
- package/dist/meteor/mongo/caching_change_observer.d.ts +16 -16
- package/dist/meteor/mongo/caching_change_observer.js +63 -63
- package/dist/meteor/mongo/doc_fetcher.d.ts +7 -7
- package/dist/meteor/mongo/doc_fetcher.js +53 -53
- package/dist/meteor/mongo/geojson_utils.d.ts +3 -3
- package/dist/meteor/mongo/geojson_utils.js +40 -41
- package/dist/meteor/mongo/live_connection.d.ts +28 -28
- package/dist/meteor/mongo/live_connection.js +264 -264
- package/dist/meteor/mongo/live_cursor.d.ts +25 -25
- package/dist/meteor/mongo/live_cursor.js +60 -60
- package/dist/meteor/mongo/minimongo_common.d.ts +84 -84
- package/dist/meteor/mongo/minimongo_common.js +1998 -1998
- package/dist/meteor/mongo/minimongo_matcher.d.ts +23 -23
- package/dist/meteor/mongo/minimongo_matcher.js +283 -283
- package/dist/meteor/mongo/minimongo_sorter.d.ts +16 -16
- package/dist/meteor/mongo/minimongo_sorter.js +268 -268
- package/dist/meteor/mongo/observe_driver_utils.d.ts +9 -9
- package/dist/meteor/mongo/observe_driver_utils.js +72 -73
- package/dist/meteor/mongo/observe_multiplexer.d.ts +46 -46
- package/dist/meteor/mongo/observe_multiplexer.js +203 -203
- package/dist/meteor/mongo/oplog-observe-driver.d.ts +68 -68
- package/dist/meteor/mongo/oplog-observe-driver.js +918 -918
- package/dist/meteor/mongo/oplog_tailing.d.ts +35 -35
- package/dist/meteor/mongo/oplog_tailing.js +352 -352
- package/dist/meteor/mongo/oplog_v2_converter.d.ts +1 -1
- package/dist/meteor/mongo/oplog_v2_converter.js +125 -126
- package/dist/meteor/mongo/polling_observe_driver.d.ts +30 -30
- package/dist/meteor/mongo/polling_observe_driver.js +216 -221
- package/dist/meteor/mongo/synchronous-cursor.d.ts +17 -17
- package/dist/meteor/mongo/synchronous-cursor.js +261 -261
- package/dist/meteor/mongo/synchronous-queue.d.ts +13 -13
- package/dist/meteor/mongo/synchronous-queue.js +110 -110
- package/dist/meteor/ordered-dict/ordered_dict.d.ts +31 -31
- package/dist/meteor/ordered-dict/ordered_dict.js +198 -198
- package/dist/meteor/random/AbstractRandomGenerator.d.ts +42 -42
- package/dist/meteor/random/AbstractRandomGenerator.js +92 -92
- package/dist/meteor/random/AleaRandomGenerator.d.ts +13 -13
- package/dist/meteor/random/AleaRandomGenerator.js +90 -90
- package/dist/meteor/random/NodeRandomGenerator.d.ts +16 -16
- package/dist/meteor/random/NodeRandomGenerator.js +42 -42
- package/dist/meteor/random/createAleaGenerator.d.ts +2 -2
- package/dist/meteor/random/createAleaGenerator.js +32 -32
- package/dist/meteor/random/createRandom.d.ts +1 -1
- package/dist/meteor/random/createRandom.js +22 -22
- package/dist/meteor/random/main.d.ts +1 -1
- package/dist/meteor/random/main.js +12 -12
- package/dist/meteor/types.d.ts +1 -1
- package/dist/meteor/types.js +2 -2
- package/package.json +5 -5
|
@@ -1,370 +1,370 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
// Ctor for a sub handle: the input to each publish function
|
|
3
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.Subscription = void 0;
|
|
5
|
-
const ejson_1 = require("../ejson/ejson");
|
|
6
|
-
const main_1 = require("../random/main");
|
|
7
|
-
const livedata_server_1 = require("./livedata_server");
|
|
8
|
-
// Instance name is this because it's usually referred to as this inside a
|
|
9
|
-
// publish
|
|
10
|
-
/**
|
|
11
|
-
* @summary The server's side of a subscription
|
|
12
|
-
* @class Subscription
|
|
13
|
-
* @instanceName this
|
|
14
|
-
* @showInstanceName true
|
|
15
|
-
*/
|
|
16
|
-
class Subscription {
|
|
17
|
-
constructor(_session, _handler, _subscriptionId, _params = [], _name) {
|
|
18
|
-
this._session = _session;
|
|
19
|
-
this._handler = _handler;
|
|
20
|
-
this._subscriptionId = _subscriptionId;
|
|
21
|
-
this._params = _params;
|
|
22
|
-
this._name = _name;
|
|
23
|
-
// Has _deactivate been called?
|
|
24
|
-
this._deactivated = false;
|
|
25
|
-
// Stop callbacks to g/c this sub. called w/ zero arguments.
|
|
26
|
-
this._stopCallbacks = [];
|
|
27
|
-
// The set of (collection, documentid) that this subscription has
|
|
28
|
-
// an opinion about.
|
|
29
|
-
this._documents = new Map();
|
|
30
|
-
// Remember if we are ready.
|
|
31
|
-
this._ready = false;
|
|
32
|
-
// For now, the id filter is going to default to
|
|
33
|
-
// the to/from DDP methods on MongoID, to
|
|
34
|
-
// specifically deal with mongo/minimongo ObjectIds.
|
|
35
|
-
// Later, you will be able to make this be "raw"
|
|
36
|
-
// if you want to publish a collection that you know
|
|
37
|
-
// just has strings for keys and no funny business, to
|
|
38
|
-
// a DDP consumer that isn't minimongo.
|
|
39
|
-
this._idFilter = {
|
|
40
|
-
idStringify: id => id /*MongoID.idStringify*/,
|
|
41
|
-
idParse: id => id /*MongoID.idParse*/
|
|
42
|
-
};
|
|
43
|
-
/**
|
|
44
|
-
* @summary Access inside the publish function. The incoming [connection](#meteor_onconnection) for this subscription.
|
|
45
|
-
* @locus Server
|
|
46
|
-
* @name connection
|
|
47
|
-
* @memberOf Subscription
|
|
48
|
-
* @instance
|
|
49
|
-
*/
|
|
50
|
-
this.connection = _session.connectionHandle; // public API object
|
|
51
|
-
// Only named subscriptions have IDs, but we need some sort of string
|
|
52
|
-
// internally to keep track of all subscriptions inside
|
|
53
|
-
// SessionDocumentViews. We use this subscriptionHandle for that.
|
|
54
|
-
if (this._subscriptionId) {
|
|
55
|
-
this._subscriptionHandle = `N${this._subscriptionId}`;
|
|
56
|
-
}
|
|
57
|
-
else {
|
|
58
|
-
this._subscriptionHandle = `U${main_1.Random.id()}`;
|
|
59
|
-
}
|
|
60
|
-
// Part of the public API: the user of this sub.
|
|
61
|
-
/**
|
|
62
|
-
* @summary Access inside the publish function. The id of the logged-in user, or `null` if no user is logged in.
|
|
63
|
-
* @locus Server
|
|
64
|
-
* @memberOf Subscription
|
|
65
|
-
* @name userId
|
|
66
|
-
* @instance
|
|
67
|
-
*/
|
|
68
|
-
this.userId = this._session.userId;
|
|
69
|
-
}
|
|
70
|
-
;
|
|
71
|
-
async _runHandler() {
|
|
72
|
-
// XXX should we unblock() here? Either before running the publish
|
|
73
|
-
// function, or before running _publishCursor.
|
|
74
|
-
//
|
|
75
|
-
// Right now, each publish function blocks all future publishes and
|
|
76
|
-
// methods waiting on data from Mongo (or whatever else the function
|
|
77
|
-
// blocks on). This probably slows page load in common cases.
|
|
78
|
-
let resultOrThenable = null;
|
|
79
|
-
const oldInvocation = livedata_server_1.DDP._CurrentPublicationInvocation;
|
|
80
|
-
try {
|
|
81
|
-
livedata_server_1.DDP._CurrentPublicationInvocation = this;
|
|
82
|
-
resultOrThenable = (0, livedata_server_1.maybeAuditArgumentChecks)(this._handler, this, (0, ejson_1.clone)(this._params),
|
|
83
|
-
// It's OK that this would look weird for universal subscriptions,
|
|
84
|
-
// because they have no arguments so there can never be an
|
|
85
|
-
// audit-argument-checks failure.
|
|
86
|
-
"publisher '" + this._name + "'");
|
|
87
|
-
}
|
|
88
|
-
catch (e) {
|
|
89
|
-
this.error(e);
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
finally {
|
|
93
|
-
livedata_server_1.DDP._CurrentPublicationInvocation = oldInvocation;
|
|
94
|
-
}
|
|
95
|
-
// Did the handler call this.error or this.stop?
|
|
96
|
-
if (this._isDeactivated())
|
|
97
|
-
return;
|
|
98
|
-
// Both conventional and async publish handler functions are supported.
|
|
99
|
-
// If an object is returned with a then() function, it is either a promise
|
|
100
|
-
// or thenable and will be resolved asynchronously.
|
|
101
|
-
const isThenable = resultOrThenable && typeof resultOrThenable.then === 'function';
|
|
102
|
-
if (isThenable) {
|
|
103
|
-
let result;
|
|
104
|
-
try {
|
|
105
|
-
result = await resultOrThenable;
|
|
106
|
-
}
|
|
107
|
-
catch (e) {
|
|
108
|
-
this.error(e);
|
|
109
|
-
}
|
|
110
|
-
await this._publishHandlerResult(result);
|
|
111
|
-
}
|
|
112
|
-
else {
|
|
113
|
-
await this._publishHandlerResult(resultOrThenable);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
async _publishHandlerResult(res) {
|
|
117
|
-
// SPECIAL CASE: Instead of writing their own callbacks that invoke
|
|
118
|
-
// this.added/changed/ready/etc, the user can just return a collection
|
|
119
|
-
// cursor or array of cursors from the publish function; we call their
|
|
120
|
-
// _publishCursor method which starts observing the cursor and publishes the
|
|
121
|
-
// results. Note that _publishCursor does NOT call ready().
|
|
122
|
-
//
|
|
123
|
-
// XXX This uses an undocumented interface which only the Mongo cursor
|
|
124
|
-
// interface publishes. Should we make this interface public and encourage
|
|
125
|
-
// users to implement it themselves? Arguably, it's unnecessary; users can
|
|
126
|
-
// already write their own functions like
|
|
127
|
-
// var publishMyReactiveThingy = function (name, handler) {
|
|
128
|
-
// Meteor.publish(name, function () {
|
|
129
|
-
// var reactiveThingy = handler();
|
|
130
|
-
// reactiveThingy.publishMe();
|
|
131
|
-
// });
|
|
132
|
-
// };
|
|
133
|
-
var self = this;
|
|
134
|
-
var isCursor = function (c) {
|
|
135
|
-
return c && c._publishCursor;
|
|
136
|
-
};
|
|
137
|
-
if (isCursor(res)) {
|
|
138
|
-
try {
|
|
139
|
-
await res._publishCursor(self);
|
|
140
|
-
}
|
|
141
|
-
catch (e) {
|
|
142
|
-
self.error(e);
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
// _publishCursor only returns after the initial added callbacks have run.
|
|
146
|
-
// mark subscription as ready.
|
|
147
|
-
self.ready();
|
|
148
|
-
}
|
|
149
|
-
else if (Array.isArray(res)) {
|
|
150
|
-
// Check all the elements are cursors
|
|
151
|
-
if (!res.every(isCursor)) {
|
|
152
|
-
self.error(new Error("Publish function returned an array of non-Cursors"));
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
// Find duplicate collection names
|
|
156
|
-
// XXX we should support overlapping cursors, but that would require the
|
|
157
|
-
// merge box to allow overlap within a subscription
|
|
158
|
-
var collectionNames = {};
|
|
159
|
-
for (var i = 0; i < res.length; ++i) {
|
|
160
|
-
var collectionName = res[i].cursorDescription.collectionName;
|
|
161
|
-
if (collectionNames.hasOwnProperty(collectionName)) {
|
|
162
|
-
self.error(new Error("Publish function returned multiple cursors for collection " +
|
|
163
|
-
collectionName));
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
collectionNames[collectionName] = true;
|
|
167
|
-
}
|
|
168
|
-
;
|
|
169
|
-
try {
|
|
170
|
-
for (const cur of res) {
|
|
171
|
-
await cur._publishCursor(self);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
catch (e) {
|
|
175
|
-
self.error(e);
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
self.ready();
|
|
179
|
-
}
|
|
180
|
-
else if (res) {
|
|
181
|
-
// Truthy values other than cursors or arrays are probably a
|
|
182
|
-
// user mistake (possible returning a Mongo document via, say,
|
|
183
|
-
// `coll.findOne()`).
|
|
184
|
-
self.error(new Error("Publish function can only return a Cursor or "
|
|
185
|
-
+ "an array of Cursors"));
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
// This calls all stop callbacks and prevents the handler from updating any
|
|
189
|
-
// SessionCollectionViews further. It's used when the user unsubscribes or
|
|
190
|
-
// disconnects, as well as during setUserId re-runs. It does *NOT* send
|
|
191
|
-
// removed messages for the published objects; if that is necessary, call
|
|
192
|
-
// _removeAllDocuments first.
|
|
193
|
-
_deactivate() {
|
|
194
|
-
var self = this;
|
|
195
|
-
if (self._deactivated)
|
|
196
|
-
return;
|
|
197
|
-
self._deactivated = true;
|
|
198
|
-
self._callStopCallbacks();
|
|
199
|
-
/*Package['facts-base'] && Package['facts-base'].Facts.incrementServerFact(
|
|
200
|
-
"livedata", "subscriptions", -1);*/
|
|
201
|
-
}
|
|
202
|
-
_callStopCallbacks() {
|
|
203
|
-
var self = this;
|
|
204
|
-
// Tell listeners, so they can clean up
|
|
205
|
-
var callbacks = self._stopCallbacks;
|
|
206
|
-
self._stopCallbacks = [];
|
|
207
|
-
for (const callback of callbacks) {
|
|
208
|
-
callback();
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
// Send remove messages for every document.
|
|
212
|
-
_removeAllDocuments() {
|
|
213
|
-
var self = this;
|
|
214
|
-
self._documents.forEach(function (collectionDocs, collectionName) {
|
|
215
|
-
collectionDocs.forEach(function (strId) {
|
|
216
|
-
self.removed(collectionName, self._idFilter.idParse(strId));
|
|
217
|
-
});
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
// Returns a new Subscription for the same session with the same
|
|
221
|
-
// initial creation parameters. This isn't a clone: it doesn't have
|
|
222
|
-
// the same _documents cache, stopped state or callbacks; may have a
|
|
223
|
-
// different _subscriptionHandle, and gets its userId from the
|
|
224
|
-
// session, not from this object.
|
|
225
|
-
_recreate() {
|
|
226
|
-
var self = this;
|
|
227
|
-
return new Subscription(self._session, self._handler, self._subscriptionId, self._params, self._name);
|
|
228
|
-
}
|
|
229
|
-
/**
|
|
230
|
-
* @summary Call inside the publish function. Stops this client's subscription, triggering a call on the client to the `onStop` callback passed to [`Meteor.subscribe`](#meteor_subscribe), if any. If `error` is not a [`Meteor.Error`](#meteor_error), it will be [sanitized](#meteor_error).
|
|
231
|
-
* @locus Server
|
|
232
|
-
* @param {Error} error The error to pass to the client.
|
|
233
|
-
* @instance
|
|
234
|
-
* @memberOf Subscription
|
|
235
|
-
*/
|
|
236
|
-
error(error) {
|
|
237
|
-
var self = this;
|
|
238
|
-
if (self._isDeactivated())
|
|
239
|
-
return;
|
|
240
|
-
self._session._stopSubscription(self._subscriptionId, error);
|
|
241
|
-
}
|
|
242
|
-
// Note that while our DDP client will notice that you've called stop() on the
|
|
243
|
-
// server (and clean up its _subscriptions table) we don't actually provide a
|
|
244
|
-
// mechanism for an app to notice this (the subscribe onError callback only
|
|
245
|
-
// triggers if there is an error).
|
|
246
|
-
/**
|
|
247
|
-
* @summary Call inside the publish function. Stops this client's subscription and invokes the client's `onStop` callback with no error.
|
|
248
|
-
* @locus Server
|
|
249
|
-
* @instance
|
|
250
|
-
* @memberOf Subscription
|
|
251
|
-
*/
|
|
252
|
-
stop() {
|
|
253
|
-
var self = this;
|
|
254
|
-
if (self._isDeactivated())
|
|
255
|
-
return;
|
|
256
|
-
self._session._stopSubscription(self._subscriptionId);
|
|
257
|
-
}
|
|
258
|
-
/**
|
|
259
|
-
* @summary Call inside the publish function. Registers a callback function to run when the subscription is stopped.
|
|
260
|
-
* @locus Server
|
|
261
|
-
* @memberOf Subscription
|
|
262
|
-
* @instance
|
|
263
|
-
* @param {Function} func The callback function
|
|
264
|
-
*/
|
|
265
|
-
onStop(func) {
|
|
266
|
-
var self = this;
|
|
267
|
-
if (self._isDeactivated())
|
|
268
|
-
func();
|
|
269
|
-
else
|
|
270
|
-
self._stopCallbacks.push(func);
|
|
271
|
-
}
|
|
272
|
-
// This returns true if the sub has been deactivated, *OR* if the session was
|
|
273
|
-
// destroyed but the deferred call to _deactivateAllSubscriptions hasn't
|
|
274
|
-
// happened yet.
|
|
275
|
-
_isDeactivated() {
|
|
276
|
-
var self = this;
|
|
277
|
-
return self._deactivated || self._session.inQueue === null;
|
|
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
|
-
if (this._session.version === "1a")
|
|
291
|
-
this._session.initialAdds(this._subscriptionHandle, collectionName, documents);
|
|
292
|
-
else
|
|
293
|
-
documents.forEach((doc, id) => this._session.added(this._subscriptionHandle, collectionName, id, doc));
|
|
294
|
-
}
|
|
295
|
-
/**
|
|
296
|
-
* @summary Call inside the publish function. Informs the subscriber that a document has been added to the record set.
|
|
297
|
-
* @locus Server
|
|
298
|
-
* @memberOf Subscription
|
|
299
|
-
* @instance
|
|
300
|
-
* @param {String} collection The name of the collection that contains the new document.
|
|
301
|
-
* @param {String} id The new document's ID.
|
|
302
|
-
* @param {Object} fields The fields in the new document. If `_id` is present it is ignored.
|
|
303
|
-
*/
|
|
304
|
-
added(collectionName, id, fields) {
|
|
305
|
-
if (this._isDeactivated())
|
|
306
|
-
return;
|
|
307
|
-
id = this._idFilter.idStringify(id);
|
|
308
|
-
if (this._session.server.getPublicationStrategy(collectionName).doAccountingForCollection) {
|
|
309
|
-
let ids = this._documents.get(collectionName);
|
|
310
|
-
if (ids == null) {
|
|
311
|
-
ids = new Set();
|
|
312
|
-
this._documents.set(collectionName, ids);
|
|
313
|
-
}
|
|
314
|
-
ids.add(id);
|
|
315
|
-
}
|
|
316
|
-
this._session.added(this._subscriptionHandle, collectionName, id, fields);
|
|
317
|
-
}
|
|
318
|
-
/**
|
|
319
|
-
* @summary Call inside the publish function. Informs the subscriber that a document in the record set has been modified.
|
|
320
|
-
* @locus Server
|
|
321
|
-
* @memberOf Subscription
|
|
322
|
-
* @instance
|
|
323
|
-
* @param {String} collection The name of the collection that contains the changed document.
|
|
324
|
-
* @param {String} id The changed document's ID.
|
|
325
|
-
* @param {Object} fields The fields in the document that have changed, together with their new values. If a field is not present in `fields` it was left unchanged; if it is present in `fields` and has a value of `undefined` it was removed from the document. If `_id` is present it is ignored.
|
|
326
|
-
*/
|
|
327
|
-
changed(collectionName, id, fields) {
|
|
328
|
-
if (this._isDeactivated())
|
|
329
|
-
return;
|
|
330
|
-
id = this._idFilter.idStringify(id);
|
|
331
|
-
this._session.changed(this._subscriptionHandle, collectionName, id, fields);
|
|
332
|
-
}
|
|
333
|
-
/**
|
|
334
|
-
* @summary Call inside the publish function. Informs the subscriber that a document has been removed from the record set.
|
|
335
|
-
* @locus Server
|
|
336
|
-
* @memberOf Subscription
|
|
337
|
-
* @instance
|
|
338
|
-
* @param {String} collection The name of the collection that the document has been removed from.
|
|
339
|
-
* @param {String} id The ID of the document that has been removed.
|
|
340
|
-
*/
|
|
341
|
-
removed(collectionName, id) {
|
|
342
|
-
if (this._isDeactivated())
|
|
343
|
-
return;
|
|
344
|
-
id = this._idFilter.idStringify(id);
|
|
345
|
-
if (this._session.server.getPublicationStrategy(collectionName).doAccountingForCollection) {
|
|
346
|
-
// We don't bother to delete sets of things in a collection if the
|
|
347
|
-
// collection is empty. It could break _removeAllDocuments.
|
|
348
|
-
this._documents.get(collectionName).delete(id);
|
|
349
|
-
}
|
|
350
|
-
this._session.removed(this._subscriptionHandle, collectionName, id);
|
|
351
|
-
}
|
|
352
|
-
/**
|
|
353
|
-
* @summary Call inside the publish function. Informs the subscriber that an initial, complete snapshot of the record set has been sent. This will trigger a call on the client to the `onReady` callback passed to [`Meteor.subscribe`](#meteor_subscribe), if any.
|
|
354
|
-
* @locus Server
|
|
355
|
-
* @memberOf Subscription
|
|
356
|
-
* @instance
|
|
357
|
-
*/
|
|
358
|
-
ready() {
|
|
359
|
-
var self = this;
|
|
360
|
-
if (self._isDeactivated())
|
|
361
|
-
return;
|
|
362
|
-
if (!self._subscriptionId)
|
|
363
|
-
return; // Unnecessary but ignored for universal sub
|
|
364
|
-
if (!self._ready) {
|
|
365
|
-
self._session.sendReady([self._subscriptionId]);
|
|
366
|
-
self._ready = true;
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
exports.Subscription = Subscription;
|
|
1
|
+
"use strict";
|
|
2
|
+
// Ctor for a sub handle: the input to each publish function
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.Subscription = void 0;
|
|
5
|
+
const ejson_1 = require("../ejson/ejson");
|
|
6
|
+
const main_1 = require("../random/main");
|
|
7
|
+
const livedata_server_1 = require("./livedata_server");
|
|
8
|
+
// Instance name is this because it's usually referred to as this inside a
|
|
9
|
+
// publish
|
|
10
|
+
/**
|
|
11
|
+
* @summary The server's side of a subscription
|
|
12
|
+
* @class Subscription
|
|
13
|
+
* @instanceName this
|
|
14
|
+
* @showInstanceName true
|
|
15
|
+
*/
|
|
16
|
+
class Subscription {
|
|
17
|
+
constructor(_session, _handler, _subscriptionId, _params = [], _name) {
|
|
18
|
+
this._session = _session;
|
|
19
|
+
this._handler = _handler;
|
|
20
|
+
this._subscriptionId = _subscriptionId;
|
|
21
|
+
this._params = _params;
|
|
22
|
+
this._name = _name;
|
|
23
|
+
// Has _deactivate been called?
|
|
24
|
+
this._deactivated = false;
|
|
25
|
+
// Stop callbacks to g/c this sub. called w/ zero arguments.
|
|
26
|
+
this._stopCallbacks = [];
|
|
27
|
+
// The set of (collection, documentid) that this subscription has
|
|
28
|
+
// an opinion about.
|
|
29
|
+
this._documents = new Map();
|
|
30
|
+
// Remember if we are ready.
|
|
31
|
+
this._ready = false;
|
|
32
|
+
// For now, the id filter is going to default to
|
|
33
|
+
// the to/from DDP methods on MongoID, to
|
|
34
|
+
// specifically deal with mongo/minimongo ObjectIds.
|
|
35
|
+
// Later, you will be able to make this be "raw"
|
|
36
|
+
// if you want to publish a collection that you know
|
|
37
|
+
// just has strings for keys and no funny business, to
|
|
38
|
+
// a DDP consumer that isn't minimongo.
|
|
39
|
+
this._idFilter = {
|
|
40
|
+
idStringify: id => id /*MongoID.idStringify*/,
|
|
41
|
+
idParse: id => id /*MongoID.idParse*/
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* @summary Access inside the publish function. The incoming [connection](#meteor_onconnection) for this subscription.
|
|
45
|
+
* @locus Server
|
|
46
|
+
* @name connection
|
|
47
|
+
* @memberOf Subscription
|
|
48
|
+
* @instance
|
|
49
|
+
*/
|
|
50
|
+
this.connection = _session.connectionHandle; // public API object
|
|
51
|
+
// Only named subscriptions have IDs, but we need some sort of string
|
|
52
|
+
// internally to keep track of all subscriptions inside
|
|
53
|
+
// SessionDocumentViews. We use this subscriptionHandle for that.
|
|
54
|
+
if (this._subscriptionId) {
|
|
55
|
+
this._subscriptionHandle = `N${this._subscriptionId}`;
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
this._subscriptionHandle = `U${main_1.Random.id()}`;
|
|
59
|
+
}
|
|
60
|
+
// Part of the public API: the user of this sub.
|
|
61
|
+
/**
|
|
62
|
+
* @summary Access inside the publish function. The id of the logged-in user, or `null` if no user is logged in.
|
|
63
|
+
* @locus Server
|
|
64
|
+
* @memberOf Subscription
|
|
65
|
+
* @name userId
|
|
66
|
+
* @instance
|
|
67
|
+
*/
|
|
68
|
+
this.userId = this._session.userId;
|
|
69
|
+
}
|
|
70
|
+
;
|
|
71
|
+
async _runHandler() {
|
|
72
|
+
// XXX should we unblock() here? Either before running the publish
|
|
73
|
+
// function, or before running _publishCursor.
|
|
74
|
+
//
|
|
75
|
+
// Right now, each publish function blocks all future publishes and
|
|
76
|
+
// methods waiting on data from Mongo (or whatever else the function
|
|
77
|
+
// blocks on). This probably slows page load in common cases.
|
|
78
|
+
let resultOrThenable = null;
|
|
79
|
+
const oldInvocation = livedata_server_1.DDP._CurrentPublicationInvocation;
|
|
80
|
+
try {
|
|
81
|
+
livedata_server_1.DDP._CurrentPublicationInvocation = this;
|
|
82
|
+
resultOrThenable = (0, livedata_server_1.maybeAuditArgumentChecks)(this._handler, this, (0, ejson_1.clone)(this._params),
|
|
83
|
+
// It's OK that this would look weird for universal subscriptions,
|
|
84
|
+
// because they have no arguments so there can never be an
|
|
85
|
+
// audit-argument-checks failure.
|
|
86
|
+
"publisher '" + this._name + "'");
|
|
87
|
+
}
|
|
88
|
+
catch (e) {
|
|
89
|
+
this.error(e);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
finally {
|
|
93
|
+
livedata_server_1.DDP._CurrentPublicationInvocation = oldInvocation;
|
|
94
|
+
}
|
|
95
|
+
// Did the handler call this.error or this.stop?
|
|
96
|
+
if (this._isDeactivated())
|
|
97
|
+
return;
|
|
98
|
+
// Both conventional and async publish handler functions are supported.
|
|
99
|
+
// If an object is returned with a then() function, it is either a promise
|
|
100
|
+
// or thenable and will be resolved asynchronously.
|
|
101
|
+
const isThenable = resultOrThenable && typeof resultOrThenable.then === 'function';
|
|
102
|
+
if (isThenable) {
|
|
103
|
+
let result;
|
|
104
|
+
try {
|
|
105
|
+
result = await resultOrThenable;
|
|
106
|
+
}
|
|
107
|
+
catch (e) {
|
|
108
|
+
this.error(e);
|
|
109
|
+
}
|
|
110
|
+
await this._publishHandlerResult(result);
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
await this._publishHandlerResult(resultOrThenable);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
async _publishHandlerResult(res) {
|
|
117
|
+
// SPECIAL CASE: Instead of writing their own callbacks that invoke
|
|
118
|
+
// this.added/changed/ready/etc, the user can just return a collection
|
|
119
|
+
// cursor or array of cursors from the publish function; we call their
|
|
120
|
+
// _publishCursor method which starts observing the cursor and publishes the
|
|
121
|
+
// results. Note that _publishCursor does NOT call ready().
|
|
122
|
+
//
|
|
123
|
+
// XXX This uses an undocumented interface which only the Mongo cursor
|
|
124
|
+
// interface publishes. Should we make this interface public and encourage
|
|
125
|
+
// users to implement it themselves? Arguably, it's unnecessary; users can
|
|
126
|
+
// already write their own functions like
|
|
127
|
+
// var publishMyReactiveThingy = function (name, handler) {
|
|
128
|
+
// Meteor.publish(name, function () {
|
|
129
|
+
// var reactiveThingy = handler();
|
|
130
|
+
// reactiveThingy.publishMe();
|
|
131
|
+
// });
|
|
132
|
+
// };
|
|
133
|
+
var self = this;
|
|
134
|
+
var isCursor = function (c) {
|
|
135
|
+
return c && c._publishCursor;
|
|
136
|
+
};
|
|
137
|
+
if (isCursor(res)) {
|
|
138
|
+
try {
|
|
139
|
+
await res._publishCursor(self);
|
|
140
|
+
}
|
|
141
|
+
catch (e) {
|
|
142
|
+
self.error(e);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
// _publishCursor only returns after the initial added callbacks have run.
|
|
146
|
+
// mark subscription as ready.
|
|
147
|
+
self.ready();
|
|
148
|
+
}
|
|
149
|
+
else if (Array.isArray(res)) {
|
|
150
|
+
// Check all the elements are cursors
|
|
151
|
+
if (!res.every(isCursor)) {
|
|
152
|
+
self.error(new Error("Publish function returned an array of non-Cursors"));
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
// Find duplicate collection names
|
|
156
|
+
// XXX we should support overlapping cursors, but that would require the
|
|
157
|
+
// merge box to allow overlap within a subscription
|
|
158
|
+
var collectionNames = {};
|
|
159
|
+
for (var i = 0; i < res.length; ++i) {
|
|
160
|
+
var collectionName = res[i].cursorDescription.collectionName;
|
|
161
|
+
if (collectionNames.hasOwnProperty(collectionName)) {
|
|
162
|
+
self.error(new Error("Publish function returned multiple cursors for collection " +
|
|
163
|
+
collectionName));
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
collectionNames[collectionName] = true;
|
|
167
|
+
}
|
|
168
|
+
;
|
|
169
|
+
try {
|
|
170
|
+
for (const cur of res) {
|
|
171
|
+
await cur._publishCursor(self);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
catch (e) {
|
|
175
|
+
self.error(e);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
self.ready();
|
|
179
|
+
}
|
|
180
|
+
else if (res) {
|
|
181
|
+
// Truthy values other than cursors or arrays are probably a
|
|
182
|
+
// user mistake (possible returning a Mongo document via, say,
|
|
183
|
+
// `coll.findOne()`).
|
|
184
|
+
self.error(new Error("Publish function can only return a Cursor or "
|
|
185
|
+
+ "an array of Cursors"));
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
// This calls all stop callbacks and prevents the handler from updating any
|
|
189
|
+
// SessionCollectionViews further. It's used when the user unsubscribes or
|
|
190
|
+
// disconnects, as well as during setUserId re-runs. It does *NOT* send
|
|
191
|
+
// removed messages for the published objects; if that is necessary, call
|
|
192
|
+
// _removeAllDocuments first.
|
|
193
|
+
_deactivate() {
|
|
194
|
+
var self = this;
|
|
195
|
+
if (self._deactivated)
|
|
196
|
+
return;
|
|
197
|
+
self._deactivated = true;
|
|
198
|
+
self._callStopCallbacks();
|
|
199
|
+
/*Package['facts-base'] && Package['facts-base'].Facts.incrementServerFact(
|
|
200
|
+
"livedata", "subscriptions", -1);*/
|
|
201
|
+
}
|
|
202
|
+
_callStopCallbacks() {
|
|
203
|
+
var self = this;
|
|
204
|
+
// Tell listeners, so they can clean up
|
|
205
|
+
var callbacks = self._stopCallbacks;
|
|
206
|
+
self._stopCallbacks = [];
|
|
207
|
+
for (const callback of callbacks) {
|
|
208
|
+
callback();
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
// Send remove messages for every document.
|
|
212
|
+
_removeAllDocuments() {
|
|
213
|
+
var self = this;
|
|
214
|
+
self._documents.forEach(function (collectionDocs, collectionName) {
|
|
215
|
+
collectionDocs.forEach(function (strId) {
|
|
216
|
+
self.removed(collectionName, self._idFilter.idParse(strId));
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
// Returns a new Subscription for the same session with the same
|
|
221
|
+
// initial creation parameters. This isn't a clone: it doesn't have
|
|
222
|
+
// the same _documents cache, stopped state or callbacks; may have a
|
|
223
|
+
// different _subscriptionHandle, and gets its userId from the
|
|
224
|
+
// session, not from this object.
|
|
225
|
+
_recreate() {
|
|
226
|
+
var self = this;
|
|
227
|
+
return new Subscription(self._session, self._handler, self._subscriptionId, self._params, self._name);
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* @summary Call inside the publish function. Stops this client's subscription, triggering a call on the client to the `onStop` callback passed to [`Meteor.subscribe`](#meteor_subscribe), if any. If `error` is not a [`Meteor.Error`](#meteor_error), it will be [sanitized](#meteor_error).
|
|
231
|
+
* @locus Server
|
|
232
|
+
* @param {Error} error The error to pass to the client.
|
|
233
|
+
* @instance
|
|
234
|
+
* @memberOf Subscription
|
|
235
|
+
*/
|
|
236
|
+
error(error) {
|
|
237
|
+
var self = this;
|
|
238
|
+
if (self._isDeactivated())
|
|
239
|
+
return;
|
|
240
|
+
self._session._stopSubscription(self._subscriptionId, error);
|
|
241
|
+
}
|
|
242
|
+
// Note that while our DDP client will notice that you've called stop() on the
|
|
243
|
+
// server (and clean up its _subscriptions table) we don't actually provide a
|
|
244
|
+
// mechanism for an app to notice this (the subscribe onError callback only
|
|
245
|
+
// triggers if there is an error).
|
|
246
|
+
/**
|
|
247
|
+
* @summary Call inside the publish function. Stops this client's subscription and invokes the client's `onStop` callback with no error.
|
|
248
|
+
* @locus Server
|
|
249
|
+
* @instance
|
|
250
|
+
* @memberOf Subscription
|
|
251
|
+
*/
|
|
252
|
+
stop() {
|
|
253
|
+
var self = this;
|
|
254
|
+
if (self._isDeactivated())
|
|
255
|
+
return;
|
|
256
|
+
self._session._stopSubscription(self._subscriptionId);
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* @summary Call inside the publish function. Registers a callback function to run when the subscription is stopped.
|
|
260
|
+
* @locus Server
|
|
261
|
+
* @memberOf Subscription
|
|
262
|
+
* @instance
|
|
263
|
+
* @param {Function} func The callback function
|
|
264
|
+
*/
|
|
265
|
+
onStop(func) {
|
|
266
|
+
var self = this;
|
|
267
|
+
if (self._isDeactivated())
|
|
268
|
+
func();
|
|
269
|
+
else
|
|
270
|
+
self._stopCallbacks.push(func);
|
|
271
|
+
}
|
|
272
|
+
// This returns true if the sub has been deactivated, *OR* if the session was
|
|
273
|
+
// destroyed but the deferred call to _deactivateAllSubscriptions hasn't
|
|
274
|
+
// happened yet.
|
|
275
|
+
_isDeactivated() {
|
|
276
|
+
var self = this;
|
|
277
|
+
return self._deactivated || self._session.inQueue === null;
|
|
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
|
+
if (this._session.version === "1a")
|
|
291
|
+
this._session.initialAdds(this._subscriptionHandle, collectionName, documents);
|
|
292
|
+
else
|
|
293
|
+
documents.forEach((doc, id) => this._session.added(this._subscriptionHandle, collectionName, id, doc));
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* @summary Call inside the publish function. Informs the subscriber that a document has been added to the record set.
|
|
297
|
+
* @locus Server
|
|
298
|
+
* @memberOf Subscription
|
|
299
|
+
* @instance
|
|
300
|
+
* @param {String} collection The name of the collection that contains the new document.
|
|
301
|
+
* @param {String} id The new document's ID.
|
|
302
|
+
* @param {Object} fields The fields in the new document. If `_id` is present it is ignored.
|
|
303
|
+
*/
|
|
304
|
+
added(collectionName, id, fields) {
|
|
305
|
+
if (this._isDeactivated())
|
|
306
|
+
return;
|
|
307
|
+
id = this._idFilter.idStringify(id);
|
|
308
|
+
if (this._session.server.getPublicationStrategy(collectionName).doAccountingForCollection) {
|
|
309
|
+
let ids = this._documents.get(collectionName);
|
|
310
|
+
if (ids == null) {
|
|
311
|
+
ids = new Set();
|
|
312
|
+
this._documents.set(collectionName, ids);
|
|
313
|
+
}
|
|
314
|
+
ids.add(id);
|
|
315
|
+
}
|
|
316
|
+
this._session.added(this._subscriptionHandle, collectionName, id, fields);
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* @summary Call inside the publish function. Informs the subscriber that a document in the record set has been modified.
|
|
320
|
+
* @locus Server
|
|
321
|
+
* @memberOf Subscription
|
|
322
|
+
* @instance
|
|
323
|
+
* @param {String} collection The name of the collection that contains the changed document.
|
|
324
|
+
* @param {String} id The changed document's ID.
|
|
325
|
+
* @param {Object} fields The fields in the document that have changed, together with their new values. If a field is not present in `fields` it was left unchanged; if it is present in `fields` and has a value of `undefined` it was removed from the document. If `_id` is present it is ignored.
|
|
326
|
+
*/
|
|
327
|
+
changed(collectionName, id, fields) {
|
|
328
|
+
if (this._isDeactivated())
|
|
329
|
+
return;
|
|
330
|
+
id = this._idFilter.idStringify(id);
|
|
331
|
+
this._session.changed(this._subscriptionHandle, collectionName, id, fields);
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* @summary Call inside the publish function. Informs the subscriber that a document has been removed from the record set.
|
|
335
|
+
* @locus Server
|
|
336
|
+
* @memberOf Subscription
|
|
337
|
+
* @instance
|
|
338
|
+
* @param {String} collection The name of the collection that the document has been removed from.
|
|
339
|
+
* @param {String} id The ID of the document that has been removed.
|
|
340
|
+
*/
|
|
341
|
+
removed(collectionName, id) {
|
|
342
|
+
if (this._isDeactivated())
|
|
343
|
+
return;
|
|
344
|
+
id = this._idFilter.idStringify(id);
|
|
345
|
+
if (this._session.server.getPublicationStrategy(collectionName).doAccountingForCollection) {
|
|
346
|
+
// We don't bother to delete sets of things in a collection if the
|
|
347
|
+
// collection is empty. It could break _removeAllDocuments.
|
|
348
|
+
this._documents.get(collectionName).delete(id);
|
|
349
|
+
}
|
|
350
|
+
this._session.removed(this._subscriptionHandle, collectionName, id);
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* @summary Call inside the publish function. Informs the subscriber that an initial, complete snapshot of the record set has been sent. This will trigger a call on the client to the `onReady` callback passed to [`Meteor.subscribe`](#meteor_subscribe), if any.
|
|
354
|
+
* @locus Server
|
|
355
|
+
* @memberOf Subscription
|
|
356
|
+
* @instance
|
|
357
|
+
*/
|
|
358
|
+
ready() {
|
|
359
|
+
var self = this;
|
|
360
|
+
if (self._isDeactivated())
|
|
361
|
+
return;
|
|
362
|
+
if (!self._subscriptionId)
|
|
363
|
+
return; // Unnecessary but ignored for universal sub
|
|
364
|
+
if (!self._ready) {
|
|
365
|
+
self._session.sendReady([self._subscriptionId]);
|
|
366
|
+
self._ready = true;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
exports.Subscription = Subscription;
|