react-native-mosquito-transport 0.0.18 → 0.0.21
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/.jshintignore +4 -0
- package/.jshintrc +16 -0
- package/README.md +75 -1
- package/TODO +10 -1
- package/example/ios/MosquitodbExample.xcodeproj/project.pbxproj +6 -5
- package/example/ios/MosquitodbExample.xcworkspace/contents.xcworkspacedata +10 -0
- package/example/ios/MosquitodbExample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- package/example/ios/MosquitodbExample.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +8 -0
- package/example/ios/MosquitodbExample.xcworkspace/xcuserdata/anthony.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/example/ios/MosquitodbExample.xcworkspace/xcuserdata/anthony.xcuserdatad/WorkspaceSettings.xcsettings +14 -0
- package/ios/Mosquitodb.swift +14 -1
- package/package.json +15 -14
- package/src/helpers/engine_api.js +39 -0
- package/src/helpers/peripherals.js +73 -127
- package/src/helpers/utils.js +48 -19
- package/src/helpers/values.js +8 -47
- package/src/helpers/variables.js +14 -6
- package/src/index.d.ts +103 -43
- package/src/index.js +198 -121
- package/src/products/auth/accessor.js +97 -36
- package/src/products/auth/index.js +151 -82
- package/src/products/database/accessor.js +720 -223
- package/src/products/database/bson.js +16 -0
- package/src/products/database/counter.js +16 -0
- package/src/products/database/index.js +303 -190
- package/src/products/database/types.js +1 -1
- package/src/products/database/validator.js +517 -254
- package/src/products/http_callable/index.js +111 -106
- package/src/products/storage/index.js +97 -88
- package/src/helpers/EngineApi.js +0 -33
|
@@ -1,289 +1,552 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { guardArray, GuardError, guardObject, GuardSignal, niceGuard, Validator } from "guard-object";
|
|
2
|
+
import { sameInstance } from "../../helpers/peripherals";
|
|
3
|
+
import { RETRIEVAL } from "../../helpers/values";
|
|
3
4
|
import getLodash from 'lodash.get';
|
|
4
|
-
import
|
|
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
|
-
|
|
5
|
+
import { Binary, BSONRegExp, BSONSymbol, Code, DBRef, Decimal128, Double, Int32, Long, MaxKey, MinKey, ObjectId, Timestamp, UUID } from 'bson';
|
|
6
|
+
import { bboxPolygon, booleanIntersects, booleanWithin, circle, distance, polygon } from "@turf/turf";
|
|
7
|
+
|
|
8
|
+
const DirectionList = [1, -1, 'asc', 'desc', 'ascending', 'descending'];
|
|
9
|
+
const FilterFootPrint = t => {
|
|
10
|
+
validateFilter(t);
|
|
11
|
+
return true;
|
|
12
|
+
};
|
|
13
|
+
const ReturnAndExcludeFootprint = t => t === undefined ||
|
|
14
|
+
!(Array.isArray(t) ? t : [t]).filter(v => !Validator.TRIMMED_NON_EMPTY_STRING(v)).length;
|
|
15
|
+
|
|
16
|
+
const ConfigFind = t => t && FilterFootPrint(assignExtractionFind({}, t));
|
|
17
|
+
|
|
18
|
+
const FindConfig = {
|
|
19
|
+
extraction: t => t === undefined ||
|
|
20
|
+
(Array.isArray(t) ? t : [t]).filter(m =>
|
|
21
|
+
guardObject({
|
|
22
|
+
collection: isValidCollectionName,
|
|
23
|
+
sort: (t, p) => t === undefined || (Validator.TRIMMED_NON_EMPTY_STRING(t) && p.find),
|
|
24
|
+
direction: (t, p) => t === undefined || (p.sort && p.find && DirectionList.includes(t)),
|
|
25
|
+
limit: (t, p) => t === undefined || (Validator.POSITIVE_INTEGER(t) && p.find),
|
|
26
|
+
find: (t, p) => (t === undefined && p.findOne) || (!p.findOne && ConfigFind(t)),
|
|
27
|
+
findOne: (t, p) => (t === undefined && p.find) || (!p.find && ConfigFind(t)),
|
|
28
|
+
returnOnly: ReturnAndExcludeFootprint,
|
|
29
|
+
excludeFields: ReturnAndExcludeFootprint
|
|
30
|
+
}).validate(m)
|
|
31
|
+
).length,
|
|
32
|
+
returnOnly: ReturnAndExcludeFootprint,
|
|
33
|
+
excludeFields: ReturnAndExcludeFootprint,
|
|
34
|
+
|
|
35
|
+
episode: t => [undefined, 0, 1].includes(t),
|
|
36
|
+
retrieval: t => t === undefined || Object.values(RETRIEVAL).includes(t),
|
|
37
|
+
disableAuth: t => t === undefined || typeof t === 'boolean',
|
|
38
|
+
disableMinimizer: t => t === undefined || typeof t === 'boolean'
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const validateFindConfig = (config) => config === undefined ||
|
|
42
|
+
guardObject(FindConfig).validate(config);
|
|
43
|
+
|
|
44
|
+
export const validateListenFindConfig = (config) => config === undefined ||
|
|
45
|
+
guardObject({
|
|
46
|
+
extraction: FindConfig.extraction,
|
|
47
|
+
returnOnly: FindConfig.returnOnly,
|
|
48
|
+
excludeFields: FindConfig.excludeFields,
|
|
49
|
+
disableAuth: FindConfig.disableAuth,
|
|
50
|
+
episode: t => [undefined, 0, 1].includes(t)
|
|
51
|
+
}).validate(config);
|
|
52
|
+
|
|
53
|
+
export const validateFindObject = command =>
|
|
54
|
+
guardObject({
|
|
55
|
+
// path: GuardSignal.TRIMMED_NON_EMPTY_STRING,
|
|
56
|
+
find: (t, p) => (t === undefined && p.findOne) || (!p.findOne && FilterFootPrint(t)),
|
|
57
|
+
findOne: (t, p) => (t === undefined && p.find) || (!p.find && FilterFootPrint(t)),
|
|
58
|
+
sort: t => t === undefined || Validator.TRIMMED_NON_EMPTY_STRING(t),
|
|
59
|
+
direction: (t, p) => t === undefined || (p.sort && DirectionList.includes(t)),
|
|
60
|
+
limit: t => t === undefined || Validator.POSITIVE_INTEGER(t),
|
|
61
|
+
random: (t, p) => t === undefined || (!p.sort && t === true),
|
|
62
|
+
}).validate({ ...command });
|
|
63
|
+
|
|
64
|
+
export const assignExtractionFind = (data, find) => {
|
|
65
|
+
if (!find) return find;
|
|
66
|
+
|
|
67
|
+
if (niceGuard({ $dynamicValue: GuardSignal.NON_EMPTY_STRING }, find)) {
|
|
68
|
+
return getLodash(data, find.$dynamicValue) || null;
|
|
69
|
+
} else if (Validator.OBJECT(find)) {
|
|
70
|
+
return Object.fromEntries(
|
|
71
|
+
Object.entries(find).map(([k, v]) =>
|
|
72
|
+
Validator.JSON(v) ? [k, assignExtractionFind(data, v)] : [k, v]
|
|
73
|
+
)
|
|
74
|
+
);
|
|
75
|
+
} else if (Array.isArray(find)) {
|
|
76
|
+
return find.map(v => assignExtractionFind(data, v));
|
|
77
|
+
} else return find;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export const validateCollectionName = collectionName => {
|
|
81
|
+
// Check if the collection name is empty
|
|
82
|
+
if (!collectionName || typeof collectionName !== 'string')
|
|
83
|
+
throw `collection name must be a non-empty string but got ${collectionName}`;
|
|
84
|
+
|
|
85
|
+
// Collection name cannot start with 'system.' (reserved)
|
|
86
|
+
if (collectionName.startsWith('system.'))
|
|
87
|
+
throw `collection name cannot start with 'system.' but got ${collectionName}`;
|
|
88
|
+
|
|
89
|
+
// Collection name cannot contain the '$' character
|
|
90
|
+
if (collectionName.includes('$'))
|
|
91
|
+
throw `collection name cannot contain the '$' character but got ${collectionName}`;
|
|
60
92
|
}
|
|
61
93
|
|
|
62
|
-
|
|
63
|
-
|
|
94
|
+
function isValidDatabaseName(dbName) {
|
|
95
|
+
// Check if the database name is empty
|
|
96
|
+
if (!dbName || typeof dbName !== 'string') {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
64
99
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
throw `expected an array of document in ${type}() operation but got ${value}`;
|
|
100
|
+
// Database name must be less than 64 characters
|
|
101
|
+
if (Buffer.byteLength(dbName, 'utf8') >= 64) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
70
104
|
|
|
71
|
-
|
|
105
|
+
// Database name cannot contain invalid characters: / \ " . $ space
|
|
106
|
+
const invalidDbChars = /[\/\\."$ ]/;
|
|
107
|
+
if (invalidDbChars.test(dbName)) {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
72
110
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
return;
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function isValidCollectionName(collectionName) {
|
|
115
|
+
try {
|
|
116
|
+
validateCollectionName(collectionName);
|
|
117
|
+
return true;
|
|
118
|
+
} catch (_) {
|
|
119
|
+
return false;
|
|
80
120
|
}
|
|
121
|
+
};
|
|
81
122
|
|
|
82
|
-
|
|
83
|
-
if (!isObject) throw `expected raw object in ${type}() operation but got ${value}`;
|
|
123
|
+
export const validateFilter = (filter) => confirmFilterDoc({}, filter);
|
|
84
124
|
|
|
85
|
-
|
|
125
|
+
export const confirmFilterDoc = (data, filter) => {
|
|
126
|
+
if (!Validator.OBJECT(filter)) throw `expected an object as filter value but got ${filter}`;
|
|
86
127
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
throw `"_foreign_doc" is a reserved word, don't use it as a field in a document`;
|
|
128
|
+
const logicalList = ['$and', '$or', '$nor'];
|
|
129
|
+
const logics = [[], [], []]; // [$and, $or, $nor]
|
|
90
130
|
|
|
91
|
-
|
|
131
|
+
Object.entries(filter).forEach(([key, value]) => {
|
|
132
|
+
if (logicalList.includes(key)) {
|
|
133
|
+
if (!Array.isArray(value)) throw `"${key}" must be an array`;
|
|
134
|
+
if (!value.length) throw `"${key}" must be a nonempty array`;
|
|
135
|
+
logics[logicalList.indexOf(key)].push(...value.map(v => evaluateFilter(data, v)));
|
|
136
|
+
} else logics[0].push(evaluateFilter(data, { [key]: value }));
|
|
92
137
|
});
|
|
93
|
-
|
|
138
|
+
const [AND, OR, NOR] = logics;
|
|
94
139
|
|
|
95
|
-
|
|
140
|
+
return !AND.some(v => !v) &&
|
|
141
|
+
(!OR.length || OR.some(v => v)) &&
|
|
142
|
+
(!NOR.length || NOR.some(v => !v));
|
|
143
|
+
};
|
|
96
144
|
|
|
97
|
-
|
|
98
|
-
if (typeof path !== 'string' || path.includes('.') || !path.trim())
|
|
99
|
-
throw `invalid collection path "${path}", expected non-empty string and mustn't contain "."`;
|
|
100
|
-
}
|
|
145
|
+
const plumeDoc = doc => [doc, ...Array.isArray(doc) ? doc : []];
|
|
101
146
|
|
|
102
|
-
export const
|
|
103
|
-
|
|
104
|
-
|
|
147
|
+
export const defaultBSON = (value, instance) => {
|
|
148
|
+
try {
|
|
149
|
+
return instance.constructor(value);
|
|
150
|
+
} catch (_) {
|
|
151
|
+
return value;
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
export const downcastBSON = d => {
|
|
156
|
+
if (d instanceof BSONRegExp)
|
|
157
|
+
return new RegExp(d.pattern, d.options);
|
|
158
|
+
if (
|
|
159
|
+
[
|
|
160
|
+
Long,
|
|
161
|
+
Double,
|
|
162
|
+
Int32,
|
|
163
|
+
Decimal128
|
|
164
|
+
].some(v => d instanceof v)
|
|
165
|
+
) return d * 1;
|
|
166
|
+
return d;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const isBasicBSON = d =>
|
|
170
|
+
[
|
|
171
|
+
Code,
|
|
172
|
+
ObjectId,
|
|
173
|
+
Binary,
|
|
174
|
+
MaxKey,
|
|
175
|
+
MinKey,
|
|
176
|
+
UUID,
|
|
177
|
+
Timestamp,
|
|
178
|
+
BSONSymbol
|
|
179
|
+
].some(v => d instanceof v);
|
|
180
|
+
|
|
181
|
+
export const CompareBson = {
|
|
182
|
+
equal: (doc, q, explicit) => {
|
|
183
|
+
doc = downcastBSON(doc);
|
|
184
|
+
q = downcastBSON(q);
|
|
185
|
+
|
|
186
|
+
if (
|
|
187
|
+
isBasicBSON(q) ||
|
|
188
|
+
isBasicBSON(doc)
|
|
189
|
+
) {
|
|
190
|
+
return sameInstance(doc, q) &&
|
|
191
|
+
JSON.stringify(doc) === JSON.stringify(q);
|
|
192
|
+
}
|
|
105
193
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
194
|
+
if (q instanceof RegExp) {
|
|
195
|
+
return sameInstance(doc, q) ?
|
|
196
|
+
(doc.source === q.source && doc.flags === q.flags) :
|
|
197
|
+
(explicit && typeof doc === 'string' && q.test(doc));
|
|
198
|
+
}
|
|
199
|
+
return JSON.stringify(doc) === JSON.stringify(q)
|
|
200
|
+
},
|
|
201
|
+
greater: (doc, q) => {
|
|
202
|
+
doc = downcastBSON(doc);
|
|
203
|
+
q = downcastBSON(q);
|
|
204
|
+
|
|
205
|
+
if (doc instanceof Timestamp || q instanceof Timestamp) {
|
|
206
|
+
return sameInstance(doc, q) && doc.greaterThan(q);
|
|
207
|
+
}
|
|
115
208
|
|
|
116
|
-
|
|
117
|
-
}
|
|
209
|
+
return typeof doc === typeof q && ![q, doc].some(v => Array.isArray(v) || Validator.OBJECT(v)) && doc > q;
|
|
210
|
+
},
|
|
211
|
+
lesser: (doc, q) => {
|
|
212
|
+
doc = downcastBSON(doc);
|
|
213
|
+
q = downcastBSON(q);
|
|
118
214
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
'
|
|
130
|
-
|
|
215
|
+
if (doc instanceof Timestamp || q instanceof Timestamp) {
|
|
216
|
+
return sameInstance(doc, q) && doc.lessThan(q);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return typeof doc === typeof q && ![q, doc].some(v => Array.isArray(v) || Validator.OBJECT(v)) && doc < q;
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
const BsonTypeMap = {
|
|
224
|
+
double: [1, d => d instanceof Double],
|
|
225
|
+
string: [2, d => typeof d === 'string'],
|
|
226
|
+
object: [3, d => Validator.OBJECT(d)],
|
|
227
|
+
array: [4, d => Array.isArray(d)],
|
|
228
|
+
binData: [5, d => d instanceof Binary],
|
|
229
|
+
objectId: [7, d => d instanceof ObjectId],
|
|
230
|
+
bool: [8, d => typeof d === 'boolean'],
|
|
231
|
+
date: [9, d => d instanceof Date],
|
|
232
|
+
null: [10, d => d === null],
|
|
233
|
+
regex: [11, d => d instanceof RegExp || d instanceof BSONRegExp],
|
|
234
|
+
dbPointer: [12, d => d instanceof DBRef],
|
|
235
|
+
javascript: [13, d => d instanceof Code],
|
|
236
|
+
symbol: [14, d => d instanceof BSONSymbol],
|
|
237
|
+
int: [16, d => d instanceof Int32],
|
|
238
|
+
timestamp: [17, d => d instanceof Timestamp],
|
|
239
|
+
long: [18, d => d instanceof Long],
|
|
240
|
+
decimal: [19, d => d instanceof Decimal128],
|
|
241
|
+
minKey: [-1, d => d instanceof MinKey],
|
|
242
|
+
maxKey: [127, d => d instanceof MaxKey],
|
|
243
|
+
number: [undefined, d => d instanceof Double ||
|
|
244
|
+
d instanceof Int32 ||
|
|
245
|
+
d instanceof Long ||
|
|
246
|
+
d instanceof Decimal128 ||
|
|
247
|
+
Validator.NUMBER(d)]
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
const COORDINATE_GUARD = [
|
|
251
|
+
GuardSignal.COORDINATE.LONGITUDE_INT,
|
|
252
|
+
GuardSignal.COORDINATE.LATITUDE_INT
|
|
131
253
|
];
|
|
132
254
|
|
|
133
|
-
const
|
|
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
|
-
|
|
212
|
-
fieldArr.forEach(v => {
|
|
213
|
-
if (typeof v !== 'string' || !v.trim())
|
|
214
|
-
throw `invalid item inside "$field", expected a non-empty string but got "${v}"`;
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
const searchTxt = fieldArr.map(v => getLodash(dataObj, v || '')).map(v =>
|
|
218
|
-
`${typeof v === 'string' ? v :
|
|
219
|
-
Array.isArray(v) ? v.map(v => typeof v === 'string' ? v : '').join(' ').trim() : ''}`.trim()
|
|
220
|
-
).join(' ').trim();
|
|
221
|
-
|
|
222
|
-
logics.push(
|
|
223
|
-
$caseSensitive ? searchTxt.includes(value.trim()) :
|
|
224
|
-
searchTxt.toLowerCase().includes(value.toLowerCase().trim())
|
|
225
|
-
);
|
|
226
|
-
}
|
|
227
|
-
} else if ($ === $EQ) {
|
|
255
|
+
const validateGeoNear = q =>
|
|
256
|
+
guardObject({
|
|
257
|
+
$geometry: {
|
|
258
|
+
type: 'Point',
|
|
259
|
+
coordinates: COORDINATE_GUARD
|
|
260
|
+
},
|
|
261
|
+
$minDistance: (t, p) => Validator.POSITIVE_NUMBER(t) && p.$maxDistance > t,
|
|
262
|
+
$maxDistance: (t, p) => Validator.POSITIVE_NUMBER(t) && p.$minDistance < t
|
|
263
|
+
}).validate(q);
|
|
264
|
+
|
|
265
|
+
const FilterUtils = {
|
|
266
|
+
$eq: (doc, q) => plumeDoc(doc).some(v =>
|
|
267
|
+
CompareBson.equal(v, q)
|
|
268
|
+
),
|
|
269
|
+
|
|
270
|
+
$ne: (doc, q) => !FilterUtils.$eq(doc, q),
|
|
271
|
+
|
|
272
|
+
$gt: (doc, q) => plumeDoc(doc).some(v =>
|
|
273
|
+
CompareBson.greater(v, q)
|
|
274
|
+
),
|
|
275
|
+
|
|
276
|
+
$gte: (doc, q) => plumeDoc(doc).some(v =>
|
|
277
|
+
CompareBson.greater(v, q) || CompareBson.equal(v, q)
|
|
278
|
+
),
|
|
279
|
+
|
|
280
|
+
$lt: (doc, q) => plumeDoc(doc).some(v =>
|
|
281
|
+
CompareBson.lesser(v, q)
|
|
282
|
+
),
|
|
283
|
+
|
|
284
|
+
$lte: (doc, q) => plumeDoc(doc).some(v =>
|
|
285
|
+
CompareBson.lesser(v, q) || CompareBson.equal(v, q)
|
|
286
|
+
),
|
|
287
|
+
|
|
288
|
+
$in: (doc, q) => {
|
|
289
|
+
if (!Array.isArray(q)) throw '$in needs an array';
|
|
290
|
+
return plumeDoc(doc).some(v =>
|
|
291
|
+
q.some(k => CompareBson.equal(v, k, true))
|
|
292
|
+
);
|
|
293
|
+
},
|
|
294
|
+
|
|
295
|
+
$nin: (doc, q) => !FilterUtils.$in(doc, q),
|
|
296
|
+
|
|
297
|
+
$all: (doc, q) => {
|
|
298
|
+
if (!Array.isArray(q)) throw '$all needs an array';
|
|
299
|
+
return plumeDoc(doc).filter(v =>
|
|
300
|
+
q.some(k => CompareBson.equal(v, k, true))
|
|
301
|
+
).length >= q.length;
|
|
302
|
+
},
|
|
303
|
+
|
|
304
|
+
$size: (doc, q) => {
|
|
305
|
+
if (!Validator.POSITIVE_INTEGER(q))
|
|
306
|
+
throw `Failed to parse $size. Expected a positive integer in: $size: ${q}`;
|
|
307
|
+
return Array.isArray(doc) && doc.length === q;
|
|
308
|
+
},
|
|
309
|
+
|
|
310
|
+
$type: (doc, q) => {
|
|
311
|
+
if (q === undefined) return false;
|
|
312
|
+
return plumeDoc(doc).some(docx => {
|
|
313
|
+
if (q in BsonTypeMap) {
|
|
314
|
+
return BsonTypeMap[q][1](docx);
|
|
315
|
+
}
|
|
316
|
+
const c = Object.entries(BsonTypeMap).find(([_, v]) => v[0] === q);
|
|
317
|
+
if (c) return c[1][1](docx);
|
|
318
|
+
if (typeof q === 'number') throw `Invalid numerical type code: ${q}`;
|
|
319
|
+
throw `Unknown type name alias: ${q}`;
|
|
320
|
+
});
|
|
321
|
+
},
|
|
322
|
+
|
|
323
|
+
$regex: (doc, q) => {
|
|
324
|
+
doc = downcastBSON(doc);
|
|
325
|
+
q = downcastBSON(q);
|
|
326
|
+
|
|
327
|
+
return plumeDoc(doc).some(docx => {
|
|
328
|
+
if (q instanceof RegExp) {
|
|
329
|
+
return typeof docx === 'string' ? q.test(docx) :
|
|
330
|
+
(docx instanceof RegExp &&
|
|
331
|
+
docx.source === q.source &&
|
|
332
|
+
docx.flags === q.flags);
|
|
333
|
+
}
|
|
228
334
|
|
|
229
|
-
|
|
335
|
+
if (typeof q === 'string') {
|
|
336
|
+
return typeof docx === 'string' &&
|
|
337
|
+
!!docx.match(q);
|
|
338
|
+
}
|
|
230
339
|
|
|
231
|
-
|
|
340
|
+
throw '$regex has to be a string or a regex';
|
|
341
|
+
});
|
|
342
|
+
},
|
|
343
|
+
|
|
344
|
+
$exists: (doc, q) => {
|
|
345
|
+
return q ? doc !== undefined : doc === undefined;
|
|
346
|
+
},
|
|
347
|
+
|
|
348
|
+
$ne: (doc, q) => !FilterUtils.$eq(doc, q),
|
|
349
|
+
|
|
350
|
+
$text: (parent, q) => {
|
|
351
|
+
guardObject({
|
|
352
|
+
$search: GuardSignal.STRING,
|
|
353
|
+
$field: t => Validator.STRING(t) || (t.length && niceGuard(guardArray(GuardSignal.STRING), t)),
|
|
354
|
+
$caseSensitive: t => t === undefined || Validator.BOOLEAN(t)
|
|
355
|
+
}).validate(q);
|
|
356
|
+
let { $field, $search, $caseSensitive } = q;
|
|
357
|
+
|
|
358
|
+
$field = (Array.isArray($field) ? $field : [$field]).map(v => {
|
|
359
|
+
const f = getLodash({ ...parent }, v);
|
|
360
|
+
return typeof f === 'string' ? f : '';
|
|
361
|
+
}).join(' ');
|
|
362
|
+
|
|
363
|
+
if (!$caseSensitive) {
|
|
364
|
+
$field = $field.toLowerCase();
|
|
365
|
+
$search = $search.toLowerCase();
|
|
366
|
+
}
|
|
232
367
|
|
|
233
|
-
|
|
368
|
+
return $field.includes($search);
|
|
369
|
+
},
|
|
234
370
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
371
|
+
$geoIntersects: (doc, q) => {
|
|
372
|
+
if (
|
|
373
|
+
!niceGuard({
|
|
374
|
+
$geometry: {
|
|
375
|
+
type: 'Point',
|
|
376
|
+
coordinates: COORDINATE_GUARD
|
|
377
|
+
}
|
|
378
|
+
}, q) &&
|
|
379
|
+
!niceGuard({
|
|
380
|
+
$geometry: {
|
|
381
|
+
type: 'LineString',
|
|
382
|
+
coordinates: [COORDINATE_GUARD, COORDINATE_GUARD]
|
|
383
|
+
}
|
|
384
|
+
}, q) &&
|
|
385
|
+
!niceGuard({
|
|
386
|
+
$geometry: {
|
|
387
|
+
type: 'Polygon',
|
|
388
|
+
coordinates: guardArray(guardArray(COORDINATE_GUARD))
|
|
389
|
+
}
|
|
390
|
+
}, q) &&
|
|
391
|
+
!niceGuard({
|
|
392
|
+
$geometry: {
|
|
393
|
+
type: 'MultiPoint',
|
|
394
|
+
coordinates: guardArray(COORDINATE_GUARD)
|
|
395
|
+
}
|
|
396
|
+
}, q) &&
|
|
397
|
+
!niceGuard({
|
|
398
|
+
$geometry: {
|
|
399
|
+
type: 'MultiLineString',
|
|
400
|
+
coordinates: guardArray([COORDINATE_GUARD, COORDINATE_GUARD])
|
|
401
|
+
}
|
|
402
|
+
}, q) &&
|
|
403
|
+
!niceGuard({
|
|
404
|
+
$geometry: {
|
|
405
|
+
type: 'MultiPolygon',
|
|
406
|
+
coordinates: guardArray(guardArray(guardArray(COORDINATE_GUARD)))
|
|
407
|
+
}
|
|
408
|
+
}, q)
|
|
409
|
+
) throw `unknown operator: ${q}`;
|
|
241
410
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
411
|
+
try {
|
|
412
|
+
return booleanIntersects(doc, q.$geometry);
|
|
413
|
+
} catch (_) {
|
|
414
|
+
return false;
|
|
415
|
+
}
|
|
416
|
+
},
|
|
417
|
+
$geoWithin: (doc, q) => {
|
|
418
|
+
const { $box, $geometry, $center, $centerSphere, $polygon } = { ...q };
|
|
419
|
+
try {
|
|
420
|
+
if ($geometry) {
|
|
421
|
+
if (
|
|
422
|
+
niceGuard({
|
|
423
|
+
$geometry: {
|
|
424
|
+
type: 'Polygon',
|
|
425
|
+
coordinates: guardArray(guardArray(COORDINATE_GUARD))
|
|
246
426
|
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
return value === 'date';
|
|
257
|
-
} else if (IS_RAW_OBJECT(v)) {
|
|
258
|
-
return value === 'object';
|
|
259
|
-
}
|
|
260
|
-
return false;
|
|
427
|
+
}, q) ||
|
|
428
|
+
niceGuard({
|
|
429
|
+
$geometry: {
|
|
430
|
+
type: 'MultiPolygon',
|
|
431
|
+
coordinates: guardArray(guardArray(guardArray(COORDINATE_GUARD)))
|
|
432
|
+
}
|
|
433
|
+
}, q)
|
|
434
|
+
) {
|
|
435
|
+
return booleanWithin(doc, $geometry);
|
|
261
436
|
}
|
|
437
|
+
} else if ($box) {
|
|
438
|
+
guardObject({ $box: Array(2).fill(COORDINATE_GUARD) }).validate(q);
|
|
439
|
+
|
|
440
|
+
const [bottomLeft, topRight] = $box;
|
|
441
|
+
const boundingBox = bboxPolygon([bottomLeft[0], bottomLeft[1], topRight[0], topRight[1]]);
|
|
442
|
+
return booleanWithin(doc, boundingBox);
|
|
443
|
+
} else if ($center) {
|
|
444
|
+
guardObject({ $center: [COORDINATE_GUARD, GuardSignal.POSITIVE_NUMBER] }).validate(q);
|
|
445
|
+
|
|
446
|
+
const [center, radius] = $center;
|
|
447
|
+
return booleanWithin(doc, circle(center, radius, { units: 'kilometers' }));
|
|
448
|
+
} else if ($centerSphere) {
|
|
449
|
+
guardObject({ $centerSphere: [COORDINATE_GUARD, GuardSignal.POSITIVE_NUMBER] }).validate(q);
|
|
450
|
+
|
|
451
|
+
const [center, radius] = $centerSphere;
|
|
452
|
+
// Convert radians to km
|
|
453
|
+
return booleanWithin(doc, circle(center, radius * 6371, { units: 'kilometers' }));
|
|
454
|
+
} else if ($polygon) {
|
|
455
|
+
guardObject({ $polygon: guardArray(COORDINATE_GUARD) }).validate(q);
|
|
456
|
+
return booleanWithin(doc, polygon([$polygon]));
|
|
457
|
+
}
|
|
458
|
+
} catch (e) {
|
|
459
|
+
if (!(e instanceof GuardError)) return false;
|
|
460
|
+
}
|
|
461
|
+
throw `unknown operator: ${JSON.stringify(q)}`;
|
|
462
|
+
},
|
|
463
|
+
$near: (doc, q) => {
|
|
464
|
+
validateGeoNear(q);
|
|
465
|
+
try {
|
|
466
|
+
const { $geometry, $maxDistance, $minDistance } = q;
|
|
467
|
+
const distanceOffset = distance($geometry, doc);
|
|
468
|
+
|
|
469
|
+
if ($minDistance && distanceOffset < $minDistance) {
|
|
470
|
+
return false;
|
|
471
|
+
}
|
|
262
472
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
473
|
+
if ($maxDistance && distanceOffset > $maxDistance) {
|
|
474
|
+
return false;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
return true;
|
|
478
|
+
} catch (error) {
|
|
479
|
+
return false;
|
|
480
|
+
}
|
|
481
|
+
},
|
|
482
|
+
$nearSphere: (doc, q) => {
|
|
483
|
+
validateGeoNear(q);
|
|
484
|
+
try {
|
|
485
|
+
const { $geometry, $maxDistance, $minDistance } = q.$nearSphere;
|
|
486
|
+
const distanceOffset = distance($geometry, doc, { units: 'degrees' });
|
|
487
|
+
|
|
488
|
+
if ($minDistance && distanceOffset < $minDistance) {
|
|
489
|
+
return false;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
if ($maxDistance && distanceOffset > $maxDistance) {
|
|
493
|
+
return false;
|
|
268
494
|
}
|
|
269
|
-
} else {
|
|
270
|
-
const pathValue = getLodash(dataObj, segment.join('.'));
|
|
271
495
|
|
|
272
|
-
|
|
496
|
+
return true;
|
|
497
|
+
} catch (_) {
|
|
498
|
+
return false;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
const evaluateFilter = (data, filter = {}, parentData, level = 0) => {
|
|
504
|
+
if (!Validator.OBJECT(filter)) throw `filter must be a raw object but got ${filter}`;
|
|
505
|
+
if (!level) parentData = data;
|
|
506
|
+
|
|
507
|
+
const logics = [];
|
|
508
|
+
|
|
509
|
+
Object.entries(filter).map(([key, value]) => {
|
|
510
|
+
if (key.startsWith('$') && (key !== '$text' || !Validator.OBJECT(value)) && !level)
|
|
511
|
+
throw `unknown top level operator: ${key}`;
|
|
512
|
+
|
|
513
|
+
let thisData;
|
|
514
|
+
try {
|
|
515
|
+
thisData = data && getLodash(data, key);
|
|
516
|
+
} catch (_) { }
|
|
517
|
+
|
|
518
|
+
if (key === '$text' && !level) {
|
|
519
|
+
logics.push(FilterUtils.$text(parentData, value));
|
|
520
|
+
} else if (Validator.OBJECT(value) && !level) {
|
|
521
|
+
const valueEntrie = Object.entries(value);
|
|
522
|
+
|
|
523
|
+
if (valueEntrie.some(([k]) => k.startsWith('$'))) {
|
|
524
|
+
valueEntrie.forEach(([query, queryValue]) => {
|
|
525
|
+
if (query in FilterUtils) {
|
|
526
|
+
if (query === '$text' && level) throw '$text must be a top level operator';
|
|
527
|
+
logics.push(FilterUtils[query](thisData, queryValue));
|
|
528
|
+
} else if (query === '$not') {
|
|
529
|
+
if (Validator.OBJECT(queryValue)) {
|
|
530
|
+
logics.push(!evaluateFilter(thisData, queryValue, parentData, level + 1));
|
|
531
|
+
} else logics.push(
|
|
532
|
+
!plumeDoc(thisData).some(v => CompareBson.equal(v, queryValue, true))
|
|
533
|
+
);
|
|
534
|
+
} else throw `unknown operator: ${query}`;
|
|
535
|
+
});
|
|
536
|
+
} else if (valueEntrie.length) {
|
|
537
|
+
logics.push(evaluateFilter(thisData, value, parentData, level + 1));
|
|
538
|
+
} else {
|
|
273
539
|
logics.push(
|
|
274
|
-
|
|
275
|
-
|
|
540
|
+
Validator.OBJECT(thisData) &&
|
|
541
|
+
!Object.keys(thisData).length
|
|
276
542
|
);
|
|
277
|
-
}
|
|
543
|
+
}
|
|
544
|
+
} else {
|
|
545
|
+
logics.push(
|
|
546
|
+
plumeDoc(thisData).some(v => CompareBson.equal(v, value, true))
|
|
547
|
+
);
|
|
278
548
|
}
|
|
279
549
|
});
|
|
280
550
|
|
|
281
|
-
return !logics.
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
const checkTestEquality = (test, o) => {
|
|
285
|
-
if (test instanceof RegExp) {
|
|
286
|
-
if (typeof o === 'string') return test.test(o);
|
|
287
|
-
else return false;
|
|
288
|
-
} else return isEqual(test, o);
|
|
289
|
-
}
|
|
551
|
+
return !logics.some(v => !v);
|
|
552
|
+
};
|