mongodb-livedata-server 0.0.12 → 0.0.13
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.
|
@@ -15,7 +15,8 @@ export declare class MinimongoMatcher {
|
|
|
15
15
|
isSimple(): boolean;
|
|
16
16
|
_compileSelector(selector: Filter<any>): any;
|
|
17
17
|
affectedByModifier(modifier: any): boolean;
|
|
18
|
-
canBecomeTrueByModifier
|
|
18
|
+
canBecomeTrueByModifier(modifier: any): any;
|
|
19
|
+
matchingDocument(): any;
|
|
19
20
|
combineIntoProjection(projection: any): {};
|
|
20
21
|
_getPaths(): string[];
|
|
21
22
|
_recordPathUsed(path: any): void;
|
|
@@ -23,62 +23,6 @@ const ejson_1 = require("../ejson/ejson");
|
|
|
23
23
|
// if (matcher.documentMatches({a: 7})) ...
|
|
24
24
|
class MinimongoMatcher {
|
|
25
25
|
constructor(selector, isUpdate) {
|
|
26
|
-
this.canBecomeTrueByModifier = function (modifier) {
|
|
27
|
-
if (!this.affectedByModifier(modifier)) {
|
|
28
|
-
return false;
|
|
29
|
-
}
|
|
30
|
-
if (!this.isSimple()) {
|
|
31
|
-
return true;
|
|
32
|
-
}
|
|
33
|
-
modifier = Object.assign({ $set: {}, $unset: {} }, modifier);
|
|
34
|
-
const modifierPaths = [].concat(Object.keys(modifier.$set), Object.keys(modifier.$unset));
|
|
35
|
-
if (this._getPaths().some(pathHasNumericKeys) ||
|
|
36
|
-
modifierPaths.some(pathHasNumericKeys)) {
|
|
37
|
-
return true;
|
|
38
|
-
}
|
|
39
|
-
// check if there is a $set or $unset that indicates something is an
|
|
40
|
-
// object rather than a scalar in the actual object where we saw $-operator
|
|
41
|
-
// NOTE: it is correct since we allow only scalars in $-operators
|
|
42
|
-
// Example: for selector {'a.b': {$gt: 5}} the modifier {'a.b.c':7} would
|
|
43
|
-
// definitely set the result to false as 'a.b' appears to be an object.
|
|
44
|
-
const expectedScalarIsObject = Object.keys(this._selector).some(path => {
|
|
45
|
-
if (!(0, minimongo_common_1.isOperatorObject)(this._selector[path])) {
|
|
46
|
-
return false;
|
|
47
|
-
}
|
|
48
|
-
return modifierPaths.some(modifierPath => modifierPath.startsWith(`${path}.`));
|
|
49
|
-
});
|
|
50
|
-
if (expectedScalarIsObject) {
|
|
51
|
-
return false;
|
|
52
|
-
}
|
|
53
|
-
// See if we can apply the modifier on the ideally matching object. If it
|
|
54
|
-
// still matches the selector, then the modifier could have turned the real
|
|
55
|
-
// object in the database into something matching.
|
|
56
|
-
const matchingDocument = (0, ejson_1.clone)(this.matchingDocument());
|
|
57
|
-
// The selector is too complex, anything can happen.
|
|
58
|
-
if (matchingDocument === null) {
|
|
59
|
-
return true;
|
|
60
|
-
}
|
|
61
|
-
try {
|
|
62
|
-
(0, minimongo_common_1._modify)(matchingDocument, modifier);
|
|
63
|
-
}
|
|
64
|
-
catch (error) {
|
|
65
|
-
// Couldn't set a property on a field which is a scalar or null in the
|
|
66
|
-
// selector.
|
|
67
|
-
// Example:
|
|
68
|
-
// real document: { 'a.b': 3 }
|
|
69
|
-
// selector: { 'a': 12 }
|
|
70
|
-
// converted selector (ideal document): { 'a': 12 }
|
|
71
|
-
// modifier: { $set: { 'a.b': 4 } }
|
|
72
|
-
// We don't know what real document was like but from the error raised by
|
|
73
|
-
// $set on a scalar field we can reason that the structure of real document
|
|
74
|
-
// is completely different.
|
|
75
|
-
if (error.name === 'MinimongoError' && error.setPropertyError) {
|
|
76
|
-
return false;
|
|
77
|
-
}
|
|
78
|
-
throw error;
|
|
79
|
-
}
|
|
80
|
-
return this.documentMatches(matchingDocument).result;
|
|
81
|
-
};
|
|
82
26
|
// A set (object mapping string -> *) of all of the document paths looked
|
|
83
27
|
// at by the selector. Also includes the empty string if it may look at any
|
|
84
28
|
// path (eg, $where).
|
|
@@ -188,6 +132,125 @@ class MinimongoMatcher {
|
|
|
188
132
|
});
|
|
189
133
|
});
|
|
190
134
|
}
|
|
135
|
+
canBecomeTrueByModifier(modifier) {
|
|
136
|
+
if (!this.affectedByModifier(modifier)) {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
if (!this.isSimple()) {
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
modifier = Object.assign({ $set: {}, $unset: {} }, modifier);
|
|
143
|
+
const modifierPaths = [].concat(Object.keys(modifier.$set), Object.keys(modifier.$unset));
|
|
144
|
+
if (this._getPaths().some(pathHasNumericKeys) ||
|
|
145
|
+
modifierPaths.some(pathHasNumericKeys)) {
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
// check if there is a $set or $unset that indicates something is an
|
|
149
|
+
// object rather than a scalar in the actual object where we saw $-operator
|
|
150
|
+
// NOTE: it is correct since we allow only scalars in $-operators
|
|
151
|
+
// Example: for selector {'a.b': {$gt: 5}} the modifier {'a.b.c':7} would
|
|
152
|
+
// definitely set the result to false as 'a.b' appears to be an object.
|
|
153
|
+
const expectedScalarIsObject = Object.keys(this._selector).some(path => {
|
|
154
|
+
if (!(0, minimongo_common_1.isOperatorObject)(this._selector[path])) {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
return modifierPaths.some(modifierPath => modifierPath.startsWith(`${path}.`));
|
|
158
|
+
});
|
|
159
|
+
if (expectedScalarIsObject) {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
// See if we can apply the modifier on the ideally matching object. If it
|
|
163
|
+
// still matches the selector, then the modifier could have turned the real
|
|
164
|
+
// object in the database into something matching.
|
|
165
|
+
const matchingDocument = (0, ejson_1.clone)(this.matchingDocument());
|
|
166
|
+
// The selector is too complex, anything can happen.
|
|
167
|
+
if (matchingDocument === null) {
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
try {
|
|
171
|
+
(0, minimongo_common_1._modify)(matchingDocument, modifier);
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
// Couldn't set a property on a field which is a scalar or null in the
|
|
175
|
+
// selector.
|
|
176
|
+
// Example:
|
|
177
|
+
// real document: { 'a.b': 3 }
|
|
178
|
+
// selector: { 'a': 12 }
|
|
179
|
+
// converted selector (ideal document): { 'a': 12 }
|
|
180
|
+
// modifier: { $set: { 'a.b': 4 } }
|
|
181
|
+
// We don't know what real document was like but from the error raised by
|
|
182
|
+
// $set on a scalar field we can reason that the structure of real document
|
|
183
|
+
// is completely different.
|
|
184
|
+
if (error.name === 'MinimongoError' && error.setPropertyError) {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
throw error;
|
|
188
|
+
}
|
|
189
|
+
return this.documentMatches(matchingDocument).result;
|
|
190
|
+
}
|
|
191
|
+
matchingDocument() {
|
|
192
|
+
// check if it was computed before
|
|
193
|
+
if (this._matchingDocument !== undefined) {
|
|
194
|
+
return this._matchingDocument;
|
|
195
|
+
}
|
|
196
|
+
// If the analysis of this selector is too hard for our implementation
|
|
197
|
+
// fallback to "YES"
|
|
198
|
+
let fallback = false;
|
|
199
|
+
this._matchingDocument = (0, minimongo_common_1.pathsToTree)(this._getPaths(), path => {
|
|
200
|
+
const valueSelector = this._selector[path];
|
|
201
|
+
if ((0, minimongo_common_1.isOperatorObject)(valueSelector)) {
|
|
202
|
+
// if there is a strict equality, there is a good
|
|
203
|
+
// chance we can use one of those as "matching"
|
|
204
|
+
// dummy value
|
|
205
|
+
if (valueSelector.$eq) {
|
|
206
|
+
return valueSelector.$eq;
|
|
207
|
+
}
|
|
208
|
+
if (valueSelector.$in) {
|
|
209
|
+
const matcher = new MinimongoMatcher({ placeholder: valueSelector });
|
|
210
|
+
// Return anything from $in that matches the whole selector for this
|
|
211
|
+
// path. If nothing matches, returns `undefined` as nothing can make
|
|
212
|
+
// this selector into `true`.
|
|
213
|
+
return valueSelector.$in.find(placeholder => matcher.documentMatches({ placeholder }).result);
|
|
214
|
+
}
|
|
215
|
+
if (onlyContainsKeys(valueSelector, ['$gt', '$gte', '$lt', '$lte'])) {
|
|
216
|
+
let lowerBound = -Infinity;
|
|
217
|
+
let upperBound = Infinity;
|
|
218
|
+
['$lte', '$lt'].forEach(op => {
|
|
219
|
+
if (minimongo_common_1.hasOwn.call(valueSelector, op) &&
|
|
220
|
+
valueSelector[op] < upperBound) {
|
|
221
|
+
upperBound = valueSelector[op];
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
['$gte', '$gt'].forEach(op => {
|
|
225
|
+
if (minimongo_common_1.hasOwn.call(valueSelector, op) &&
|
|
226
|
+
valueSelector[op] > lowerBound) {
|
|
227
|
+
lowerBound = valueSelector[op];
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
const middle = (lowerBound + upperBound) / 2;
|
|
231
|
+
const matcher = new MinimongoMatcher({ placeholder: valueSelector });
|
|
232
|
+
if (!matcher.documentMatches({ placeholder: middle }).result &&
|
|
233
|
+
(middle === lowerBound || middle === upperBound)) {
|
|
234
|
+
fallback = true;
|
|
235
|
+
}
|
|
236
|
+
return middle;
|
|
237
|
+
}
|
|
238
|
+
if (onlyContainsKeys(valueSelector, ['$nin', '$ne'])) {
|
|
239
|
+
// Since this._isSimple makes sure $nin and $ne are not combined with
|
|
240
|
+
// objects or arrays, we can confidently return an empty object as it
|
|
241
|
+
// never matches any scalar.
|
|
242
|
+
return {};
|
|
243
|
+
}
|
|
244
|
+
fallback = true;
|
|
245
|
+
}
|
|
246
|
+
return this._selector[path];
|
|
247
|
+
}, x => x);
|
|
248
|
+
if (fallback) {
|
|
249
|
+
this._matchingDocument = null;
|
|
250
|
+
}
|
|
251
|
+
return this._matchingDocument;
|
|
252
|
+
}
|
|
253
|
+
;
|
|
191
254
|
// Knows how to combine a mongo selector and a fields projection to a new fields
|
|
192
255
|
// projection taking into account active fields from the passed selector.
|
|
193
256
|
// @returns Object - projection object (same as fields option of mongo cursor)
|
|
@@ -215,3 +278,6 @@ exports.MinimongoMatcher = MinimongoMatcher;
|
|
|
215
278
|
function pathHasNumericKeys(path) {
|
|
216
279
|
return path.split('.').some(minimongo_common_1.isNumericKey);
|
|
217
280
|
}
|
|
281
|
+
function onlyContainsKeys(obj, keys) {
|
|
282
|
+
return Object.keys(obj).every(k => keys.includes(k));
|
|
283
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mongodb-livedata-server",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.13",
|
|
4
4
|
"description": "MongoDB live data server, extracted from Meteor, Fibers removed and converted to TypeScript",
|
|
5
5
|
"main": "dist/livedata_server.js",
|
|
6
6
|
"types": "dist/livedata_server.d.ts",
|