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: (modifier: any) => any;
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.12",
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",