mongodb-livedata-server 0.0.4 → 0.0.6
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/{livedata_server.ts → dist/livedata_server.d.ts} +1 -1
- package/dist/livedata_server.js +3 -1
- package/dist/meteor/binary-heap/max_heap.d.ts +31 -0
- package/dist/meteor/binary-heap/min_heap.d.ts +6 -0
- package/dist/meteor/binary-heap/min_max_heap.d.ts +11 -0
- package/dist/meteor/callback-hook/hook.d.ts +11 -0
- package/dist/meteor/ddp/crossbar.d.ts +15 -0
- package/dist/meteor/ddp/heartbeat.d.ts +19 -0
- package/dist/meteor/ddp/livedata_server.d.ts +141 -0
- package/dist/meteor/ddp/method-invocation.d.ts +25 -0
- package/dist/meteor/ddp/random-stream.d.ts +8 -0
- package/dist/meteor/ddp/session-collection-view.d.ts +27 -0
- package/dist/meteor/ddp/session-document-view.d.ts +8 -0
- package/dist/meteor/ddp/session.d.ts +69 -0
- package/dist/meteor/ddp/stream_server.d.ts +21 -0
- package/dist/meteor/ddp/subscription.d.ts +89 -0
- package/dist/meteor/ddp/utils.d.ts +8 -0
- package/dist/meteor/ddp/writefence.d.ts +20 -0
- package/dist/meteor/diff-sequence/diff.d.ts +13 -0
- package/dist/meteor/ejson/ejson.d.ts +82 -0
- package/dist/meteor/ejson/stringify.d.ts +2 -0
- package/dist/meteor/ejson/utils.d.ts +12 -0
- package/dist/meteor/id-map/id_map.d.ts +16 -0
- package/dist/meteor/mongo/caching_change_observer.d.ts +16 -0
- package/dist/meteor/mongo/doc_fetcher.d.ts +7 -0
- package/dist/meteor/mongo/geojson_utils.d.ts +3 -0
- package/dist/meteor/mongo/live_connection.d.ts +27 -0
- package/dist/meteor/mongo/live_cursor.d.ts +25 -0
- package/dist/meteor/mongo/minimongo_common.d.ts +84 -0
- package/dist/meteor/mongo/minimongo_matcher.d.ts +22 -0
- package/dist/meteor/mongo/minimongo_sorter.d.ts +16 -0
- package/dist/meteor/mongo/observe_driver_utils.d.ts +9 -0
- package/dist/meteor/mongo/observe_multiplexer.d.ts +36 -0
- package/dist/meteor/mongo/oplog-observe-driver.d.ts +67 -0
- package/dist/meteor/mongo/oplog_tailing.d.ts +35 -0
- package/dist/meteor/mongo/oplog_v2_converter.d.ts +1 -0
- package/dist/meteor/mongo/polling_observe_driver.d.ts +30 -0
- package/dist/meteor/mongo/synchronous-cursor.d.ts +17 -0
- package/dist/meteor/mongo/synchronous-queue.d.ts +14 -0
- package/dist/meteor/ordered-dict/ordered_dict.d.ts +31 -0
- package/dist/meteor/random/AbstractRandomGenerator.d.ts +42 -0
- package/dist/meteor/random/AleaRandomGenerator.d.ts +13 -0
- package/dist/meteor/random/NodeRandomGenerator.d.ts +16 -0
- package/dist/meteor/random/createAleaGenerator.d.ts +2 -0
- package/dist/meteor/random/createRandom.d.ts +1 -0
- package/dist/meteor/random/main.d.ts +1 -0
- package/package.json +2 -2
- package/meteor/LICENSE +0 -28
- package/meteor/binary-heap/max_heap.ts +0 -225
- package/meteor/binary-heap/min_heap.ts +0 -15
- package/meteor/binary-heap/min_max_heap.ts +0 -53
- package/meteor/callback-hook/hook.ts +0 -85
- package/meteor/ddp/crossbar.ts +0 -148
- package/meteor/ddp/heartbeat.ts +0 -97
- package/meteor/ddp/livedata_server.ts +0 -474
- package/meteor/ddp/method-invocation.ts +0 -86
- package/meteor/ddp/random-stream.ts +0 -102
- package/meteor/ddp/session-collection-view.ts +0 -119
- package/meteor/ddp/session-document-view.ts +0 -92
- package/meteor/ddp/session.ts +0 -708
- package/meteor/ddp/stream_server.ts +0 -204
- package/meteor/ddp/subscription.ts +0 -392
- package/meteor/ddp/utils.ts +0 -119
- package/meteor/ddp/writefence.ts +0 -130
- package/meteor/diff-sequence/diff.ts +0 -295
- package/meteor/ejson/ejson.ts +0 -601
- package/meteor/ejson/stringify.ts +0 -122
- package/meteor/ejson/utils.ts +0 -38
- package/meteor/id-map/id_map.ts +0 -84
- package/meteor/mongo/caching_change_observer.ts +0 -120
- package/meteor/mongo/doc_fetcher.ts +0 -52
- package/meteor/mongo/geojson_utils.ts +0 -42
- package/meteor/mongo/live_connection.ts +0 -302
- package/meteor/mongo/live_cursor.ts +0 -79
- package/meteor/mongo/minimongo_common.ts +0 -2440
- package/meteor/mongo/minimongo_matcher.ts +0 -275
- package/meteor/mongo/minimongo_sorter.ts +0 -331
- package/meteor/mongo/observe_driver_utils.ts +0 -79
- package/meteor/mongo/observe_multiplexer.ts +0 -256
- package/meteor/mongo/oplog-observe-driver.ts +0 -1049
- package/meteor/mongo/oplog_tailing.ts +0 -414
- package/meteor/mongo/oplog_v2_converter.ts +0 -124
- package/meteor/mongo/polling_observe_driver.ts +0 -247
- package/meteor/mongo/synchronous-cursor.ts +0 -293
- package/meteor/mongo/synchronous-queue.ts +0 -119
- package/meteor/ordered-dict/ordered_dict.ts +0 -229
- package/meteor/random/AbstractRandomGenerator.ts +0 -99
- package/meteor/random/AleaRandomGenerator.ts +0 -96
- package/meteor/random/NodeRandomGenerator.ts +0 -37
- package/meteor/random/createAleaGenerator.ts +0 -31
- package/meteor/random/createRandom.ts +0 -19
- package/meteor/random/main.ts +0 -8
- package/tsconfig.json +0 -10
|
@@ -1,275 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
combineImportantPathsIntoProjection,
|
|
3
|
-
compileDocumentSelector,
|
|
4
|
-
hasOwn,
|
|
5
|
-
isNumericKey,
|
|
6
|
-
isOperatorObject,
|
|
7
|
-
nothingMatcher,
|
|
8
|
-
_modify,
|
|
9
|
-
_pathsElidingNumericKeys,
|
|
10
|
-
} from './minimongo_common';
|
|
11
|
-
import { Filter } from 'mongodb';
|
|
12
|
-
import { clone, isBinary } from '../ejson/ejson';
|
|
13
|
-
|
|
14
|
-
// The minimongo selector compiler!
|
|
15
|
-
|
|
16
|
-
// Terminology:
|
|
17
|
-
// - a 'selector' is the EJSON object representing a selector
|
|
18
|
-
// - a 'matcher' is its compiled form (whether a full Minimongo.Matcher
|
|
19
|
-
// object or one of the component lambdas that matches parts of it)
|
|
20
|
-
// - a 'result object' is an object with a 'result' field and maybe
|
|
21
|
-
// distance and arrayIndices.
|
|
22
|
-
// - a 'branched value' is an object with a 'value' field and maybe
|
|
23
|
-
// 'dontIterate' and 'arrayIndices'.
|
|
24
|
-
// - a 'document' is a top-level object that can be stored in a collection.
|
|
25
|
-
// - a 'lookup function' is a function that takes in a document and returns
|
|
26
|
-
// an array of 'branched values'.
|
|
27
|
-
// - a 'branched matcher' maps from an array of branched values to a result
|
|
28
|
-
// object.
|
|
29
|
-
// - an 'element matcher' maps from a single value to a bool.
|
|
30
|
-
|
|
31
|
-
// Main entry point.
|
|
32
|
-
// var matcher = new Minimongo.Matcher({a: {$gt: 5}});
|
|
33
|
-
// if (matcher.documentMatches({a: 7})) ...
|
|
34
|
-
|
|
35
|
-
export class MinimongoMatcher {
|
|
36
|
-
private _paths: Record<string, any>;
|
|
37
|
-
private _hasGeoQuery: boolean;
|
|
38
|
-
private _hasWhere: boolean;
|
|
39
|
-
private _isSimple: boolean;
|
|
40
|
-
private _matchingDocument: any;
|
|
41
|
-
private _selector: Filter<any>;
|
|
42
|
-
private _docMatcher: any;
|
|
43
|
-
private _isUpdate: boolean;
|
|
44
|
-
|
|
45
|
-
constructor(selector: Filter<any>, isUpdate?: boolean) {
|
|
46
|
-
// A set (object mapping string -> *) of all of the document paths looked
|
|
47
|
-
// at by the selector. Also includes the empty string if it may look at any
|
|
48
|
-
// path (eg, $where).
|
|
49
|
-
this._paths = {};
|
|
50
|
-
// Set to true if compilation finds a $near.
|
|
51
|
-
this._hasGeoQuery = false;
|
|
52
|
-
// Set to true if compilation finds a $where.
|
|
53
|
-
this._hasWhere = false;
|
|
54
|
-
// Set to false if compilation finds anything other than a simple equality
|
|
55
|
-
// or one or more of '$gt', '$gte', '$lt', '$lte', '$ne', '$in', '$nin' used
|
|
56
|
-
// with scalars as operands.
|
|
57
|
-
this._isSimple = true;
|
|
58
|
-
// Set to a dummy document which always matches this Matcher. Or set to null
|
|
59
|
-
// if such document is too hard to find.
|
|
60
|
-
this._matchingDocument = undefined;
|
|
61
|
-
// A clone of the original selector. It may just be a function if the user
|
|
62
|
-
// passed in a function; otherwise is definitely an object (eg, IDs are
|
|
63
|
-
// translated into {_id: ID} first. Used by canBecomeTrueByModifier and
|
|
64
|
-
// Sorter._useWithMatcher.
|
|
65
|
-
this._selector = null;
|
|
66
|
-
this._docMatcher = this._compileSelector(selector);
|
|
67
|
-
// Set to true if selection is done for an update operation
|
|
68
|
-
// Default is false
|
|
69
|
-
// Used for $near array update (issue #3599)
|
|
70
|
-
this._isUpdate = isUpdate;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
documentMatches(doc) {
|
|
74
|
-
if (doc !== Object(doc)) {
|
|
75
|
-
throw Error('documentMatches needs a document');
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return this._docMatcher(doc);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
hasGeoQuery() {
|
|
82
|
-
return this._hasGeoQuery;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
hasWhere() {
|
|
86
|
-
return this._hasWhere;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
isSimple() {
|
|
90
|
-
return this._isSimple;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Given a selector, return a function that takes one argument, a
|
|
94
|
-
// document. It returns a result object.
|
|
95
|
-
_compileSelector(selector: Filter<any>) {
|
|
96
|
-
// you can pass a literal function instead of a selector
|
|
97
|
-
if (selector instanceof Function) {
|
|
98
|
-
this._isSimple = false;
|
|
99
|
-
this._selector = selector;
|
|
100
|
-
this._recordPathUsed('');
|
|
101
|
-
|
|
102
|
-
return doc => ({ result: !!selector.call(doc) });
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// protect against dangerous selectors. falsey and {_id: falsey} are both
|
|
106
|
-
// likely programmer error, and not what you want, particularly for
|
|
107
|
-
// destructive operations.
|
|
108
|
-
if (!selector || hasOwn.call(selector, '_id') && !selector._id) {
|
|
109
|
-
this._isSimple = false;
|
|
110
|
-
return nothingMatcher;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Top level can't be an array or true or binary.
|
|
114
|
-
if (Array.isArray(selector) ||
|
|
115
|
-
isBinary(selector) ||
|
|
116
|
-
typeof selector === 'boolean') {
|
|
117
|
-
throw new Error(`Invalid selector: ${selector}`);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
this._selector = clone(selector);
|
|
121
|
-
|
|
122
|
-
return compileDocumentSelector(selector, this, { isRoot: true });
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
affectedByModifier(modifier) {
|
|
126
|
-
// safe check for $set/$unset being objects
|
|
127
|
-
modifier = Object.assign({ $set: {}, $unset: {} }, modifier);
|
|
128
|
-
|
|
129
|
-
const meaningfulPaths = this._getPaths();
|
|
130
|
-
const modifiedPaths = [].concat(
|
|
131
|
-
Object.keys(modifier.$set),
|
|
132
|
-
Object.keys(modifier.$unset)
|
|
133
|
-
);
|
|
134
|
-
|
|
135
|
-
return modifiedPaths.some(path => {
|
|
136
|
-
const mod = path.split('.');
|
|
137
|
-
|
|
138
|
-
return meaningfulPaths.some(meaningfulPath => {
|
|
139
|
-
const sel = meaningfulPath.split('.');
|
|
140
|
-
|
|
141
|
-
let i = 0, j = 0;
|
|
142
|
-
|
|
143
|
-
while (i < sel.length && j < mod.length) {
|
|
144
|
-
if (isNumericKey(sel[i]) && isNumericKey(mod[j])) {
|
|
145
|
-
// foo.4.bar selector affected by foo.4 modifier
|
|
146
|
-
// foo.3.bar selector unaffected by foo.4 modifier
|
|
147
|
-
if (sel[i] === mod[j]) {
|
|
148
|
-
i++;
|
|
149
|
-
j++;
|
|
150
|
-
} else {
|
|
151
|
-
return false;
|
|
152
|
-
}
|
|
153
|
-
} else if (isNumericKey(sel[i])) {
|
|
154
|
-
// foo.4.bar selector unaffected by foo.bar modifier
|
|
155
|
-
return false;
|
|
156
|
-
} else if (isNumericKey(mod[j])) {
|
|
157
|
-
j++;
|
|
158
|
-
} else if (sel[i] === mod[j]) {
|
|
159
|
-
i++;
|
|
160
|
-
j++;
|
|
161
|
-
} else {
|
|
162
|
-
return false;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// One is a prefix of another, taking numeric fields into account
|
|
167
|
-
return true;
|
|
168
|
-
});
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
canBecomeTrueByModifier = function (modifier) {
|
|
173
|
-
if (!this.affectedByModifier(modifier)) {
|
|
174
|
-
return false;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
if (!this.isSimple()) {
|
|
178
|
-
return true;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
modifier = Object.assign({ $set: {}, $unset: {} }, modifier);
|
|
182
|
-
|
|
183
|
-
const modifierPaths = [].concat(
|
|
184
|
-
Object.keys(modifier.$set),
|
|
185
|
-
Object.keys(modifier.$unset)
|
|
186
|
-
);
|
|
187
|
-
|
|
188
|
-
if (this._getPaths().some(pathHasNumericKeys) ||
|
|
189
|
-
modifierPaths.some(pathHasNumericKeys)) {
|
|
190
|
-
return true;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// check if there is a $set or $unset that indicates something is an
|
|
194
|
-
// object rather than a scalar in the actual object where we saw $-operator
|
|
195
|
-
// NOTE: it is correct since we allow only scalars in $-operators
|
|
196
|
-
// Example: for selector {'a.b': {$gt: 5}} the modifier {'a.b.c':7} would
|
|
197
|
-
// definitely set the result to false as 'a.b' appears to be an object.
|
|
198
|
-
const expectedScalarIsObject = Object.keys(this._selector).some(path => {
|
|
199
|
-
if (!isOperatorObject(this._selector[path])) {
|
|
200
|
-
return false;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
return modifierPaths.some(modifierPath =>
|
|
204
|
-
modifierPath.startsWith(`${path}.`)
|
|
205
|
-
);
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
if (expectedScalarIsObject) {
|
|
209
|
-
return false;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// See if we can apply the modifier on the ideally matching object. If it
|
|
213
|
-
// still matches the selector, then the modifier could have turned the real
|
|
214
|
-
// object in the database into something matching.
|
|
215
|
-
const matchingDocument = clone(this.matchingDocument());
|
|
216
|
-
|
|
217
|
-
// The selector is too complex, anything can happen.
|
|
218
|
-
if (matchingDocument === null) {
|
|
219
|
-
return true;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
try {
|
|
223
|
-
_modify(matchingDocument, modifier);
|
|
224
|
-
} catch (error) {
|
|
225
|
-
// Couldn't set a property on a field which is a scalar or null in the
|
|
226
|
-
// selector.
|
|
227
|
-
// Example:
|
|
228
|
-
// real document: { 'a.b': 3 }
|
|
229
|
-
// selector: { 'a': 12 }
|
|
230
|
-
// converted selector (ideal document): { 'a': 12 }
|
|
231
|
-
// modifier: { $set: { 'a.b': 4 } }
|
|
232
|
-
// We don't know what real document was like but from the error raised by
|
|
233
|
-
// $set on a scalar field we can reason that the structure of real document
|
|
234
|
-
// is completely different.
|
|
235
|
-
if (error.name === 'MinimongoError' && error.setPropertyError) {
|
|
236
|
-
return false;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
throw error;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
return this.documentMatches(matchingDocument).result;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// Knows how to combine a mongo selector and a fields projection to a new fields
|
|
246
|
-
// projection taking into account active fields from the passed selector.
|
|
247
|
-
// @returns Object - projection object (same as fields option of mongo cursor)
|
|
248
|
-
combineIntoProjection(projection) {
|
|
249
|
-
const selectorPaths = _pathsElidingNumericKeys(this._getPaths());
|
|
250
|
-
|
|
251
|
-
// Special case for $where operator in the selector - projection should depend
|
|
252
|
-
// on all fields of the document. getSelectorPaths returns a list of paths
|
|
253
|
-
// selector depends on. If one of the paths is '' (empty string) representing
|
|
254
|
-
// the root or the whole document, complete projection should be returned.
|
|
255
|
-
if (selectorPaths.includes('')) {
|
|
256
|
-
return {};
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
return combineImportantPathsIntoProjection(selectorPaths, projection);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
// Returns a list of key paths the given selector is looking for. It includes
|
|
263
|
-
// the empty string if there is a $where.
|
|
264
|
-
_getPaths() {
|
|
265
|
-
return Object.keys(this._paths);
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
_recordPathUsed(path) {
|
|
269
|
-
this._paths[path] = true;
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
function pathHasNumericKeys(path: string) {
|
|
274
|
-
return path.split('.').some(isNumericKey);
|
|
275
|
-
}
|
|
@@ -1,331 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
combineImportantPathsIntoProjection,
|
|
3
|
-
expandArraysInBranches,
|
|
4
|
-
hasOwn,
|
|
5
|
-
makeLookupFunction,
|
|
6
|
-
_f,
|
|
7
|
-
_pathsElidingNumericKeys,
|
|
8
|
-
} from './minimongo_common';
|
|
9
|
-
import { MinimongoMatcher } from './minimongo_matcher';
|
|
10
|
-
|
|
11
|
-
// Give a sort spec, which can be in any of these forms:
|
|
12
|
-
// {"key1": 1, "key2": -1}
|
|
13
|
-
// [["key1", "asc"], ["key2", "desc"]]
|
|
14
|
-
// ["key1", ["key2", "desc"]]
|
|
15
|
-
//
|
|
16
|
-
// (.. with the first form being dependent on the key enumeration
|
|
17
|
-
// behavior of your javascript VM, which usually does what you mean in
|
|
18
|
-
// this case if the key names don't look like integers ..)
|
|
19
|
-
//
|
|
20
|
-
// return a function that takes two objects, and returns -1 if the
|
|
21
|
-
// first object comes first in order, 1 if the second object comes
|
|
22
|
-
// first, or 0 if neither object comes before the other.
|
|
23
|
-
|
|
24
|
-
export default class MinimongoSorter {
|
|
25
|
-
private _sortSpecParts: {ascending: boolean, path: string, lookup: Function}[];
|
|
26
|
-
private _sortFunction: (a: any, b: any) => number | null;
|
|
27
|
-
private _keyComparator: (a: any, b: any) => number;
|
|
28
|
-
private _selectorForAffectedByModifier: MinimongoMatcher;
|
|
29
|
-
|
|
30
|
-
constructor(spec) {
|
|
31
|
-
this._sortSpecParts = [];
|
|
32
|
-
this._sortFunction = null;
|
|
33
|
-
|
|
34
|
-
const addSpecPart = (path: string, ascending: boolean) => {
|
|
35
|
-
if (!path) {
|
|
36
|
-
throw Error('sort keys must be non-empty');
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
if (path.charAt(0) === '$') {
|
|
40
|
-
throw Error(`unsupported sort key: ${path}`);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
this._sortSpecParts.push({
|
|
44
|
-
ascending,
|
|
45
|
-
lookup: makeLookupFunction(path, { forSort: true }),
|
|
46
|
-
path
|
|
47
|
-
});
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
if (spec instanceof Array) {
|
|
51
|
-
spec.forEach(element => {
|
|
52
|
-
if (typeof element === 'string') {
|
|
53
|
-
addSpecPart(element, true);
|
|
54
|
-
} else {
|
|
55
|
-
addSpecPart(element[0], element[1] !== 'desc');
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
} else if (typeof spec === 'object') {
|
|
59
|
-
Object.keys(spec).forEach(key => {
|
|
60
|
-
addSpecPart(key, spec[key] >= 0);
|
|
61
|
-
});
|
|
62
|
-
} else if (typeof spec === 'function') {
|
|
63
|
-
this._sortFunction = spec;
|
|
64
|
-
} else {
|
|
65
|
-
throw Error(`Bad sort specification: ${JSON.stringify(spec)}`);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// If a function is specified for sorting, we skip the rest.
|
|
69
|
-
if (this._sortFunction) {
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// To implement affectedByModifier, we piggy-back on top of Matcher's
|
|
74
|
-
// affectedByModifier code; we create a selector that is affected by the
|
|
75
|
-
// same modifiers as this sort order. This is only implemented on the
|
|
76
|
-
// server.
|
|
77
|
-
if (this.affectedByModifier) {
|
|
78
|
-
const selector = {};
|
|
79
|
-
|
|
80
|
-
this._sortSpecParts.forEach(spec => {
|
|
81
|
-
selector[spec.path] = 1;
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
this._selectorForAffectedByModifier = new MinimongoMatcher(selector);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
this._keyComparator = composeComparators(
|
|
88
|
-
this._sortSpecParts.map((spec, i) => this._keyFieldComparator(i))
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
affectedByModifier(modifier) {
|
|
93
|
-
return this._selectorForAffectedByModifier.affectedByModifier(modifier);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
getComparator(options?): (a: any, b: any) => number {
|
|
97
|
-
// If sort is specified or have no distances, just use the comparator from
|
|
98
|
-
// the source specification (which defaults to "everything is equal".
|
|
99
|
-
// issue #3599
|
|
100
|
-
// https://docs.mongodb.com/manual/reference/operator/query/near/#sort-operation
|
|
101
|
-
// sort effectively overrides $near
|
|
102
|
-
if (this._sortSpecParts.length || !options || !options.distances) {
|
|
103
|
-
return this._getBaseComparator();
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const distances = options.distances;
|
|
107
|
-
|
|
108
|
-
// Return a comparator which compares using $near distances.
|
|
109
|
-
return (a, b) => {
|
|
110
|
-
if (!distances.has(a._id)) {
|
|
111
|
-
throw Error(`Missing distance for ${a._id}`);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (!distances.has(b._id)) {
|
|
115
|
-
throw Error(`Missing distance for ${b._id}`);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
return distances.get(a._id) - distances.get(b._id);
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Takes in two keys: arrays whose lengths match the number of spec
|
|
123
|
-
// parts. Returns negative, 0, or positive based on using the sort spec to
|
|
124
|
-
// compare fields.
|
|
125
|
-
_compareKeys(key1, key2) {
|
|
126
|
-
if (key1.length !== this._sortSpecParts.length ||
|
|
127
|
-
key2.length !== this._sortSpecParts.length) {
|
|
128
|
-
throw Error('Key has wrong length');
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
return this._keyComparator(key1, key2);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Iterates over each possible "key" from doc (ie, over each branch), calling
|
|
135
|
-
// 'cb' with the key.
|
|
136
|
-
_generateKeysFromDoc(doc, cb) {
|
|
137
|
-
if (this._sortSpecParts.length === 0) {
|
|
138
|
-
throw new Error('can\'t generate keys without a spec');
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const pathFromIndices = indices => `${indices.join(',')},`;
|
|
142
|
-
|
|
143
|
-
let knownPaths = null;
|
|
144
|
-
|
|
145
|
-
// maps index -> ({'' -> value} or {path -> value})
|
|
146
|
-
const valuesByIndexAndPath = this._sortSpecParts.map(spec => {
|
|
147
|
-
// Expand any leaf arrays that we find, and ignore those arrays
|
|
148
|
-
// themselves. (We never sort based on an array itself.)
|
|
149
|
-
let branches = expandArraysInBranches(spec.lookup(doc), true);
|
|
150
|
-
|
|
151
|
-
// If there are no values for a key (eg, key goes to an empty array),
|
|
152
|
-
// pretend we found one undefined value.
|
|
153
|
-
if (!branches.length) {
|
|
154
|
-
branches = [{ value: void 0 }];
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const element = Object.create(null);
|
|
158
|
-
let usedPaths = false;
|
|
159
|
-
|
|
160
|
-
branches.forEach(branch => {
|
|
161
|
-
if (!branch.arrayIndices) {
|
|
162
|
-
// If there are no array indices for a branch, then it must be the
|
|
163
|
-
// only branch, because the only thing that produces multiple branches
|
|
164
|
-
// is the use of arrays.
|
|
165
|
-
if (branches.length > 1) {
|
|
166
|
-
throw Error('multiple branches but no array used?');
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
element[''] = branch.value;
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
usedPaths = true;
|
|
174
|
-
|
|
175
|
-
const path = pathFromIndices(branch.arrayIndices);
|
|
176
|
-
|
|
177
|
-
if (hasOwn.call(element, path)) {
|
|
178
|
-
throw Error(`duplicate path: ${path}`);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
element[path] = branch.value;
|
|
182
|
-
|
|
183
|
-
// If two sort fields both go into arrays, they have to go into the
|
|
184
|
-
// exact same arrays and we have to find the same paths. This is
|
|
185
|
-
// roughly the same condition that makes MongoDB throw this strange
|
|
186
|
-
// error message. eg, the main thing is that if sort spec is {a: 1,
|
|
187
|
-
// b:1} then a and b cannot both be arrays.
|
|
188
|
-
//
|
|
189
|
-
// (In MongoDB it seems to be OK to have {a: 1, 'a.x.y': 1} where 'a'
|
|
190
|
-
// and 'a.x.y' are both arrays, but we don't allow this for now.
|
|
191
|
-
// #NestedArraySort
|
|
192
|
-
// XXX achieve full compatibility here
|
|
193
|
-
if (knownPaths && !hasOwn.call(knownPaths, path)) {
|
|
194
|
-
throw Error('cannot index parallel arrays');
|
|
195
|
-
}
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
if (knownPaths) {
|
|
199
|
-
// Similarly to above, paths must match everywhere, unless this is a
|
|
200
|
-
// non-array field.
|
|
201
|
-
if (!hasOwn.call(element, '') &&
|
|
202
|
-
Object.keys(knownPaths).length !== Object.keys(element).length) {
|
|
203
|
-
throw Error('cannot index parallel arrays!');
|
|
204
|
-
}
|
|
205
|
-
} else if (usedPaths) {
|
|
206
|
-
knownPaths = {};
|
|
207
|
-
|
|
208
|
-
Object.keys(element).forEach(path => {
|
|
209
|
-
knownPaths[path] = true;
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
return element;
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
if (!knownPaths) {
|
|
217
|
-
// Easy case: no use of arrays.
|
|
218
|
-
const soleKey = valuesByIndexAndPath.map(values => {
|
|
219
|
-
if (!hasOwn.call(values, '')) {
|
|
220
|
-
throw Error('no value in sole key case?');
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
return values[''];
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
cb(soleKey);
|
|
227
|
-
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
Object.keys(knownPaths).forEach(path => {
|
|
232
|
-
const key = valuesByIndexAndPath.map(values => {
|
|
233
|
-
if (hasOwn.call(values, '')) {
|
|
234
|
-
return values[''];
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
if (!hasOwn.call(values, path)) {
|
|
238
|
-
throw Error('missing path?');
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
return values[path];
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
cb(key);
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// Returns a comparator that represents the sort specification (but not
|
|
249
|
-
// including a possible geoquery distance tie-breaker).
|
|
250
|
-
_getBaseComparator() {
|
|
251
|
-
if (this._sortFunction) {
|
|
252
|
-
return this._sortFunction;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// If we're only sorting on geoquery distance and no specs, just say
|
|
256
|
-
// everything is equal.
|
|
257
|
-
if (!this._sortSpecParts.length) {
|
|
258
|
-
return (doc1, doc2) => 0;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
return (doc1, doc2) => {
|
|
262
|
-
const key1 = this._getMinKeyFromDoc(doc1);
|
|
263
|
-
const key2 = this._getMinKeyFromDoc(doc2);
|
|
264
|
-
return this._compareKeys(key1, key2);
|
|
265
|
-
};
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// Finds the minimum key from the doc, according to the sort specs. (We say
|
|
269
|
-
// "minimum" here but this is with respect to the sort spec, so "descending"
|
|
270
|
-
// sort fields mean we're finding the max for that field.)
|
|
271
|
-
//
|
|
272
|
-
// Note that this is NOT "find the minimum value of the first field, the
|
|
273
|
-
// minimum value of the second field, etc"... it's "choose the
|
|
274
|
-
// lexicographically minimum value of the key vector, allowing only keys which
|
|
275
|
-
// you can find along the same paths". ie, for a doc {a: [{x: 0, y: 5}, {x:
|
|
276
|
-
// 1, y: 3}]} with sort spec {'a.x': 1, 'a.y': 1}, the only keys are [0,5] and
|
|
277
|
-
// [1,3], and the minimum key is [0,5]; notably, [0,3] is NOT a key.
|
|
278
|
-
_getMinKeyFromDoc(doc) {
|
|
279
|
-
let minKey = null;
|
|
280
|
-
|
|
281
|
-
this._generateKeysFromDoc(doc, key => {
|
|
282
|
-
if (minKey === null) {
|
|
283
|
-
minKey = key;
|
|
284
|
-
return;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
if (this._compareKeys(key, minKey) < 0) {
|
|
288
|
-
minKey = key;
|
|
289
|
-
}
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
return minKey;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
_getPaths() {
|
|
296
|
-
return this._sortSpecParts.map(part => part.path);
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
// Given an index 'i', returns a comparator that compares two key arrays based
|
|
300
|
-
// on field 'i'.
|
|
301
|
-
_keyFieldComparator(i: number) {
|
|
302
|
-
const invert = !this._sortSpecParts[i].ascending;
|
|
303
|
-
|
|
304
|
-
return (key1, key2) => {
|
|
305
|
-
const compare = _f._cmp(key1[i], key2[i]);
|
|
306
|
-
return invert ? -compare : compare;
|
|
307
|
-
};
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
combineIntoProjection = function (projection) {
|
|
311
|
-
return combineImportantPathsIntoProjection(_pathsElidingNumericKeys(this._getPaths()), projection);
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
// Given an array of comparators
|
|
317
|
-
// (functions (a,b)->(negative or positive or zero)), returns a single
|
|
318
|
-
// comparator which uses each comparator in order and returns the first
|
|
319
|
-
// non-zero value.
|
|
320
|
-
function composeComparators(comparatorArray) {
|
|
321
|
-
return (a, b) => {
|
|
322
|
-
for (let i = 0; i < comparatorArray.length; ++i) {
|
|
323
|
-
const compare = comparatorArray[i](a, b);
|
|
324
|
-
if (compare !== 0) {
|
|
325
|
-
return compare;
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
return 0;
|
|
330
|
-
};
|
|
331
|
-
}
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import MongoDB from "mongodb";
|
|
2
|
-
import { _InvalidationCrossbar } from "../ddp/crossbar";
|
|
3
|
-
import { CursorDescription } from "./live_cursor";
|
|
4
|
-
|
|
5
|
-
// Listen for the invalidation messages that will trigger us to poll the
|
|
6
|
-
// database for changes. If this selector specifies specific IDs, specify them
|
|
7
|
-
// here, so that updates to different specific IDs don't cause us to poll.
|
|
8
|
-
// listenCallback is the same kind of (notification, complete) callback passed
|
|
9
|
-
// to InvalidationCrossbar.listen.
|
|
10
|
-
|
|
11
|
-
export function listenAll(cursorDescription: CursorDescription<any>, listenCallback: Function) {
|
|
12
|
-
var listeners = [];
|
|
13
|
-
forEachTrigger(cursorDescription, function (trigger) {
|
|
14
|
-
listeners.push(_InvalidationCrossbar.listen(trigger, listenCallback));
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
return {
|
|
18
|
-
stop: function () {
|
|
19
|
-
for (const listener of listeners) {
|
|
20
|
-
listener.stop();
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function forEachTrigger(cursorDescription: CursorDescription<any>, triggerCallback: Function) {
|
|
27
|
-
var key = { collection: cursorDescription.collectionName };
|
|
28
|
-
var specificIds = _idsMatchedBySelector(cursorDescription.selector);
|
|
29
|
-
if (specificIds) {
|
|
30
|
-
for (const id of specificIds) {
|
|
31
|
-
triggerCallback(Object.assign({ id: id }, key));
|
|
32
|
-
}
|
|
33
|
-
triggerCallback(Object.assign({ dropCollection: true, id: null }, key));
|
|
34
|
-
} else {
|
|
35
|
-
triggerCallback(key);
|
|
36
|
-
}
|
|
37
|
-
// Everyone cares about the database being dropped.
|
|
38
|
-
triggerCallback({ dropDatabase: true });
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function _idsMatchedBySelector(selector: MongoDB.Filter<{ _id: string }>) {
|
|
42
|
-
if (!selector) {
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Do we have an _id clause?
|
|
47
|
-
if (selector.hasOwnProperty('_id')) {
|
|
48
|
-
// Is the _id clause just an ID?
|
|
49
|
-
if (typeof selector._id === "string") {
|
|
50
|
-
return [selector._id];
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Is the _id clause {_id: {$in: ["x", "y", "z"]}}?
|
|
54
|
-
if (selector._id
|
|
55
|
-
&& "$in" in selector._id
|
|
56
|
-
&& Array.isArray(selector._id.$in)
|
|
57
|
-
&& selector._id.$in.length
|
|
58
|
-
&& selector._id.$in.every(id => typeof id === "string")) {
|
|
59
|
-
return selector._id.$in;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return null;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// If this is a top-level $and, and any of the clauses constrain their
|
|
66
|
-
// documents, then the whole selector is constrained by any one clause's
|
|
67
|
-
// constraint. (Well, by their intersection, but that seems unlikely.)
|
|
68
|
-
if ("$and" in selector && Array.isArray(selector.$and)) {
|
|
69
|
-
for (let i = 0; i < selector.$and.length; ++i) {
|
|
70
|
-
const subIds = _idsMatchedBySelector(selector.$and[i]);
|
|
71
|
-
|
|
72
|
-
if (subIds) {
|
|
73
|
-
return subIds;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return null;
|
|
79
|
-
}
|