UNPKG

respond-framework

Version:
378 lines (375 loc) 10.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _mongodb = require("mongodb"); var _constants = require("../helpers/constants.js"); var _indexMock = require("./index.mock.js"); var _toFromObjectIds = require("./helpers/toFromObjectIds.js"); var _createQuerySelector = require("./helpers/createQuerySelector.js"); var _safeMethods = require("./safeMethods.js"); var _createQueryKey = require("./helpers/createQueryKey.js"); var _default = exports.default = _constants.isDev ? _indexMock.default : { create({ _id, ...doc } = {}) { // in standard collections, _id would become id, but here we just use iGolf's id return new this.Model({ ...(0, _toFromObjectIds.fromObjectIds)(doc) }, false); }, async insertOne(doc) { // id not removed!, regular signature: async insertOne({ id, ...doc }) { doc._id = new _mongodb.ObjectId(); doc.createdAt = doc.updatedAt = new Date(doc.createdAt || new Date()); doc = (0, _toFromObjectIds.toObjectIds)(doc); await this.mongo().insertOne(doc); return this.create(doc); }, async findOne(selector, { project, sort = { updatedAt: -1 } } = {}) { selector = typeof selector === 'string' ? { id: selector } : selector; const doc = await this.mongo().findOne(selector, { projection: project, sort }); return doc && this.create(doc); }, async findMany(selector, { project, sort = { updatedAt: -1, _id: 1 }, limit = this.config.listLimit ?? 10, skip = 0 } = {}) { const models = await this.mongo().find(selector).sort(sort).skip(skip * limit).limit(limit).project(project).toArray(); return models.map(m => this.create(m)); }, async findLike(key, term, { selector, ...options } = {}) { term = term.replace(/\\*$/g, ''); // backslashes cant exist at end of regex const value = new RegExp(`^${term}`, 'i'); return this.findMany({ ...selector, [key]: value }, options); }, async updateOne(sel = {}, newDoc, { project } = {}) { const selector = (0, _toFromObjectIds.toIdSelector)(sel); const { createdAt: _, updatedAt: __, ...doc } = newDoc ?? sel; // accept signature: updateOne(doc) const result = await this.mongo().findOneAndUpdate(selector, { $set: (0, _toFromObjectIds.toObjectIds)(doc), $currentDate: { updatedAt: true } }, { projection: project, returnDocument: 'after' }); return result.value ? this.create(result.value) : selector.id ? this.insertOne({ id: selector.id, ...doc }, { project }) : undefined; // honor id: client-created id or deleted doc }, async upsert(sel = {}, newDoc, { insertDoc, project } = {}) { const selector = (0, _toFromObjectIds.toIdSelector)(sel); const { createdAt: _, updatedAt: __, ...doc } = newDoc || sel; // upsert accepts this signature: upsert(doc) const result = await this.mongo().findOneAndUpdate(selector, { $set: (0, _toFromObjectIds.toObjectIds)(doc), $setOnInsert: { ...selector, ...insertDoc, createdAt: new Date() }, $currentDate: { updatedAt: true } }, { upsert: true, projection: project, returnDocument: 'after' }); return result.value && this.create(result.value); }, save(model) { return this.upsert(model); }, make(doc) { const revived = this.db.revive(doc); return new this.Model(revived, false); }, async incrementOne(selector, $inc) { return this.mongo().updateOne((0, _toFromObjectIds.toIdSelector)(selector), { $inc, $set: { updatedAt: new Date() } }); }, async count(selector) { return this.mongo().count((0, _toFromObjectIds.toObjectIds)(selector)); }, async totalPages(selector, limit = this.config.listLimit ?? 10) { const count = await this.mongo().count((0, _toFromObjectIds.toObjectIds)(selector)); return Math.ceil(count / limit); }, async updateMany(selector, $set) { return this.mongo().updateMany((0, _toFromObjectIds.toObjectIds)(selector), { $set: (0, _toFromObjectIds.toObjectIds)($set) }); }, async deleteMany(selector) { await this.mongo().deleteMany((0, _toFromObjectIds.toObjectIds)(selector)); }, async deleteOne(selector) { return this.mongo().deleteOne((0, _toFromObjectIds.toIdSelector)(selector)); }, async findAll(selector, options) { return this.super.findMany(selector, { ...options, limit: 0 }); }, async findManyPaginated(selector, options = {}) { const skip = options.skip ? parseInt(options.skip) : 0; const [models, count] = await Promise.all([this.super.findMany(selector, { ...options, skip }), this.count(selector)]); return { [this._namePlural]: models, count, skip }; }, async queryPaginated(query, { project } = {}) { const { sortKey = 'updatedAt', sortValue = -1, limit = this.config.listLimit ?? 10, skip = 0, location, ...sel } = query; const selector = this.createQuerySelector(sel); // advanced filtering suitable for an admin panel const sort = { [sortKey]: sortValue, _id: sortValue }; const [count, models] = await Promise.all([this.count(selector), location ? this.searchGeo(location, { selector, project, limit, skip }) : this.findMany(selector, { project, sort, limit, skip })]); const total = Math.ceil(count / limit); const hasNext = skip + 1 < total; const next = hasNext ? skip + 1 : null; return { query, count, total, next, index: skip, [this._namePlural]: models, page: models.map(m => m.id), key: this.createQueryKey(query) }; }, async search(query, { path = ['firstName', 'lastName'], selector, project, limit = this.config.listLimit ?? 10, skip = 0 } = {}) { selector = (0, _toFromObjectIds.toObjectIds)(selector); if (this.config.useLocalDb) { return this.super.findMany({ $text: { $search: query }, ...selector }, { project, skip, limit, sort: { updatedAt: 1 } }); } const models = await this.mongo().aggregate([{ $search: { index: 'nameSearch', text: { query, path, fuzzy: { maxEdits: 2, prefixLength: 1, maxExpansions: 100 } } } }, ...(selector ? [{ $match: selector }] : []), { $skip: skip * limit }, { $limit: limit }, ...(project ? [{ $project: project }] : [])]).toArray(); return models.map(m => this.super.create(m)); }, async searchGeo({ lng, lat }, { selector, project, limit = this.config.listLimit ?? 10, skip = 0 } = {}) { selector = (0, _toFromObjectIds.toObjectIds)(selector); if (this.config.useLocalDb) { return this.super.findMany(selector, { project, limit, skip, sort: { updatedAt: 1 } }); // updateAt: 1, sorts in opposite of default direction to indicate something happened } const models = await this.mongo().aggregate([{ $geoNear: { near: { type: 'Point', coordinates: [lng, lat] }, spherical: true, distanceField: 'distance', key: 'location' } }, ...(selector ? [{ $match: selector }] : []), { $skip: skip * limit }, { $limit: limit }, ...(project ? [{ $project: project }] : [])]).toArray(); return models.map(m => this.super.create(m)); }, async joinOne(id, name, { project, projectJoin, sort = { updatedAt: -1, _id: 1 } } = {}) { const coll = this.db[name]; const parentName = this._name; const fk = parentName + 'Id'; const selector = (0, _toFromObjectIds.toObjectIds)({ [fk]: id }); let [parent, children] = await Promise.all([this.super.findOne(id, project), coll.super.findOne(selector, { project: projectJoin, sort })]); return { [parentName]: parent, [name]: children }; }, async joinMany(id, name, { project, projectJoin, sort = { updatedAt: -1, _id: 1 }, limit = this.config.listLimit ?? 10, skip = 0 } = {}) { const coll = this.db[name]; const parentName = this._name; const fk = parentName + 'Id'; const selector = (0, _toFromObjectIds.toObjectIds)({ [fk]: id }); let [parent, children] = await Promise.all([this.super.findOne(id, project), coll.super.findMany(selector, { project: projectJoin, sort, limit, skip })]); return { [parentName]: parent, [coll._namePlural]: children }; }, mongo() { if (this._mongoCollection) return this._mongoCollection; const str = typeof this.config.dbConnectionString === 'function' ? this.config.dbConnectionString() : this.config.dbConnectionString; const dbName = this.config.dbName ?? str.slice(str.lastIndexOf('/') + 1).split('?')[0] ?? 'app'; const client = new _mongodb.MongoClient(str); const db = client.db(dbName); return this._mongoCollection = db.collection(this._name); }, insertSeed() { console.warn(`respond: db.${this._name}.insertSeed is intended for development use only`); }, get super() { if (this._super) return this._super; const proto = Object.getPrototypeOf(this); return this._super = new Proxy({}, { get: (_, k) => proto[k].bind(this) }); }, clone() { return Object.defineProperties(Object.create(this.parent), Object.getOwnPropertyDescriptors(this)); }, createQuerySelector: _createQuerySelector.default, createQueryKey: _createQueryKey.default, ..._safeMethods.default };