-
Notifications
You must be signed in to change notification settings - Fork 47
/
Copy pathpublish-counts.js
211 lines (180 loc) · 6.79 KB
/
publish-counts.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
var noWarnings = false;
Counts = {};
Counts.publish = async function(self, name, cursor, options) {
var initializing = true;
var handle;
options = options || {};
var extraField, countFn;
if (options.countFromField) {
extraField = options.countFromField;
if ('function' === typeof extraField) {
countFn = Counts._safeAccessorFunction(extraField);
} else {
countFn = function(doc) {
return doc[extraField] || 0; // return 0 instead of undefined.
}
}
} else if (options.countFromFieldLength) {
extraField = options.countFromFieldLength;
if ('function' === typeof extraField) {
countFn = Counts._safeAccessorFunction(function (doc) {
return extraField(doc).length;
});
} else {
countFn = function(doc) {
if (doc[extraField]) {
return doc[extraField].length;
} else {
return 0;
}
}
}
}
if (countFn && options.nonReactive)
throw new Error("options.nonReactive is not yet supported with options.countFromFieldLength or options.countFromFieldSum");
if (cursor && cursor._cursorDescription) {
cursor._cursorDescription.options.fields =
Counts._optimizeQueryFields(cursor._cursorDescription.options.fields || cursor._cursorDescription.options.projection, extraField, options.noWarnings);
}
var count = 0;
var observers = {
added: function(doc) {
if (countFn) {
count += countFn(doc);
} else {
count += 1;
}
if (!initializing)
self.changed('counts', name, {count: count});
},
removed: function(doc) {
if (countFn) {
count -= countFn(doc);
} else {
count -= 1;
}
self.changed('counts', name, {count: count});
}
};
if (countFn) {
observers.changed = function(newDoc, oldDoc) {
if (countFn) {
count += countFn(newDoc) - countFn(oldDoc);
}
self.changed('counts', name, {count: count});
};
}
if (!countFn) {
let count;
// Cursor.count is officially deprecated, so reuse Meteor's stored cursor options, like
// https://github.com/meteor/meteor/blob/release-3.0/packages/mongo/mongo_driver.js#L876,
// but we reuse the method countDocuments available here
// https://github.com/meteor/meteor/blob/release-3.0/packages/mongo/mongo_driver.js#L772
// given that it applies the internal methods replaceTypes and replaceMeteorAtomWithMongo
// to the `selector` and `options` arguments
if (cursor._cursorDescription && typeof cursor._mongo?.countDocuments === 'function') {
const { collectionName, selector, options } = cursor._cursorDescription;
count = await cursor._mongo.countDocuments(collectionName, selector, options);
} else {
const isLocalCollection = cursor.collection && !cursor.collection.name;
if (!isLocalCollection) {
Counts._warn(null, 'publish-counts: Internal Meteor API not available, ' +
'loading cursor documents in the memory to perform the count');
}
const array = await cursor.fetchAsync();
count = array.length;
}
self.added('counts', name, {count: count});
if (!options.noReady)
self.ready();
}
if (!options.nonReactive)
handle = await cursor.observe(observers);
if (countFn)
self.added('counts', name, {count: count});
if (!options.noReady)
self.ready();
initializing = false;
self.onStop(function() {
if (handle)
handle.stop();
});
return {
stop: function() {
if (handle) {
handle.stop();
handle = undefined;
}
}
};
};
// back compatibility
publishCount = Counts.publish;
Counts.noWarnings = function (noWarn) {
// suppress warnings if no arguments, or first argument is truthy
noWarnings = (0 == arguments.length || !!noWarn);
}
Counts._safeAccessorFunction = function safeAccessorFunction (fn) {
// ensure that missing fields don't corrupt the count. If the count field
// doesn't exist, then it has a zero count.
return function (doc) {
try {
return fn(doc) || 0; // return 0 instead of undefined
}
catch (err) {
if (err instanceof TypeError) { // attempted to access property of undefined (i.e. deep access).
return 0;
} else {
throw err;
}
}
};
}
Counts._optimizeQueryFields = function optimizeQueryFields (fields, extraField, noWarn) {
switch (typeof extraField) {
case 'function': // accessor function used.
if (undefined === fields) {
// user did not place restrictions on cursor fields.
Counts._warn(noWarn,
'publish-counts: Collection cursor has no field limits and will fetch entire documents. ' +
'consider specifying only required fields.');
// if cursor field limits are empty to begin with, leave them empty. it is the
// user's responsibility to specify field limits when using accessor functions.
}
// else user specified restrictions on cursor fields. Meteor will ensure _id is one of them.
// WARNING: unable to verify user included appropriate field for accessor function to work. we can't hold their hand ;_;
return fields;
case 'string': // countFromField or countFromFieldLength has property name.
// extra field is a property
// automatically set limits if none specified. keep existing limits since user
// may use a cursor transform and specify a dynamic field to count, but require other
// fields in the transform process (e.g. https://github.com/percolatestudio/publish-counts/issues/47).
fields = fields || {};
// _id and extraField are required
fields._id = true;
fields[extraField] = true;
if (2 < _.keys(fields).length)
Counts._warn(noWarn,
'publish-counts: unused fields detected in cursor fields option',
_.omit(fields, ['_id', extraField]));
// use modified field limits. automatically defaults to _id and extraField if none specified by user.
return fields;
case 'undefined': // basic count
if (fields && 0 < _.keys(_.omit(fields, ['_id'])).length)
Counts._warn(noWarn,
'publish-counts: unused fields removed from cursor fields option.',
_.omit(fields, ['_id']));
// dispose of user field limits, only _id is required
fields = { _id: true };
// use modified field limits. automatically defaults to _id if none specified by user.
return fields;
default:
throw new Error("unknown invocation of Count.publish() detected.");
}
}
Counts._warn = function warn (noWarn) {
if (noWarnings || noWarn || 'production' == process.env.NODE_ENV)
return;
var args = Array.prototype.slice.call(arguments, 1);
console.warn.apply(console, args);
}