@onesy/mongo
Version:
Utils for easier using of mongodb library.
848 lines (823 loc) • 36.2 kB
JavaScript
import _objectWithoutProperties from "@babel/runtime/helpers/objectWithoutProperties";
import _defineProperty from "@babel/runtime/helpers/defineProperty";
const _excluded = ["total", "sort", "limit", "skip"],
_excluded2 = ["total", "sort", "limit", "skip", "projection"],
_excluded3 = ["projection"],
_excluded4 = ["add_date"],
_excluded5 = ["update_date"],
_excluded6 = ["add_date", "update_date"],
_excluded7 = ["add_date"],
_excluded8 = ["update_date"];
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
import * as mongodb from 'mongodb';
import is from '@onesy/utils/is';
import copy from '@onesy/utils/copy';
import wait from '@onesy/utils/wait';
import getObjectValue from '@onesy/utils/getObjectValue';
import setObjectValue from '@onesy/utils/setObjectValue';
import { Query, getMongoMatch, MongoResponse } from '@onesy/models';
import { OnesyMongoError, DeveloperError } from '@onesy/errors';
import OnesyDate from '@onesy/date/OnesyDate';
import duration from '@onesy/date/duration';
import Mongo from './Mongo';
import OnesyMongo from './OnesyMongo';
export class BaseCollection {
constructor(collectionName, mongo, Model, defaults) {
this.collectionName = collectionName;
this.mongo = mongo;
this.Model = Model;
this.defaults = defaults;
_defineProperty(this, "collections", {});
_defineProperty(this, "onesyLog", void 0);
if (!(mongo && mongo instanceof Mongo)) throw new OnesyMongoError(`Mongo instance is required`);
if (!collectionName) throw new OnesyMongoError(`Collection name is required`);
// log inherit from Mongo
// so it can be configured on per use basis
this.onesyLog = mongo.onesyLog;
}
get sort() {
return {
[this.sortProperty]: this.sortAscending
};
}
get sortProperty() {
return 'added_at';
}
get sortAscending() {
return -1;
}
get addedProperty() {
return 'added_at';
}
get updatedProperty() {
return 'updated_at';
}
get projection() {
return;
}
get db() {
return new Promise(async resolve => {
const db = await this.mongo.connection;
return resolve(db);
});
}
async collection() {
let name = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.collectionName;
let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
const db = await this.db;
if (!this.collections[name]) this.collections[name] = db.collection(name);
const collections = await this.mongo.getCollections();
// Good to create a collection in advance if it doesn't exist atm
// as it might fail if you wanna add a document within a transaction
// on a non existing mongo collection atm
if (!collections?.find(item => item.name === this.collectionName)) {
const collection = await db.createCollection(this.collectionName, options);
this.mongo.collections.push({
name: collection.collectionName
});
// Add collection to Query model collections
Query.collections.push(collection.collectionName);
Query.keys.allowed.push(collection.collectionName);
this.onesyLog.info(`${this.collectionName} collection created`);
}
return this.collections[name];
}
async transaction(method) {
let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {
retries: 5,
retriesWait: 140
};
if (!is('function', method)) throw new DeveloperError('First argument has to be a function');
const transactionOptions = {
readPreference: mongodb.ReadPreference.primary,
readConcern: {
level: 'local'
},
writeConcern: {
w: 'majority'
}
};
let response;
const retriesTotal = is('number', options.retries) ? options.retries : 5;
const retriesWait = is('number', options.retriesWait) ? options.retriesWait : 140;
let retries = retriesTotal;
let error;
let codeName = 'WriteConflict';
while (codeName === 'WriteConflict' && retries > 0) {
error = undefined;
codeName = undefined;
if (retries < retriesTotal) await wait(retriesWait);
const session = this.mongo.client.startSession();
try {
await session.withTransaction(async () => {
response = await method(session);
return response;
}, transactionOptions);
} catch (error_) {
error = error_;
codeName = error_.codeName || error_.message?.codeName;
} finally {
await session.endSession();
}
retries--;
}
if (error) throw new DeveloperError(error);
return response;
}
async count() {
let query = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : new Query();
let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
const collection = await this.collection();
const start = OnesyDate.utc.milliseconds;
try {
const defaults = this.getDefaults('count');
const response = await collection.countDocuments(_objectSpread(_objectSpread({}, defaults), this.query(query)), options);
return this.response(start, collection, 'count', response);
} catch (error) {
this.response(start, collection, 'count');
throw new OnesyMongoError(error);
}
}
async exists(query) {
let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
const collection = await this.collection();
const start = OnesyDate.utc.milliseconds;
try {
const defaults = this.getDefaults('exists');
const response = await collection.findOne(_objectSpread(_objectSpread({}, defaults), this.query(query)), _objectSpread({
projection: {
_id: 1
}
}, options));
return this.response(start, collection, 'exists', !!response);
} catch (error) {
this.response(start, collection, 'exists');
throw new OnesyMongoError(error);
}
}
async find(query) {
let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
const collection = await this.collection();
const start = OnesyDate.utc.milliseconds;
try {
const {
total,
sort,
limit,
skip
} = options,
optionsOther = _objectWithoutProperties(options, _excluded);
const defaults = this.getDefaults('find');
const optionsMongo = _objectSpread({}, optionsOther);
if (!optionsMongo.projection) {
optionsMongo.projection = BaseCollection.isOnesyQuery(query) && query.projection || this.projection;
if (!optionsMongo.projection) delete optionsMongo.projection;
}
optionsMongo.sort = (BaseCollection.isOnesyQuery(query) ? query.sort : sort) || this.sort;
optionsMongo.skip = (BaseCollection.isOnesyQuery(query) ? query.skip : skip) || 0;
optionsMongo.limit = (BaseCollection.isOnesyQuery(query) ? query.limit : limit) || 15;
const queryMongo = _objectSpread(_objectSpread({}, defaults), this.query(query));
const response_ = await collection.find(queryMongo, optionsMongo).toArray();
const response = new MongoResponse(response_);
response.sort = optionsMongo.sort;
response.size = response_.length;
response.skip = optionsMongo.skip;
response.limit = optionsMongo.limit;
if (BaseCollection.isOnesyQuery(query) ? query.total : total) {
response['total'] = await collection.find(queryMongo, {
projection: {
_id: 1
}
}).count();
}
return this.response(start, collection, 'find', response);
} catch (error) {
this.response(start, collection, 'find');
throw new OnesyMongoError(error);
}
}
async findOne(query) {
let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
const collection = await this.collection();
const start = OnesyDate.utc.milliseconds;
try {
const defaults = this.getDefaults('findOne');
if (!options.projection) {
options.projection = BaseCollection.isOnesyQuery(query) && query.projection || this.projection;
if (!options.projection) delete options.projection;
}
const response = await collection.findOne(_objectSpread(_objectSpread({}, defaults), this.query(query)), options);
return this.response(start, collection, 'findOne', response);
} catch (error) {
this.response(start, collection, 'findOne');
throw new OnesyMongoError(error);
}
}
async aggregate() {
let query = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : new Query();
let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
const collection = await this.collection();
const start = OnesyDate.utc.milliseconds;
try {
const defaults = this.getDefaults('aggregate');
const response = await collection.aggregate([
// defaults
...(defaults || []), ...this.query(query)], _objectSpread(_objectSpread({}, Mongo.defaults.aggregateOptions), options)).toArray();
return this.response(start, collection, 'aggregate', response);
} catch (error) {
this.response(start, collection, 'aggregate');
throw new OnesyMongoError(error);
}
}
async searchMany(query) {
let additional = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {
pre: [],
prePagination: [],
post: [],
options: [],
lookups: []
};
let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
const collection = await this.collection();
const start = OnesyDate.utc.milliseconds;
try {
const {
total: optionsTotal,
sort: optionsSort,
limit: optionsLimit = 15,
skip: optionsSkip,
projection: optionsProjection
} = options,
optionsOther = _objectWithoutProperties(options, _excluded2);
const defaults = this.getDefaults('searchMany');
const optionsMongo = _objectSpread({}, optionsOther);
const projection = (BaseCollection.isOnesyQuery(query) ? query.projection : optionsProjection) || this.projection;
const sort = (BaseCollection.isOnesyQuery(query) ? query.sort : optionsSort) || this.sort;
const {
limit = optionsLimit,
skip = optionsSkip,
next,
previous
} = BaseCollection.isOnesyQuery(query) ? query : options;
const hasPaginator = next || previous;
const paginatorProperty = Object.keys(next || previous || {})[0];
const pre = additional.pre || [];
const pre_pagination = additional.prePagination || [];
const post = additional.post || [];
const queries = {
search: (BaseCollection.isOnesyQuery(query) ? query.queries.search[this.collectionName] : []) || [],
api: (BaseCollection.isOnesyQuery(query) ? query.queries.api[this.collectionName] : []) || [],
permissions: (BaseCollection.isOnesyQuery(query) ? query.queries.permissions[this.collectionName] : []) || [],
aggregate: (BaseCollection.isOnesyQuery(query) ? query.queries.aggregate[this.collectionName] : []) || []
};
const queryMongo = [
// defaults
...(defaults || []), ...((BaseCollection.isOnesyQuery(query) ? query.query : query) || []), ...pre, ...queries.aggregate,
// Search
...(queries.search.length ? getMongoMatch(queries.search, query.settings.type) : []),
// API
...(queries.api.length ? getMongoMatch(queries.api) : []),
// Permissions
...(queries.permissions.length ? getMongoMatch(queries.permissions, '$or') : []), ...pre_pagination];
const pipeline = [...queryMongo,
// Next paginator
...(next ? getMongoMatch([next]) : []),
// Previous paginator
...(previous ? getMongoMatch([previous]) : []), ...(hasPaginator ? [{
$sort: {
[paginatorProperty]: next ? -1 : 1
}
}] : []), ...(sort && !hasPaginator ? [{
$sort: sort
}] : []),
// Either skip or a paginator
...(query?.skip !== undefined && !hasPaginator ? [{
$skip: skip
}] : []),
// +1 so we know if there's a next page
{
$limit: limit + 1
}, ...(sort && hasPaginator ? [{
$sort: sort
}] : []), ...(projection ? [{
$project: projection
}] : []), ...post];
const response_ = await collection.aggregate(pipeline, _objectSpread(_objectSpread({}, Mongo.defaults.aggregateOptions), optionsMongo)).toArray();
// Add results and limit
const objects = !hasPaginator || next || response_.length <= limit ? response_.slice(0, limit) : response_.slice(1, limit + 1);
const first = objects[0];
const last = objects[objects.length - 1];
const response = new MongoResponse(objects);
response.sort = sort;
response.skip = skip;
response.limit = limit;
// Add hasNext, next, previous
response['hasNext'] = previous || response_.length > objects.length;
response['hasPrevious'] = !hasPaginator ? query.skip > 0 : next || response_.length > objects.length;
if (last) response['next'] = OnesyMongo.createPaginator(last, [this.sortProperty], sort);
if (first) response['previous'] = OnesyMongo.createPaginator(first, [this.sortProperty], sort, 'previous');
// lookups
await this.lookups(response.response, additional.lookups, options.request);
// options
if (!!additional.options?.length) {
const optionsResponse = await collection.aggregate([...queryMongo, ...additional.options.map(item => ['array', undefined].includes(item.version) ? {
$unwind: `$${item.property}`
} : undefined).filter(Boolean), ...additional.options.map(item => ({
$group: {
_id: item.name,
value: {
$addToSet: item.property.startsWith('$') ? item.property : `$${item.property}`
}
}
}))], _objectSpread(_objectSpread({}, Mongo.defaults.aggregateOptions), optionsMongo)).toArray();
const optionsMongoResponse = {};
optionsResponse.forEach(item => optionsMongoResponse[item._id] = item.value?.flatMap(item => item) || []);
for (const optionName of Object.keys(optionsMongoResponse)) {
const optionRequest = additional.options.find(item => item.name === optionName);
if (optionRequest.lookup) await this.lookups(optionsMongoResponse[optionName], [optionRequest.lookup], options.request);
}
response.options = optionsMongoResponse;
}
// Count total only if it's requested by the query
let total;
if (BaseCollection.isOnesyQuery(query) ? query.total : optionsTotal) {
const total_ = await collection.aggregate([...queryMongo,
// Limit count for performance reasons
{
$limit: Mongo.defaults.limitCount
}, {
$group: {
_id: null,
count: {
$sum: 1
}
}
}], _objectSpread(_objectSpread({}, Mongo.defaults.aggregateOptions), optionsMongo)).toArray();
total = total_[0] && total_[0].count;
response['total'] = total || 0;
}
return this.response(start, collection, 'searchMany', response);
} catch (error) {
this.response(start, collection, 'searchMany');
throw new OnesyMongoError(error);
}
}
async searchOne(query) {
let additional = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {
pre: [],
post: [],
lookups: []
};
let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
const collection = await this.collection();
const start = OnesyDate.utc.milliseconds;
try {
const {
projection: optionsProjection
} = options,
optionsOther = _objectWithoutProperties(options, _excluded3);
const defaults = this.getDefaults('searchOne');
const optionsMongo = _objectSpread({}, optionsOther);
const limit = 1;
const projection = (BaseCollection.isOnesyQuery(query) ? query.projection : optionsProjection) || this.projection;
const pre = additional.pre || [];
const post = additional.post || [];
const queries = {
search: (BaseCollection.isOnesyQuery(query) ? query.queries.search[this.collectionName] : []) || [],
api: (BaseCollection.isOnesyQuery(query) ? query.queries.api[this.collectionName] : []) || [],
permissions: (BaseCollection.isOnesyQuery(query) ? query.queries.permissions[this.collectionName] : []) || [],
aggregate: (BaseCollection.isOnesyQuery(query) ? query.queries.aggregate[this.collectionName] : []) || []
};
const queryMongo = [
// defaults
...(defaults || []), ...((BaseCollection.isOnesyQuery(query) ? query.query : query) || []), ...pre, ...queries.aggregate,
// Search
...(queries.search.length ? getMongoMatch(queries.search, query.settings.type) : []),
// API
...(queries.api.length ? getMongoMatch(queries.api) : []),
// Permissions
...(queries.permissions.length ? getMongoMatch(queries.permissions, '$or') : [])];
const pipeline = [...queryMongo, {
$limit: limit
}, ...(projection ? [{
$project: projection
}] : []), ...post];
const response = await collection.aggregate(pipeline, _objectSpread(_objectSpread({}, Mongo.defaults.aggregateOptions), optionsMongo)).toArray();
// lookups
await this.lookups(response, additional.lookups, options.request);
return this.response(start, collection, 'searchOne', response[0]);
} catch (error) {
this.response(start, collection, 'searchOne');
throw new OnesyMongoError(error);
}
}
async addOne(value_) {
let options_ = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
const options = _objectSpread({
add_date: true
}, options_);
const {
add_date
} = options,
optionsMongo = _objectWithoutProperties(options, _excluded4);
const collection = await this.collection();
const start = OnesyDate.utc.milliseconds;
try {
const value = BaseCollection.value(value_);
if (!value) throw new OnesyMongoError(`No value provided`);
if (add_date) setObjectValue(value, this.addedProperty || 'added_at', OnesyDate.utc.milliseconds);
const response = await collection.insertOne(value, optionsMongo);
return this.response(start, collection, 'addOne', _objectSpread({
_id: response.insertedId
}, value));
} catch (error) {
this.response(start, collection, 'addOne');
throw new OnesyMongoError(error);
}
}
async updateOne(query, value) {
let options_ = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {
lookups: []
};
const options = _objectSpread({
update_date: true
}, options_);
const {
update_date
} = options,
optionsMongo = _objectWithoutProperties(options, _excluded5);
const collection = await this.collection();
const start = OnesyDate.utc.milliseconds;
try {
const defaults = this.getDefaults('updateOne');
if (value !== undefined && !is('object', value)) throw new OnesyMongoError(`Value has to be an object with update values`);
if (is('object', value) && update_date) value[this.updatedProperty || 'updated_at'] = OnesyDate.utc.milliseconds;
const update = {};
const operators = {};
// use both update values
// and operator values in the same object
Object.keys(value).forEach(item => {
if (item.startsWith('$')) operators[item] = value[item];else update[item] = value[item];
});
const response = await collection.findOneAndUpdate(_objectSpread(_objectSpread({}, defaults), this.query(query)), _objectSpread(_objectSpread({}, operators), (!!Object.keys(update).length || operators['$set']) && {
$set: _objectSpread(_objectSpread({}, operators['$set']), update)
}), _objectSpread({
returnDocument: 'after'
}, optionsMongo));
// lookups
await this.lookups(response.value, options.lookups, options.request);
return this.response(start, collection, 'updateOne', response.value);
} catch (error) {
this.response(start, collection, 'updateOne');
throw new OnesyMongoError(error);
}
}
async removeOne(query) {
let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
const collection = await this.collection();
const start = OnesyDate.utc.milliseconds;
try {
const defaults = this.getDefaults('removeOne');
const response = await collection.findOneAndDelete(_objectSpread(_objectSpread({}, defaults), this.query(query)), options);
return this.response(start, collection, 'removeOne', response.value);
} catch (error) {
this.response(start, collection, 'removeOne');
throw new OnesyMongoError(error);
}
}
async updateOneOrAdd(query, value) {
let options_ = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
const options = _objectSpread({
add_date: true,
update_date: true
}, options_);
const {
add_date,
update_date
} = options,
optionsMongo = _objectWithoutProperties(options, _excluded6);
const collection = await this.collection();
const start = OnesyDate.utc.milliseconds;
try {
const defaults = this.getDefaults('updateOneOrAdd');
if (!is('object', value)) throw new OnesyMongoError(`Value has to be an object with properties and values`);
if (update_date) value[this.updatedProperty || 'updated_at'] = OnesyDate.utc.milliseconds;
let setOnInsert;
if (add_date) setOnInsert = {
[this.addedProperty || 'added_at']: OnesyDate.utc.milliseconds
};
const response = await collection.findOneAndUpdate(_objectSpread(_objectSpread({}, defaults), this.query(query)), _objectSpread({
$set: value
}, setOnInsert && {
$setOnInsert: setOnInsert
}), _objectSpread({
upsert: true,
returnDocument: 'after'
}, optionsMongo));
return this.response(start, collection, 'updateOneOrAdd', response.value);
} catch (error) {
this.response(start, collection, 'updateOneOrAdd');
throw new OnesyMongoError(error);
}
}
async addMany(values_) {
let options_ = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
const options = _objectSpread({
add_date: true
}, options_);
const {
add_date
} = options,
optionsMongo = _objectWithoutProperties(options, _excluded7);
const collection = await this.collection();
const start = OnesyDate.utc.milliseconds;
try {
let values = values_.map(item => BaseCollection.value(item));
if (!values?.length) throw new OnesyMongoError(`Values have to be a non empty array`);
if (add_date) values = values.map(item => {
setObjectValue(item, this.addedProperty || 'added_at', OnesyDate.utc.milliseconds);
return item;
});
let response = await collection.insertMany(values, _objectSpread({
ordered: false
}, optionsMongo));
if (!options.original) {
const ids = Object.keys(response.insertedIds || {}).map(item => response.insertedIds?.[item]);
response = values.filter(item => !!ids.find(id => item._id.toString() === id.toString()));
}
return this.response(start, collection, 'addMany', response);
} catch (error) {
this.response(start, collection, 'addMany');
throw new OnesyMongoError(error);
}
}
async updateMany(query, value) {
let options_ = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
const options = _objectSpread({
update_date: true
}, options_);
const {
update_date
} = options,
optionsMongo = _objectWithoutProperties(options, _excluded8);
const collection = await this.collection();
const start = OnesyDate.utc.milliseconds;
try {
const defaults = this.getDefaults('updateMany');
if (value !== undefined && !is('object', value)) throw new OnesyMongoError(`Value has to be an object with properties and values`);
if (is('object', value) && update_date) value[this.updatedProperty || 'updated_at'] = OnesyDate.utc.milliseconds;
const update = {};
const operators = {};
// use both update values
// and operator values in the same object
Object.keys(value).forEach(item => {
if (item.startsWith('$')) operators[item] = value[item];else update[item] = value[item];
});
const response = await collection.updateMany(_objectSpread(_objectSpread({}, defaults), this.query(query)), _objectSpread(_objectSpread({}, operators), (!!Object.keys(update).length || operators['$set']) && {
$set: _objectSpread(_objectSpread({}, operators['$set']), update)
}), _objectSpread({}, optionsMongo));
return this.response(start, collection, 'updateMany', response);
} catch (error) {
this.response(start, collection, 'updateMany');
throw new OnesyMongoError(error);
}
}
async removeMany(query) {
let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
const collection = await this.collection();
const start = OnesyDate.utc.milliseconds;
try {
const defaults = this.getDefaults('removeMany');
const response = await collection.deleteMany(_objectSpread(_objectSpread({}, defaults), this.query(query)), _objectSpread({
ordered: false
}, options));
return this.response(start, collection, 'removeMany', response);
} catch (error) {
this.response(start, collection, 'removeMany');
throw new OnesyMongoError(error);
}
}
async bulkWrite() {
let values = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
let options_ = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
const options = _objectSpread({}, options_);
const collection = await this.collection();
const start = OnesyDate.utc.milliseconds;
try {
if (!values?.length) throw new OnesyMongoError(`Values have to be a non empty array`);
const response = await collection.bulkWrite(values, _objectSpread({
ordered: false
}, options));
return this.response(start, collection, 'bulkWrite', response);
} catch (error) {
this.response(start, collection, 'bulkWrite');
throw new OnesyMongoError(error);
}
}
async lookups(value_, lookups, request) {
const value = is('array', value_) ? value_ : [value_];
if (!!value.length && !!lookups?.length) {
for (const lookup of lookups) {
try {
if (lookup.objects) {
const ids = [];
// Get the ids to lookup
value.forEach(item => {
const valueProperty = !lookup.property ? item : getObjectValue(item, lookup.property);
ids.push(...this.getLookupIDs(valueProperty));
});
if (!!ids.length) {
// Search objects
const query = lookup.query || [];
if (BaseCollection.isOnesyQuery(query)) {
if (is('array', query.query)) {
query.query.unshift({
$match: {
_id: {
$in: ids
}
}
});
if (lookup.projection) {
query.query.push({
$project: _objectSpread({}, lookup.projection)
});
}
}
} else {
query.unshift({
$match: {
_id: {
$in: ids
}
}
});
if (lookup.projection) {
query.push({
$project: _objectSpread({}, lookup.projection)
});
}
}
const method = lookup.objects.aggregate.bind(lookup.objects);
let response = await method(query, lookup.options);
if ([true, undefined].includes(lookup.toObjectResponse)) {
response = response.map(item => {
if (item.toObjectResponse) return item.toObjectResponse(request);
return item;
});
}
const responseMap = {};
response.forEach(item => {
responseMap[(item._id || item.id).toString()] = item;
});
// Update all the id objects
value.forEach((item, index) => {
const valueItem = this.updateLookupProperty(item, item, responseMap, lookup);
if (!lookup.property && valueItem) value[index] = valueItem;
});
}
}
} catch (error) {
console.error(`Lookups error`, error);
}
}
}
}
updateLookupProperty(mongoObject, object, responseMap, lookup) {
let array = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;
const valueProperty = array ? object : !lookup.property ? object : getObjectValue(object, lookup.property);
// string
if (is('string', valueProperty)) {
const valueResponse = responseMap[valueProperty];
if (valueResponse !== undefined) {
if (lookup.property) setObjectValue(mongoObject, lookup.property, valueResponse);else return valueResponse;
}
}
// mongoDB ObjectId
else if (mongodb.ObjectId.isValid(valueProperty)) {
const valueResponse = responseMap[valueProperty?.toString()];
if (valueResponse !== undefined) {
if (lookup.property) setObjectValue(mongoObject, lookup.property, valueResponse);else return valueResponse;
}
}
// object
else if (is('object', valueProperty)) {
const id = valueProperty?.id || valueProperty?._id;
const valueResponse = responseMap[id?.toString()];
const previous = copy(getObjectValue(mongoObject, lookup.property));
if (lookup.override) {
lookup.override.forEach(item => {
setObjectValue(valueResponse, item, getObjectValue(previous, item));
});
}
if (valueResponse !== undefined) {
if (lookup.property) setObjectValue(mongoObject, lookup.property, valueResponse);else return valueResponse;
}
}
// array
else if (is('array', valueProperty)) {
valueProperty.forEach((valuePropertyItem, index) => {
const lookupItem = _objectSpread({}, lookup);
lookupItem.property = `${lookupItem.property || ''}${lookupItem.property ? '.' : ''}${index}`;
this.updateLookupProperty(mongoObject, valuePropertyItem, responseMap, lookupItem, true);
});
}
}
getLookupIDs(value) {
const ids = [];
if (is('string', value) || mongodb.ObjectId.isValid(value) || is('array', value) || is('object', value)) {
if (is('string', value)) ids.push(new mongodb.ObjectId(value));else if (mongodb.ObjectId.isValid(value)) ids.push(value);else if (is('object', value)) ids.push(new mongodb.ObjectId(value?.id || value?._id));else if (is('array', value)) ids.push(...value.flatMap(item => this.getLookupIDs(item)));
}
return ids;
}
toModel(value) {
if (!this.Model || [null, undefined].includes(value)) return value;
return is('array', value) ? value.map(item => new this.Model(item, false)) : new this.Model(value, false);
}
response(start, collection, method, value, req) {
if (is('number', start)) {
const arguments_ = [];
if (collection) arguments_.push(`Collection: ${collection.collectionName}`);
if (method) arguments_.push(`Method: ${method}`);
if (req?.id) arguments_.push(`Request ID: ${req.id}`);
arguments_.push(`Duration: ${duration(OnesyDate.utc.milliseconds - start, true)}`);
this.onesyLog.debug(...arguments_);
}
if (value && this.Model !== undefined) {
switch (method) {
case 'find':
case 'searchMany':
value.response = this.toModel(value.response);
break;
case 'findOne':
case 'aggregate':
case 'searchOne':
case 'addOne':
case 'updateOne':
case 'removeOne':
case 'updateOneOrAdd':
return this.toModel(value);
default:
break;
}
}
return value;
}
query(query) {
let aggregate = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
if (BaseCollection.isOnesyQuery(query)) {
if (aggregate) {
return [...(query?.query || []), ...(query?.queries?.aggregate?.[this.collectionName] || [])];
} else {
return _objectSpread(_objectSpread({}, query?.query), query?.queries?.find?.[this.collectionName]);
}
}
return aggregate ? query || [] : query;
}
getDefaults(method) {
let value = ['aggregate', 'searchMany', 'searchOne'].includes(method) ? [] : {};
// static
if (['aggregate', 'searchMany', 'searchOne'].includes(method)) {
// query
if (is('array', BaseCollection.defaults?.query)) value.push(...BaseCollection.defaults?.query);
// queryArray
if (is('array', BaseCollection.defaults?.queryArray)) value.push(...BaseCollection.defaults?.queryArray);
// method
if (is('array', BaseCollection.defaults?.[method])) value.push(...BaseCollection.defaults?.[method]);
} else {
// query
if (is('object', BaseCollection.defaults?.query)) value = _objectSpread(_objectSpread({}, value), BaseCollection.defaults?.query);
// queryObject
if (is('object', BaseCollection.defaults?.queryObject)) value = _objectSpread(_objectSpread({}, value), BaseCollection.defaults?.queryObject);
// method
if (is('object', BaseCollection.defaults?.[method])) value = _objectSpread(_objectSpread({}, value), BaseCollection.defaults?.[method]);
}
// instance
if (['aggregate', 'searchMany', 'searchOne'].includes(method)) {
// query
if (is('array', this.defaults?.query)) value.push(...this.defaults?.query);
// queryArray
if (is('array', this.defaults?.queryArray)) value.push(...this.defaults?.queryArray);
// method
if (is('array', this.defaults?.[method])) value.push(...this.defaults?.[method]);
} else {
// query
if (is('object', this.defaults?.query)) value = _objectSpread(_objectSpread({}, value), this.defaults?.query);
// queryObject
if (is('object', this.defaults?.queryObject)) value = _objectSpread(_objectSpread({}, value), this.defaults?.queryObject);
// method
if (is('object', this.defaults?.[method])) value = _objectSpread(_objectSpread({}, value), this.defaults?.[method]);
}
}
static value(value) {
// Getter object method
if (is('function', value?.toObject)) return value.toObject();
return _objectSpread({}, value);
}
static isOnesyQuery(value) {
return value instanceof Query || value?.hasOwnProperty('query') && value?.hasOwnProperty('queries');
}
}
_defineProperty(BaseCollection, "defaults", void 0);
export default BaseCollection;