-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
executable file
·501 lines (415 loc) · 15.4 KB
/
index.js
1
2
3
4
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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
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
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
// Require dependencies
const MQuery = require('mquery');
const { MongoClient, ObjectId } = require('mongodb');
/**
* MongoDb database plug class
*/
class EdenModelMongo {
/**
* Construct MongoDb database plug class
*/
constructor(config) {
// Store config
this._config = config;
// Bind builder to self
this.build = this.build.bind(this);
// Bind raw methods to self
this.getRawDb = this.getRawDb.bind(this);
this.getRawTable = this.getRawTable.bind(this);
this.getRawCursor = this.getRawCursor.bind(this);
// Bind internal methods to self
this._queryToCursor = this._queryToCursor.bind(this);
// Bind public methods to self
this.raw = this.raw.bind(this);
this.find = this.find.bind(this);
this.count = this.count.bind(this);
this.remove = this.remove.bind(this);
this.insert = this.insert.bind(this);
this.findOne = this.findOne.bind(this);
this.findById = this.findById.bind(this);
this.findByIds = this.findByIds.bind(this);
this.removeById = this.removeById.bind(this);
this.replaceById = this.replaceById.bind(this);
// Start building internal connections and store promise
this.building = this.build();
}
/**
* Async method that resolves on internal API build completion
*/
async build() {
// create client
this._client = await new Promise((resolve, reject) => {
// connect
MongoClient.connect(this._config.url, (err, client) => {
// reject
if (err) return reject(err);
// resolve client
resolve(client);
});
});
// Internally store db by name provided in config
this._db = this._client.db(this._config.db);
}
/**
* Prepare database for new collection of provided collection ID
*/
initCollection() {
// MongoDB just works, we dont need to do anything
}
async createIndex(collectionId, name, indexes) {
await this.building;
// TODO: please standardization i am suicidal
try {
await this._db.collection(collectionId).createIndex(indexes, {
name,
});
} catch (err) { /* who care */ }
}
/**
* Return a copy of a raw cursor by provided collectionId
*/
async getRawCursor(collectionId) {
await this.building;
return MQuery(this._db.collection(collectionId));
}
/**
* Return a copy of a raw table by provided collectionId
*/
async getRawTable(collectionId) {
await this.building;
return this._db.collection(collectionId);
}
/**
* Return a copy of the raw internal database
*/
async getRawDb() {
await this.building;
return this._db;
}
/**
* Convert a standard constructed query to an MQuery cursor
*/
_queryToCursor(cursor, query) {
let neBuf = [];
// Iterate over all parts of the query
for (const [queryPtKey, queryPt] of query.pts.entries()) {
if (queryPt.type === 'filter') {
const filter = Object.assign({}, queryPt.filter);
// Iterate all values in the filter object
for (const [filterKey, filterVal] of Object.entries(filter)) {
// If value data is a RegExp match, handle seperately
if (filterVal instanceof RegExp) {
// Delete by key from filter object
delete filter[filterKey];
// Apply key and regex match to `where` and `regex` cursor method
cursor = cursor.where(filterKey).regex(filterVal);
}
}
// Apply filter object to `where` cursor method
cursor = cursor.where(filter);
} else if (queryPt.type === 'elem') {
if (typeof queryPt.filter !== 'object') {
// Apply supplied matches array to `where` and `elemMatch` cursor method
cursor = cursor.where(queryPt.arrKey).elemMatch({
$eq : queryPt.filter,
});
} else {
// Apply supplied matches array to `where` and `elemMatch` cursor method
cursor = cursor.where(queryPt.arrKey).elemMatch(queryPt.filter);
}
} else if (queryPt.type === 'ne') {
const nextPt = query.pts[queryPtKey + 1];
if (nextPt != null && nextPt.type === 'ne' && nextPt.key === queryPt.key) {
neBuf.push(queryPt.val);
} else if (neBuf.length > 0) {
// Apply supplied negative match and previous
// matches array to `where` and `nin` cursor method
cursor = cursor.where(queryPt.key).nin([...neBuf, queryPt.val]);
neBuf = [];
} else {
// Apply supplied negative to `where` and `ne` cursor method
cursor = cursor.where(queryPt.key).ne(queryPt.val);
}
} else if (queryPt.type === 'nin') {
// Apply supplied values array to `where` and `nin` cursor method
cursor = cursor.where(queryPt.key).nin(queryPt.vals);
} else if (queryPt.type === 'in') {
// Apply supplied values array to `where` and `in` cursor method
cursor = cursor.where(queryPt.key).in(queryPt.vals);
} else if (queryPt.type === 'whereOr') {
// Apply supplied matches array to `or` cursor method
cursor = cursor.or(queryPt.matches);
} else if (queryPt.type === 'whereAnd') {
// Apply supplied matches array to `and` cursor method
cursor = cursor.and(queryPt.matches);
} else if (queryPt.type === 'limit') {
// Apply amt to `limit` cursor method
cursor = cursor.limit(queryPt.limitAmount);
} else if (queryPt.type === 'skip') {
// Apply amt to `skip` cursor method
cursor = cursor.skip(queryPt.skipAmount);
} else if (queryPt.type === 'sort') {
// Apply custom sort filter object to `sort` cursor method
cursor = cursor.sort({ [queryPt.sortKey] : queryPt.desc ? -1 : 1 });
} else if (queryPt.type === 'gt') {
// Apply key and max to `where` and `gt` cursor method
cursor = cursor.where(queryPt.key).gt(queryPt.min);
} else if (queryPt.type === 'lt') {
// Apply key and max to `where` and `lt` cursor method
cursor = cursor.where(queryPt.key).lt(queryPt.max);
} else if (queryPt.type === 'gte') {
// Apply key and max to `where` and `gte` cursor method
cursor = cursor.where(queryPt.key).gte(queryPt.min);
} else if (queryPt.type === 'lte') {
// Apply key and max to `where` and `lte` cursor method
cursor = cursor.where(queryPt.key).lte(queryPt.max);
}
}
// Return the fully constructed cursor
return cursor;
}
/**
* Find Model data by collection ID and Model ID
*/
async findById(collectionId, id) {
// Wait for building to finish
await this.building;
// Construct MQuery cursor from collection ID
const mQuery = MQuery(this._db.collection(collectionId));
// Find single Model instance data by provided ID
const rawModelRes = await mQuery.findOne({ _id : ObjectId(id) }).exec();
// If no Model instance data found, return null
if (rawModelRes == null) {
return null;
}
// Get internal ID from returned data
const fetchedModelId = rawModelRes._id.toString();
// Delete internal ID from the object
delete rawModelRes._id;
// Get remaining now sanitized Model instance data
const fetchedModelObject = rawModelRes;
// Return correctly structured fetched Model instance data
return {
id : fetchedModelId,
object : fetchedModelObject,
};
}
/**
* Find Model data by collection ID and Model ID
*/
async findByIds(collectionId, ids) {
// Wait for building to finish
await this.building;
// Construct MQuery cursor from collection ID
const mQuery = MQuery(this._db.collection(collectionId));
// If no Model instance data found, return null
return (await mQuery.in('_id', ids.map((id) => ObjectId(id))).exec()).map((rawModelRes) => {
// Get internal ID from returned data
const fetchedModelId = rawModelRes._id.toString();
// Delete internal ID from the object
delete rawModelRes._id;
// Get remaining now sanitized Model instance data
const fetchedModelObject = rawModelRes;
// Return correctly structured fetched Model instance data
return {
id : fetchedModelId,
object : fetchedModelObject,
};
});
}
/**
* raw
*
* @param {*} collectionId
* @param {*} query
*/
raw(collectionId, query) {
// Wait for building to finish
this.building;
// Construct MQuery cursor from collection ID
const mQuery = MQuery(this._db.collection(collectionId));
// Fetch, map, and return found Model instance
// data found by cursor constructed from provided query
return this._queryToCursor(mQuery, query)._pipeline;
}
/**
* raw
*
* @param {*} collectionId
* @param {*} query
*/
exec(collectionId, action, ...args) {
// Wait for building to finish
this.building;
// Construct MQuery cursor from collection ID
const collection = this._db.collection(collectionId);
// return promise
return new Promise((resolve, reject) => {
// execute
collection[action](...args).toArray((err, data) => {
// reject error
if (err) return reject(err);
// resolve
resolve(data);
});
});
}
/**
* Find Model data by collection ID and constructed query
*/
async find(collectionId, query) {
// Wait for building to finish
await this.building;
// Construct MQuery cursor from collection ID
const mQuery = MQuery(this._db.collection(collectionId));
// Fetch, map, and return found Model instance
// data found by cursor constructed from provided query
return (await this._queryToCursor(mQuery, query).find().exec()).map((rawModelRes) => {
// Get internal ID from returned data
const fetchedModelId = rawModelRes._id.toString();
// Delete internal ID from the object
delete rawModelRes._id;
// Get remaining now sanitized Model instance data
const fetchedModelObject = rawModelRes;
// Return correctly structured fetched Model instance data
return {
id : fetchedModelId,
object : fetchedModelObject,
};
});
}
/**
* Find single Model data by collection ID and Model ID
*/
async findOne(collectionId, query) {
// Wait for building to finish
await this.building;
// Construct MQuery cursor from collection ID
const mQuery = MQuery(this._db.collection(collectionId));
// Construct cursor from provided query, and use it to fetch single Model instance data
const rawModelRes = await this._queryToCursor(mQuery, query).findOne().exec();
// If no Model instance data found, return null
if (rawModelRes == null) {
return null;
}
// Get internal ID from returned data
const fetchedModelId = rawModelRes._id.toString();
// Delete internal ID from the object
delete rawModelRes._id;
// Get remaining now sanitized Model instance data
const fetchedModelObject = rawModelRes;
// Return correctly structured fetched Model instance data
return {
id : fetchedModelId,
object : fetchedModelObject,
};
}
/**
* Get count of Model data by collection ID and constructed query
*/
async count(collectionId, query) {
// Wait for building to finish
await this.building;
// Construct MQuery cursor from collection ID
const mQuery = MQuery(this._db.collection(collectionId));
// Construct cursor from provided query, and use it
// to fetch count of matching Model instance data
return await this._queryToCursor(mQuery, query).count().exec();
}
/**
* Get sum of data by provided key of all matching Model data
* by collection ID and constructed query
*/
async sum(collectionId, query, key) {
// Wait for building to finish
await this.building;
// Construct MQuery cursor from collection ID
const mQuery = MQuery(this._db.collection(collectionId));
// Construct cursor from provided query, and use it to get sum
// of data by provided key of all matching Model data
return await this._queryToCursor(mQuery, query).sum(`$${key}`).exec();
}
/**
* Remove matching Model data from database by collection ID and Model ID
*/
async removeById(collectionId, id) {
// Wait for building to finish
await this.building;
// Construct MQuery cursor from collection ID
const mQuery = MQuery(this._db.collection(collectionId));
// Find and remove single Model instance data by provided ID
await mQuery.findOneAndRemove({ _id : ObjectId(id) }).exec();
}
/**
* Remove matching Model data from database by collection ID and constructed query
*/
async remove(collectionId, query) {
// Wait for building to finish
await this.building;
// Construct MQuery cursor from collection ID
const mQuery = MQuery(this._db.collection(collectionId));
// Find and remove matching Model instance data by provided query
await this._queryToCursor(mQuery, query).deleteMany().exec();
}
/**
* Replace matching Model data from database by collection ID, Model ID, and replacement data
*/
async replaceById(collectionId, id, newObject) {
// Wait for building to finish
await this.building;
// Construct MQuery cursor from collection ID
const mQuery = MQuery(this._db.collection(collectionId));
// Find and update Model instance data by provided ID and replacement object
await mQuery.where({ _id : ObjectId(id) }).setOptions({ overwrite : true })
.update(newObject).exec();
}
/**
* Update matching Model data from database by collection ID, Model ID, replacement data,
* and set of updated keys
*/
async updateById(collectionId, id, newObject, updates) {
// Wait for building to finish
await this.building;
// Filter to only top level key updates
const topLevelUpdates = new Set(Array.from(updates).map(update => update.split('.')[0]));
// Create new object for storing only updated keys
const replaceObject = {};
// Create new object for storing only unset keys
const unsetObject = {};
// Iterate updated keys
for (const updatedKey of topLevelUpdates) {
if (newObject[updatedKey] != null) {
// Set replace object key-val to be from new object
replaceObject[updatedKey] = newObject[updatedKey];
} else {
// Set field on unset object to be key from new object
unsetObject[updatedKey] = 0;
}
}
// Set mongodb-special field for unsetting fields
if (Object.keys(unsetObject).length > 0) replaceObject.$unset = unsetObject;
// Construct MQuery cursor from collection ID
const mQuery = MQuery(this._db.collection(collectionId));
// Find and update Model instance data by provided ID and replacement object
await mQuery.where({ _id : ObjectId(id) }).update(replaceObject).exec();
}
/**
* Insert Model data from database by collection ID and return Model ID
*/
async insert(collectionId, object) {
// Wait for building to finish
await this.building;
// Get DB collection from collection ID
const collection = this._db.collection(collectionId);
// Convert _id to ObjectId if present
if (object._id !== null && object._id !== undefined) {
object._id = ObjectId(object._id);
}
// Insert Model instance data into database and get inserted ID
const id = (await collection.insertOne(object)).insertedId.toString();
// Return ID of Model instance data in database
return id;
}
}
// Exports
module.exports = EdenModelMongo;