@geekbears/gb-mongoose-query-parser 1.2.1
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/.prettierrc +9 -0
- package/LICENSE +21 -0
- package/README.md +420 -0
- package/lib/decorators/api-single-query.decorator.d.ts +1 -0
- package/lib/decorators/api-single-query.decorator.js +25 -0
- package/lib/decorators/api-standard-query.decorator.d.ts +6 -0
- package/lib/decorators/api-standard-query.decorator.js +50 -0
- package/lib/decorators/index.d.ts +2 -0
- package/lib/decorators/index.js +15 -0
- package/lib/index.d.ts +4 -0
- package/lib/index.js +17 -0
- package/lib/query-parser-helper.d.ts +70 -0
- package/lib/query-parser-helper.js +235 -0
- package/lib/query-parser.d.ts +96 -0
- package/lib/query-parser.js +397 -0
- package/lib/test.spec.d.ts +1 -0
- package/lib/test.spec.js +211 -0
- package/lib/types/index.d.ts +2 -0
- package/lib/types/index.js +15 -0
- package/lib/types/parser-options.type.d.ts +18 -0
- package/lib/types/parser-options.type.js +3 -0
- package/lib/types/query-options.type.d.ts +15 -0
- package/lib/types/query-options.type.js +3 -0
- package/package.json +57 -0
- package/tslint.json +62 -0
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MongooseQueryParser = void 0;
|
|
4
|
+
var Moment = require("moment");
|
|
5
|
+
var qs = require("querystring");
|
|
6
|
+
var _ = require("lodash");
|
|
7
|
+
var MongooseQueryParser = /** @class */ (function () {
|
|
8
|
+
function MongooseQueryParser(options) {
|
|
9
|
+
var _this = this;
|
|
10
|
+
if (options === void 0) { options = {}; }
|
|
11
|
+
this.options = options;
|
|
12
|
+
this.defaultDateFormat = [Moment.ISO_8601];
|
|
13
|
+
this.builtInCaster = {
|
|
14
|
+
string: function (val) { return String(val); },
|
|
15
|
+
date: function (val) {
|
|
16
|
+
var m = Moment(val, _this.options.dateFormat);
|
|
17
|
+
if (m.isValid()) {
|
|
18
|
+
return m.toDate();
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
throw new Error("Invalid date string: [" + val + "]");
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
this.operators = [
|
|
26
|
+
{ operator: 'select', method: this.castSelect, defaultKey: 'select' },
|
|
27
|
+
{ operator: 'populate', method: this.castPopulate, defaultKey: 'populate' },
|
|
28
|
+
{ operator: 'sort', method: this.castSort, defaultKey: 'sort' },
|
|
29
|
+
{ operator: 'skip', method: this.castSkip, defaultKey: 'skip' },
|
|
30
|
+
{ operator: 'limit', method: this.castLimit, defaultKey: 'limit' },
|
|
31
|
+
{ operator: 'filter', method: this.castFilter, defaultKey: 'filter' },
|
|
32
|
+
{ operator: 'deepPopulate', method: this.castDeepPopulate, defaultKey: 'deepPopulate' },
|
|
33
|
+
{ operator: 'lean', method: this.castLean, defaultKey: 'lean' },
|
|
34
|
+
];
|
|
35
|
+
// add default date format as ISO_8601
|
|
36
|
+
this.options.dateFormat = options.dateFormat || this.defaultDateFormat;
|
|
37
|
+
// add builtInCaster
|
|
38
|
+
this.options.casters = Object.assign(this.builtInCaster, options.casters);
|
|
39
|
+
// build blacklist
|
|
40
|
+
this.options.blacklist = options.blacklist || [];
|
|
41
|
+
this.operators.forEach(function (_a) {
|
|
42
|
+
var operator = _a.operator, method = _a.method, defaultKey = _a.defaultKey;
|
|
43
|
+
_this.options.blacklist.push(_this.options[operator + "Key"] || defaultKey);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* parses query string/object to Mongoose friendly query object/QueryOptions
|
|
48
|
+
* @param {string | Object} query
|
|
49
|
+
* @param {Object} [context]
|
|
50
|
+
* @return {QueryOptions}
|
|
51
|
+
*/
|
|
52
|
+
MongooseQueryParser.prototype.parse = function (query, context) {
|
|
53
|
+
var _this = this;
|
|
54
|
+
var params = _.isString(query) ? qs.parse(query) : query;
|
|
55
|
+
var options = this.options;
|
|
56
|
+
var result = {};
|
|
57
|
+
this.operators.forEach(function (_a) {
|
|
58
|
+
var operator = _a.operator, method = _a.method, defaultKey = _a.defaultKey;
|
|
59
|
+
var key = options[operator + "Key"] || defaultKey;
|
|
60
|
+
var value = params[key];
|
|
61
|
+
if (value || operator === 'filter') {
|
|
62
|
+
result[operator] = method.call(_this, value, params);
|
|
63
|
+
}
|
|
64
|
+
}, this);
|
|
65
|
+
result = this.parsePredefinedQuery(result, context);
|
|
66
|
+
return result;
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* parses string to typed values
|
|
70
|
+
* This methods will apply auto type casting on Number, RegExp, Date, Boolean and null
|
|
71
|
+
* Also, it will apply defined casters in given options of the instance
|
|
72
|
+
* @param {string} value
|
|
73
|
+
* @param {string} key
|
|
74
|
+
* @return {any} typed value
|
|
75
|
+
*/
|
|
76
|
+
MongooseQueryParser.prototype.parseValue = function (value, key) {
|
|
77
|
+
var me = this;
|
|
78
|
+
var options = this.options;
|
|
79
|
+
// Apply casters
|
|
80
|
+
// Match type casting operators like: string(true), _caster(123), $('test')
|
|
81
|
+
var casters = options.casters;
|
|
82
|
+
var casting = value.match(/^([a-zA-Z_$][0-9a-zA-Z_$]*)\((.*)\)$/);
|
|
83
|
+
if (casting && casters[casting[1]]) {
|
|
84
|
+
return casters[casting[1]](casting[2]);
|
|
85
|
+
}
|
|
86
|
+
// Apply casters per params
|
|
87
|
+
if (key && options.castParams && options.castParams[key] && casters[options.castParams[key]]) {
|
|
88
|
+
return casters[options.castParams[key]](value);
|
|
89
|
+
}
|
|
90
|
+
// cast array
|
|
91
|
+
if (value.includes(',')) {
|
|
92
|
+
return value.split(',').map(function (val) { return me.parseValue(val, key); });
|
|
93
|
+
}
|
|
94
|
+
// Apply type casting for Number, RegExp, Date, Boolean and null
|
|
95
|
+
// Match regex operators like /foo_\d+/i
|
|
96
|
+
var regex = value.match(/^\/(.*)\/(i?)$/);
|
|
97
|
+
if (regex) {
|
|
98
|
+
return new RegExp(regex[1], regex[2]);
|
|
99
|
+
}
|
|
100
|
+
// Match boolean values
|
|
101
|
+
if (value === 'true') {
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
if (value === 'false') {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
// Match null
|
|
108
|
+
if (value === 'null') {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
// Match numbers (string padded with zeros are not numbers)
|
|
112
|
+
if (value !== '' && !isNaN(Number(value)) && !/^0[0-9]+/.test(value)) {
|
|
113
|
+
return Number(value);
|
|
114
|
+
}
|
|
115
|
+
// Match dates
|
|
116
|
+
var m = Moment(value, options.dateFormat);
|
|
117
|
+
if (m.isValid()) {
|
|
118
|
+
return m.toDate();
|
|
119
|
+
}
|
|
120
|
+
return value;
|
|
121
|
+
};
|
|
122
|
+
MongooseQueryParser.prototype.castFilter = function (filter, params) {
|
|
123
|
+
var _this = this;
|
|
124
|
+
var options = this.options;
|
|
125
|
+
var parsedFilter = filter ? this.parseFilter(filter) : {};
|
|
126
|
+
return Object.keys(params)
|
|
127
|
+
.map(function (val) {
|
|
128
|
+
var join = params[val] ? val + "=" + params[val] : val;
|
|
129
|
+
// Separate key, operators and value
|
|
130
|
+
var _a = join.match(/(!?)([^><!=]+)([><]=?|!?=|)(.*)/), prefix = _a[1], key = _a[2], op = _a[3], value = _a[4];
|
|
131
|
+
return { prefix: prefix, key: key, op: _this.parseOperator(op), value: _this.parseValue(value, key) };
|
|
132
|
+
})
|
|
133
|
+
.filter(function (_a) {
|
|
134
|
+
var key = _a.key;
|
|
135
|
+
return options.blacklist.indexOf(key) === -1;
|
|
136
|
+
})
|
|
137
|
+
.reduce(function (result, _a) {
|
|
138
|
+
var prefix = _a.prefix, key = _a.key, op = _a.op, value = _a.value;
|
|
139
|
+
if (!result[key]) {
|
|
140
|
+
result[key] = {};
|
|
141
|
+
}
|
|
142
|
+
if (Array.isArray(value)) {
|
|
143
|
+
result[key][op === '$ne' ? '$nin' : '$in'] = value;
|
|
144
|
+
}
|
|
145
|
+
else if (op === '$exists') {
|
|
146
|
+
result[key][op] = prefix !== '!';
|
|
147
|
+
}
|
|
148
|
+
else if (op === '$eq') {
|
|
149
|
+
result[key] = value;
|
|
150
|
+
}
|
|
151
|
+
else if (op === '$ne' && typeof value === 'object') {
|
|
152
|
+
result[key].$not = value;
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
result[key][op] = value;
|
|
156
|
+
}
|
|
157
|
+
return result;
|
|
158
|
+
}, parsedFilter);
|
|
159
|
+
};
|
|
160
|
+
MongooseQueryParser.prototype.parseFilter = function (filter) {
|
|
161
|
+
try {
|
|
162
|
+
if (typeof filter === 'object') {
|
|
163
|
+
return filter;
|
|
164
|
+
}
|
|
165
|
+
return JSON.parse(filter);
|
|
166
|
+
}
|
|
167
|
+
catch (err) {
|
|
168
|
+
throw new Error("Invalid JSON string: " + filter);
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
MongooseQueryParser.prototype.parseOperator = function (operator) {
|
|
172
|
+
if (operator === '=') {
|
|
173
|
+
return '$eq';
|
|
174
|
+
}
|
|
175
|
+
else if (operator === '!=') {
|
|
176
|
+
return '$ne';
|
|
177
|
+
}
|
|
178
|
+
else if (operator === '>') {
|
|
179
|
+
return '$gt';
|
|
180
|
+
}
|
|
181
|
+
else if (operator === '>=') {
|
|
182
|
+
return '$gte';
|
|
183
|
+
}
|
|
184
|
+
else if (operator === '<') {
|
|
185
|
+
return '$lt';
|
|
186
|
+
}
|
|
187
|
+
else if (operator === '<=') {
|
|
188
|
+
return '$lte';
|
|
189
|
+
}
|
|
190
|
+
else if (!operator) {
|
|
191
|
+
return '$exists';
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
/**
|
|
195
|
+
* cast select query to object like:
|
|
196
|
+
* select=a,b or select=-a,-b
|
|
197
|
+
* =>
|
|
198
|
+
* {select: { a: 1, b: 1 }} or {select: { a: 0, b: 0 }}
|
|
199
|
+
* @param val
|
|
200
|
+
*/
|
|
201
|
+
MongooseQueryParser.prototype.castSelect = function (val) {
|
|
202
|
+
var fields = this.parseUnaries(val, { plus: 1, minus: 0 });
|
|
203
|
+
/*
|
|
204
|
+
From the MongoDB documentation:
|
|
205
|
+
"A projection cannot contain both include and exclude specifications, except for the exclusion of the _id field."
|
|
206
|
+
*/
|
|
207
|
+
var hasMixedValues = Object.keys(fields).reduce(function (set, key) {
|
|
208
|
+
if (key !== '_id') {
|
|
209
|
+
set.add(fields[key]);
|
|
210
|
+
}
|
|
211
|
+
return set;
|
|
212
|
+
}, new Set()).size > 1;
|
|
213
|
+
if (hasMixedValues) {
|
|
214
|
+
Object.keys(fields).forEach(function (key) {
|
|
215
|
+
if (fields[key] === 1) {
|
|
216
|
+
delete fields[key];
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
return fields;
|
|
221
|
+
};
|
|
222
|
+
/**
|
|
223
|
+
* cast populate query to object like:
|
|
224
|
+
* populate=field1.p1,field1.p2,field2
|
|
225
|
+
* =>
|
|
226
|
+
* [{path: 'field1', select: 'p1 p2'}, {path: 'field2'}]
|
|
227
|
+
* @param val
|
|
228
|
+
*/
|
|
229
|
+
MongooseQueryParser.prototype.castPopulate = function (val) {
|
|
230
|
+
return val
|
|
231
|
+
.split(',')
|
|
232
|
+
.map(function (qry) {
|
|
233
|
+
var _a = qry.split('.', 2), p = _a[0], s = _a[1];
|
|
234
|
+
return s ? { path: p, select: s } : { path: p };
|
|
235
|
+
})
|
|
236
|
+
.reduce(function (prev, curr, key) {
|
|
237
|
+
// consolidate population array
|
|
238
|
+
var path = curr.path;
|
|
239
|
+
var select = curr.select;
|
|
240
|
+
var found = false;
|
|
241
|
+
prev.forEach(function (e) {
|
|
242
|
+
if (e.path === path) {
|
|
243
|
+
found = true;
|
|
244
|
+
if (select) {
|
|
245
|
+
e.select = e.select ? e.select + ' ' + select : select;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
if (!found) {
|
|
250
|
+
prev.push(curr);
|
|
251
|
+
}
|
|
252
|
+
return prev;
|
|
253
|
+
}, []);
|
|
254
|
+
};
|
|
255
|
+
/**
|
|
256
|
+
* cast deep populate query to object like:
|
|
257
|
+
* populate={"path":"any", select="deep"}
|
|
258
|
+
* =>
|
|
259
|
+
* {path: 'field1', select: 'deep'}
|
|
260
|
+
* @param deepPopulate
|
|
261
|
+
*/
|
|
262
|
+
MongooseQueryParser.prototype.castDeepPopulate = function (deepPopulate) {
|
|
263
|
+
return this.parseDeepPopulate(deepPopulate);
|
|
264
|
+
};
|
|
265
|
+
MongooseQueryParser.prototype.parseDeepPopulate = function (deepPopulate) {
|
|
266
|
+
try {
|
|
267
|
+
if (typeof deepPopulate === 'object') {
|
|
268
|
+
return deepPopulate;
|
|
269
|
+
}
|
|
270
|
+
return JSON.parse(deepPopulate);
|
|
271
|
+
}
|
|
272
|
+
catch (err) {
|
|
273
|
+
throw new Error("Invalid JSON string: " + deepPopulate);
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
/**
|
|
277
|
+
* cast sort query to object like
|
|
278
|
+
* sort=-a,b
|
|
279
|
+
* =>
|
|
280
|
+
* {sort: {a: -1, b: 1}}
|
|
281
|
+
* @param sort
|
|
282
|
+
*/
|
|
283
|
+
MongooseQueryParser.prototype.castSort = function (sort) {
|
|
284
|
+
return this.parseUnaries(sort);
|
|
285
|
+
};
|
|
286
|
+
/**
|
|
287
|
+
* Map/reduce helper to transform list of unaries
|
|
288
|
+
* like '+a,-b,c' to {a: 1, b: -1, c: 1}
|
|
289
|
+
*/
|
|
290
|
+
MongooseQueryParser.prototype.parseUnaries = function (unaries, values) {
|
|
291
|
+
if (values === void 0) { values = { plus: 1, minus: -1 }; }
|
|
292
|
+
var unariesAsArray = _.isString(unaries) ? unaries.split(',') : unaries;
|
|
293
|
+
return unariesAsArray
|
|
294
|
+
.map(function (x) { return x.match(/^(\+|-)?(.*)/); })
|
|
295
|
+
.reduce(function (result, _a) {
|
|
296
|
+
var val = _a[1], key = _a[2];
|
|
297
|
+
result[key.trim()] = val === '-' ? values.minus : values.plus;
|
|
298
|
+
return result;
|
|
299
|
+
}, {});
|
|
300
|
+
};
|
|
301
|
+
/**
|
|
302
|
+
* cast skip query to object like
|
|
303
|
+
* skip=100
|
|
304
|
+
* =>
|
|
305
|
+
* {skip: 100}
|
|
306
|
+
* @param skip
|
|
307
|
+
*/
|
|
308
|
+
MongooseQueryParser.prototype.castSkip = function (skip) {
|
|
309
|
+
return Number(skip);
|
|
310
|
+
};
|
|
311
|
+
/**
|
|
312
|
+
* cast limit query to object like
|
|
313
|
+
* limit=10
|
|
314
|
+
* =>
|
|
315
|
+
* {limit: 10}
|
|
316
|
+
* @param limit
|
|
317
|
+
*/
|
|
318
|
+
MongooseQueryParser.prototype.castLimit = function (limit) {
|
|
319
|
+
return Number(limit);
|
|
320
|
+
};
|
|
321
|
+
/**
|
|
322
|
+
* cast lean option like
|
|
323
|
+
* lean=true
|
|
324
|
+
* =>
|
|
325
|
+
* {lean: true}
|
|
326
|
+
* @param lean
|
|
327
|
+
*/
|
|
328
|
+
MongooseQueryParser.prototype.castLean = function (lean) {
|
|
329
|
+
return lean === 'true' ? true : false;
|
|
330
|
+
};
|
|
331
|
+
/**
|
|
332
|
+
* transform predefined query strings defined in query string to the actual query object out of the given context
|
|
333
|
+
* @param query
|
|
334
|
+
* @param context
|
|
335
|
+
*/
|
|
336
|
+
MongooseQueryParser.prototype.parsePredefinedQuery = function (query, context) {
|
|
337
|
+
if (context) {
|
|
338
|
+
// check if given string is the format as predefined query i.e. ${query}
|
|
339
|
+
var _match_1 = function (str) {
|
|
340
|
+
var reg = /^\$\{([a-zA-Z_$][0-9a-zA-Z_$]*)\}$/;
|
|
341
|
+
var match = str.match(reg);
|
|
342
|
+
var val = undefined;
|
|
343
|
+
if (match) {
|
|
344
|
+
val = _.property(match[1])(context);
|
|
345
|
+
if (val === undefined) {
|
|
346
|
+
throw new Error("No predefined query found for the provided reference [" + match[1] + "]");
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return { match: !!match, val: val };
|
|
350
|
+
};
|
|
351
|
+
var _transform_1 = function (obj) {
|
|
352
|
+
return _.reduce(obj, function (prev, curr, key) {
|
|
353
|
+
var _a, _b;
|
|
354
|
+
var val = undefined, match = undefined;
|
|
355
|
+
if (_.isString(key)) {
|
|
356
|
+
(_a = _match_1(key), match = _a.match, val = _a.val);
|
|
357
|
+
if (match) {
|
|
358
|
+
if (_.has(curr, '$exists')) {
|
|
359
|
+
// 1). as a key: {'${qry}': {$exits: true}} => {${qry object}}
|
|
360
|
+
return _.merge(prev, val);
|
|
361
|
+
}
|
|
362
|
+
else if (_.isString(val)) {
|
|
363
|
+
// 1). as a key: {'${qry}': 'something'} => {'${qry object}': 'something'}
|
|
364
|
+
key = val;
|
|
365
|
+
}
|
|
366
|
+
else {
|
|
367
|
+
throw new Error("Invalid query string at " + key);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
if (_.isString(curr)) {
|
|
372
|
+
(_b = _match_1(curr), match = _b.match, val = _b.val);
|
|
373
|
+
if (match) {
|
|
374
|
+
_.isNumber(key)
|
|
375
|
+
? prev.push(val) // 3). as an item of array: ['${qry}', ...] => [${qry object}, ...]
|
|
376
|
+
: (prev[key] = val); // 2). as a value: {prop: '${qry}'} => {prop: ${qry object}}
|
|
377
|
+
return prev;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
if (_.isObject(curr) && !_.isRegExp(curr) && !_.isDate(curr)) {
|
|
381
|
+
// iterate all props & keys recursively
|
|
382
|
+
_.isNumber(key) ? prev.push(_transform_1(curr)) : (prev[key] = _transform_1(curr));
|
|
383
|
+
}
|
|
384
|
+
else {
|
|
385
|
+
_.isNumber(key) ? prev.push(curr) : (prev[key] = curr);
|
|
386
|
+
}
|
|
387
|
+
return prev;
|
|
388
|
+
}, _.isArray(obj) ? [] : {});
|
|
389
|
+
};
|
|
390
|
+
return _transform_1(query);
|
|
391
|
+
}
|
|
392
|
+
return query;
|
|
393
|
+
};
|
|
394
|
+
return MongooseQueryParser;
|
|
395
|
+
}());
|
|
396
|
+
exports.MongooseQueryParser = MongooseQueryParser;
|
|
397
|
+
//# sourceMappingURL=query-parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/lib/test.spec.js
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
12
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
13
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
14
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
15
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
16
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
17
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
21
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
22
|
+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
23
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
24
|
+
function step(op) {
|
|
25
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
26
|
+
while (_) try {
|
|
27
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
28
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
29
|
+
switch (op[0]) {
|
|
30
|
+
case 0: case 1: t = op; break;
|
|
31
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
32
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
33
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
34
|
+
default:
|
|
35
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
36
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
37
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
38
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
39
|
+
if (t[2]) _.ops.pop();
|
|
40
|
+
_.trys.pop(); continue;
|
|
41
|
+
}
|
|
42
|
+
op = body.call(thisArg, _);
|
|
43
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
44
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
48
|
+
var mocha_1 = require("@testdeck/mocha");
|
|
49
|
+
var chai_1 = require("chai");
|
|
50
|
+
var _1 = require("./");
|
|
51
|
+
var Tester = /** @class */ (function () {
|
|
52
|
+
function Tester() {
|
|
53
|
+
}
|
|
54
|
+
Tester.prototype.generalParse = function () {
|
|
55
|
+
var parser = new _1.MongooseQueryParser();
|
|
56
|
+
var qry = 'date=2016-01-01&boolean=true&integer=10®exp=/foobar/i&null=null';
|
|
57
|
+
var parsed = parser.parse(qry);
|
|
58
|
+
chai_1.assert.isNotNull(parsed.filter);
|
|
59
|
+
chai_1.assert.isOk(parsed.filter['date'] instanceof Date);
|
|
60
|
+
chai_1.assert.isOk(parsed.filter['boolean'] === true);
|
|
61
|
+
chai_1.assert.isOk(parsed.filter['integer'] === 10);
|
|
62
|
+
chai_1.assert.isOk(parsed.filter['regexp'] instanceof RegExp);
|
|
63
|
+
chai_1.assert.isOk(parsed.filter['null'] === null);
|
|
64
|
+
};
|
|
65
|
+
Tester.prototype.generalParse2 = function () {
|
|
66
|
+
var parser = new _1.MongooseQueryParser();
|
|
67
|
+
var predefined = {
|
|
68
|
+
vip: { name: { $in: ['Google', 'Microsoft', 'NodeJs'] } },
|
|
69
|
+
sentStatus: 'sent',
|
|
70
|
+
};
|
|
71
|
+
var parsed = parser.parse('${vip}&status=${sentStatus}×tamp>2017-10-01&author.firstName=/john/i&limit=100&skip=50&sort=-timestamp&select=name&populate=children', predefined);
|
|
72
|
+
chai_1.assert.isOk(parsed.filter['status'] === predefined.sentStatus);
|
|
73
|
+
chai_1.assert.isOk(parsed.filter['name'].$in.length === 3); // checking parsing of ${vip}
|
|
74
|
+
chai_1.assert.isOk(parsed.filter['timestamp']['$gt'] instanceof Date);
|
|
75
|
+
chai_1.assert.isOk(parsed.filter['author.firstName'] instanceof RegExp);
|
|
76
|
+
chai_1.assert.isOk(parsed.limit === 100);
|
|
77
|
+
chai_1.assert.isOk(parsed.skip === 50);
|
|
78
|
+
chai_1.assert.isNotNull(parsed.sort);
|
|
79
|
+
chai_1.assert.isNotNull(parsed.select);
|
|
80
|
+
chai_1.assert.isNotNull(parsed.populate);
|
|
81
|
+
};
|
|
82
|
+
Tester.prototype.populateParse = function () {
|
|
83
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
84
|
+
var parser, qry, parsed;
|
|
85
|
+
return __generator(this, function (_a) {
|
|
86
|
+
parser = new _1.MongooseQueryParser();
|
|
87
|
+
qry = '_id=1&populate=serviceSalesOrders,customer.category,customer.name';
|
|
88
|
+
parsed = parser.parse(qry);
|
|
89
|
+
chai_1.assert.isOk(parsed.populate.length === 2);
|
|
90
|
+
return [2 /*return*/];
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
Tester.prototype.builtInCastersTest = function () {
|
|
95
|
+
var parser = new _1.MongooseQueryParser();
|
|
96
|
+
var qry = 'key1=string(10)&key2=date(2017-10-01)&key3=string(null)';
|
|
97
|
+
var parsed = parser.parse(qry);
|
|
98
|
+
chai_1.assert.isOk(typeof parsed.filter['key1'] === 'string');
|
|
99
|
+
chai_1.assert.isOk(parsed.filter['key2'] instanceof Date);
|
|
100
|
+
chai_1.assert.isOk(typeof parsed.filter['key3'] === 'string');
|
|
101
|
+
};
|
|
102
|
+
Tester.prototype.parseCaster = function () {
|
|
103
|
+
var parser = new _1.MongooseQueryParser({
|
|
104
|
+
casters: { $: function (val) { return '$' + val; } },
|
|
105
|
+
});
|
|
106
|
+
var qry = '_id=$(1)';
|
|
107
|
+
var parsed = parser.parse(qry);
|
|
108
|
+
chai_1.assert.equal('$1', parsed.filter['_id']);
|
|
109
|
+
};
|
|
110
|
+
Tester.prototype.parseJsonFilter = function () {
|
|
111
|
+
var parser = new _1.MongooseQueryParser();
|
|
112
|
+
var obj = {
|
|
113
|
+
$or: [{ key1: 'value1' }, { key2: 'value2' }],
|
|
114
|
+
};
|
|
115
|
+
var qry = "filter=" + JSON.stringify(obj) + "&name=Google";
|
|
116
|
+
var parsed = parser.parse(qry);
|
|
117
|
+
chai_1.assert.isArray(parsed.filter['$or']);
|
|
118
|
+
chai_1.assert.isOk(parsed.filter['name'] === 'Google');
|
|
119
|
+
};
|
|
120
|
+
Tester.prototype.parsePredefined = function () {
|
|
121
|
+
var parser = new _1.MongooseQueryParser();
|
|
122
|
+
var preDefined = {
|
|
123
|
+
isActive: { status: { $in: ['In Progress', 'Pending'] } },
|
|
124
|
+
vip: ['KFC', 'Google', 'MS'],
|
|
125
|
+
secret: 'my_secret',
|
|
126
|
+
mykey: 'realkey',
|
|
127
|
+
};
|
|
128
|
+
// test predefined query as key
|
|
129
|
+
var qry = '${isActive}&name&${mykey}=1';
|
|
130
|
+
var parsed = parser.parse(qry, preDefined);
|
|
131
|
+
chai_1.assert.isNotNull(parsed.filter['status']);
|
|
132
|
+
chai_1.assert.isOk(!parsed.filter['${isActive}']);
|
|
133
|
+
chai_1.assert.isOk(parsed.filter['realkey'] === 1);
|
|
134
|
+
// test predefined query as value
|
|
135
|
+
qry = 'secret=${secret}';
|
|
136
|
+
parsed = parser.parse(qry, preDefined);
|
|
137
|
+
chai_1.assert.isOk(parsed.filter['secret'] === preDefined.secret);
|
|
138
|
+
// test predefined query in json
|
|
139
|
+
qry = 'filter={"$and": ["${isActive}", {"customer": "VDF"}]}';
|
|
140
|
+
parsed = parser.parse(qry, preDefined);
|
|
141
|
+
chai_1.assert.isNotNull(parsed.filter['$and'][0].status);
|
|
142
|
+
};
|
|
143
|
+
Tester.prototype.parseLean = function () {
|
|
144
|
+
var parser = new _1.MongooseQueryParser();
|
|
145
|
+
var qryTrue = 'lean=true';
|
|
146
|
+
var qryFalse = 'lean=false';
|
|
147
|
+
var qryUndefined = 'lean=';
|
|
148
|
+
var qryNotBoolean = 'lean=any';
|
|
149
|
+
var parsedTrue = parser.parse(qryTrue);
|
|
150
|
+
var parsedFalse = parser.parse(qryFalse);
|
|
151
|
+
var parsedUndefined = parser.parse(qryUndefined);
|
|
152
|
+
var parsedNotBoolean = parser.parse(qryNotBoolean);
|
|
153
|
+
chai_1.assert.isTrue(parsedTrue.lean);
|
|
154
|
+
chai_1.assert.isFalse(parsedFalse.lean);
|
|
155
|
+
chai_1.assert.notNestedProperty(parsedUndefined, 'lean');
|
|
156
|
+
chai_1.assert.isFalse(parsedNotBoolean.lean);
|
|
157
|
+
};
|
|
158
|
+
__decorate([
|
|
159
|
+
(0, mocha_1.test)('should parse general query'),
|
|
160
|
+
__metadata("design:type", Function),
|
|
161
|
+
__metadata("design:paramtypes", []),
|
|
162
|
+
__metadata("design:returntype", void 0)
|
|
163
|
+
], Tester.prototype, "generalParse", null);
|
|
164
|
+
__decorate([
|
|
165
|
+
(0, mocha_1.test)('should parse query with string templates'),
|
|
166
|
+
__metadata("design:type", Function),
|
|
167
|
+
__metadata("design:paramtypes", []),
|
|
168
|
+
__metadata("design:returntype", void 0)
|
|
169
|
+
], Tester.prototype, "generalParse2", null);
|
|
170
|
+
__decorate([
|
|
171
|
+
(0, mocha_1.test)('should parse populate query'),
|
|
172
|
+
__metadata("design:type", Function),
|
|
173
|
+
__metadata("design:paramtypes", []),
|
|
174
|
+
__metadata("design:returntype", Promise)
|
|
175
|
+
], Tester.prototype, "populateParse", null);
|
|
176
|
+
__decorate([
|
|
177
|
+
(0, mocha_1.test)('should parse built in casters'),
|
|
178
|
+
__metadata("design:type", Function),
|
|
179
|
+
__metadata("design:paramtypes", []),
|
|
180
|
+
__metadata("design:returntype", void 0)
|
|
181
|
+
], Tester.prototype, "builtInCastersTest", null);
|
|
182
|
+
__decorate([
|
|
183
|
+
(0, mocha_1.test)('should parse custom caster'),
|
|
184
|
+
__metadata("design:type", Function),
|
|
185
|
+
__metadata("design:paramtypes", []),
|
|
186
|
+
__metadata("design:returntype", void 0)
|
|
187
|
+
], Tester.prototype, "parseCaster", null);
|
|
188
|
+
__decorate([
|
|
189
|
+
(0, mocha_1.test)('should parse json filter'),
|
|
190
|
+
__metadata("design:type", Function),
|
|
191
|
+
__metadata("design:paramtypes", []),
|
|
192
|
+
__metadata("design:returntype", void 0)
|
|
193
|
+
], Tester.prototype, "parseJsonFilter", null);
|
|
194
|
+
__decorate([
|
|
195
|
+
(0, mocha_1.test)('should parse predefined query objects'),
|
|
196
|
+
__metadata("design:type", Function),
|
|
197
|
+
__metadata("design:paramtypes", []),
|
|
198
|
+
__metadata("design:returntype", void 0)
|
|
199
|
+
], Tester.prototype, "parsePredefined", null);
|
|
200
|
+
__decorate([
|
|
201
|
+
(0, mocha_1.test)('should parse lean'),
|
|
202
|
+
__metadata("design:type", Function),
|
|
203
|
+
__metadata("design:paramtypes", []),
|
|
204
|
+
__metadata("design:returntype", void 0)
|
|
205
|
+
], Tester.prototype, "parseLean", null);
|
|
206
|
+
Tester = __decorate([
|
|
207
|
+
(0, mocha_1.suite)('Tester')
|
|
208
|
+
], Tester);
|
|
209
|
+
return Tester;
|
|
210
|
+
}());
|
|
211
|
+
//# sourceMappingURL=test.spec.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
|
5
|
+
}) : (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
o[k2] = m[k];
|
|
8
|
+
}));
|
|
9
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
10
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
11
|
+
};
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
__exportStar(require("./parser-options.type"), exports);
|
|
14
|
+
__exportStar(require("./query-options.type"), exports);
|
|
15
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface ParserOptions {
|
|
2
|
+
dateFormat?: any;
|
|
3
|
+
blacklist?: string[];
|
|
4
|
+
casters?: {
|
|
5
|
+
[key: string]: (val: string) => any;
|
|
6
|
+
};
|
|
7
|
+
castParams?: {
|
|
8
|
+
[key: string]: string;
|
|
9
|
+
};
|
|
10
|
+
selectKey?: string;
|
|
11
|
+
populateKey?: string;
|
|
12
|
+
deepPopulateKey?: string;
|
|
13
|
+
sortKey?: string;
|
|
14
|
+
skipKey?: string;
|
|
15
|
+
limitKey?: string;
|
|
16
|
+
filterKey?: string;
|
|
17
|
+
leanKey?: string;
|
|
18
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { SortOrder } from 'mongoose';
|
|
2
|
+
export interface QueryOptions {
|
|
3
|
+
filter: Object;
|
|
4
|
+
sort?: string | {
|
|
5
|
+
[key: string]: SortOrder | {
|
|
6
|
+
$meta: 'textScore';
|
|
7
|
+
};
|
|
8
|
+
} | [string, SortOrder][] | undefined | null;
|
|
9
|
+
limit?: number;
|
|
10
|
+
skip?: number;
|
|
11
|
+
select?: string | Object;
|
|
12
|
+
populate?: string | Object;
|
|
13
|
+
deepPopulate?: string | Object;
|
|
14
|
+
lean?: boolean;
|
|
15
|
+
}
|