@onesy/mongo
Version:
Utils for easier using of mongodb library.
823 lines (822 loc) • 44.3 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.BaseCollection = void 0;
const mongodb = __importStar(require("mongodb"));
const is_1 = __importDefault(require("@onesy/utils/is"));
const copy_1 = __importDefault(require("@onesy/utils/copy"));
const wait_1 = __importDefault(require("@onesy/utils/wait"));
const getObjectValue_1 = __importDefault(require("@onesy/utils/getObjectValue"));
const setObjectValue_1 = __importDefault(require("@onesy/utils/setObjectValue"));
const models_1 = require("@onesy/models");
const errors_1 = require("@onesy/errors");
const OnesyDate_1 = __importDefault(require("@onesy/date/OnesyDate"));
const duration_1 = __importDefault(require("@onesy/date/duration"));
const Mongo_1 = __importDefault(require("./Mongo"));
const OnesyMongo_1 = __importDefault(require("./OnesyMongo"));
class BaseCollection {
constructor(collectionName, mongo, Model, defaults) {
this.collectionName = collectionName;
this.mongo = mongo;
this.Model = Model;
this.defaults = defaults;
this.collections = {};
if (!(mongo && mongo instanceof Mongo_1.default))
throw new errors_1.OnesyMongoError(`Mongo instance is required`);
if (!collectionName)
throw new errors_1.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(name = this.collectionName, options = {}) {
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 === null || collections === void 0 ? void 0 : 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
models_1.Query.collections.push(collection.collectionName);
models_1.Query.keys.allowed.push(collection.collectionName);
this.onesyLog.info(`${this.collectionName} collection created`);
}
return this.collections[name];
}
async transaction(method, options = { retries: 5, retriesWait: 140 }) {
var _a;
if (!(0, is_1.default)('function', method))
throw new errors_1.DeveloperError('First argument has to be a function');
const transactionOptions = {
readPreference: mongodb.ReadPreference.primary,
readConcern: { level: 'local' },
writeConcern: { w: 'majority' },
};
let response;
const retriesTotal = (0, is_1.default)('number', options.retries) ? options.retries : 5;
const retriesWait = (0, is_1.default)('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 (0, wait_1.default)(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 || ((_a = error_.message) === null || _a === void 0 ? void 0 : _a.codeName);
this.onesyLog.error('session error', error.name, error.code, error.message, error.stack);
}
finally {
await session.endSession();
}
retries--;
}
if (error)
throw new errors_1.DeveloperError(error);
return response;
}
async count(query = new models_1.Query(), options = {}) {
const collection = await this.collection();
const start = OnesyDate_1.default.utc.milliseconds;
try {
const defaults = this.getDefaults('count');
const response = await collection.countDocuments(Object.assign(Object.assign({}, defaults), this.query(query)), options);
return this.response(start, collection, 'count', response);
}
catch (error) {
this.response(start, collection, 'count');
throw new errors_1.OnesyMongoError(error);
}
}
async exists(query, options = {}) {
const collection = await this.collection();
const start = OnesyDate_1.default.utc.milliseconds;
try {
const defaults = this.getDefaults('exists');
const response = await collection.findOne(Object.assign(Object.assign({}, defaults), this.query(query)), Object.assign({ projection: { _id: 1 } }, options));
return this.response(start, collection, 'exists', !!response);
}
catch (error) {
this.response(start, collection, 'exists');
throw new errors_1.OnesyMongoError(error);
}
}
async find(query, options = {}) {
const collection = await this.collection();
const start = OnesyDate_1.default.utc.milliseconds;
try {
const { total, sort, limit, skip } = options, optionsOther = __rest(options, ["total", "sort", "limit", "skip"]);
const defaults = this.getDefaults('find');
const optionsMongo = Object.assign({}, 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 = Object.assign(Object.assign({}, defaults), this.query(query));
const response_ = await collection.find(queryMongo, optionsMongo).toArray();
const response = new models_1.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 errors_1.OnesyMongoError(error);
}
}
async findOne(query, options = {}) {
const collection = await this.collection();
const start = OnesyDate_1.default.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(Object.assign(Object.assign({}, defaults), this.query(query)), options);
return this.response(start, collection, 'findOne', response);
}
catch (error) {
this.response(start, collection, 'findOne');
throw new errors_1.OnesyMongoError(error);
}
}
async aggregate(query = new models_1.Query(), options = {}) {
const collection = await this.collection();
const start = OnesyDate_1.default.utc.milliseconds;
try {
const defaults = this.getDefaults('aggregate');
const response = await collection.aggregate([
// defaults
...(defaults || []),
...this.query(query)
], Object.assign(Object.assign({}, Mongo_1.default.defaults.aggregateOptions), options)).toArray();
return this.response(start, collection, 'aggregate', response);
}
catch (error) {
this.response(start, collection, 'aggregate');
throw new errors_1.OnesyMongoError(error);
}
}
async searchMany(query, additional = { pre: [], prePagination: [], post: [], options: [], lookups: [] }, options = {}) {
var _a;
const collection = await this.collection();
const start = OnesyDate_1.default.utc.milliseconds;
try {
const { total: optionsTotal, sort: optionsSort, limit: optionsLimit = 15, skip: optionsSkip, projection: optionsProjection } = options, optionsOther = __rest(options, ["total", "sort", "limit", "skip", "projection"]);
const defaults = this.getDefaults('searchMany');
const optionsMongo = Object.assign({}, 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 ? (0, models_1.getMongoMatch)(queries.search, query.settings.type) : []),
// API
...(queries.api.length ? (0, models_1.getMongoMatch)(queries.api) : []),
// Permissions
...(queries.permissions.length ? (0, models_1.getMongoMatch)(queries.permissions, '$or') : []),
// Pre pagination
...pre_pagination
];
const pipeline = [
...queryMongo,
// Next paginator
...(next ? (0, models_1.getMongoMatch)([next]) : []),
// Previous paginator
...(previous ? (0, models_1.getMongoMatch)([previous]) : []),
...(hasPaginator ? [{ $sort: { [paginatorProperty]: next ? -1 : 1 } }] : []),
...(sort && !hasPaginator ? [{ $sort: sort }] : []),
// Either skip or a paginator
...(((query === null || query === void 0 ? void 0 : 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, Object.assign(Object.assign({}, Mongo_1.default.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 models_1.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_1.default.createPaginator(last, [this.sortProperty], sort);
if (first)
response['previous'] = OnesyMongo_1.default.createPaginator(first, [this.sortProperty], sort, 'previous');
// lookups
await this.lookups(response.response, additional.lookups, options.request);
// options
if (!!((_a = additional.options) === null || _a === void 0 ? void 0 : _a.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}`
}
}
}))
], Object.assign(Object.assign({}, Mongo_1.default.defaults.aggregateOptions), optionsMongo)).toArray();
const optionsMongoResponse = {};
optionsResponse.forEach(item => { var _a; return optionsMongoResponse[item._id] = ((_a = item.value) === null || _a === void 0 ? void 0 : _a.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_1.default.defaults.limitCount },
{ $group: { _id: null, count: { $sum: 1 } } },
], Object.assign(Object.assign({}, Mongo_1.default.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 errors_1.OnesyMongoError(error);
}
}
async searchOne(query, additional = { pre: [], prePagination: [], post: [], lookups: [] }, options = {}) {
const collection = await this.collection();
const start = OnesyDate_1.default.utc.milliseconds;
try {
const { projection: optionsProjection } = options, optionsOther = __rest(options, ["projection"]);
const defaults = this.getDefaults('searchOne');
const optionsMongo = Object.assign({}, optionsOther);
const limit = 1;
const projection = (BaseCollection.isOnesyQuery(query) ? query.projection : optionsProjection) || this.projection;
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 ? (0, models_1.getMongoMatch)(queries.search, query.settings.type) : []),
// API
...(queries.api.length ? (0, models_1.getMongoMatch)(queries.api) : []),
// Permissions
...(queries.permissions.length ? (0, models_1.getMongoMatch)(queries.permissions, '$or') : []),
// Pre pagination
...pre_pagination
];
const pipeline = [
...queryMongo,
{ $limit: limit },
...(projection ? [{ $project: projection }] : []),
...post,
];
const response = await collection.aggregate(pipeline, Object.assign(Object.assign({}, Mongo_1.default.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 errors_1.OnesyMongoError(error);
}
}
async addOne(value_, options_ = {}) {
const options = Object.assign({ add_date: true }, options_);
const { add_date } = options, optionsMongo = __rest(options, ["add_date"]);
const collection = await this.collection();
const start = OnesyDate_1.default.utc.milliseconds;
try {
const value = BaseCollection.value(value_);
if (!value)
throw new errors_1.OnesyMongoError(`No value provided`);
if (add_date)
(0, setObjectValue_1.default)(value, this.addedProperty || 'added_at', OnesyDate_1.default.utc.milliseconds);
const response = await collection.insertOne(value, optionsMongo);
return this.response(start, collection, 'addOne', Object.assign({ _id: response.insertedId }, value));
}
catch (error) {
this.response(start, collection, 'addOne');
throw new errors_1.OnesyMongoError(error);
}
}
async updateOne(query, value, options_ = { lookups: [] }) {
const options = Object.assign({ update_date: true }, options_);
const { update_date } = options, optionsMongo = __rest(options, ["update_date"]);
const collection = await this.collection();
const start = OnesyDate_1.default.utc.milliseconds;
try {
const defaults = this.getDefaults('updateOne');
if (value !== undefined && !(0, is_1.default)('object', value))
throw new errors_1.OnesyMongoError(`Value has to be an object with update values`);
if ((0, is_1.default)('object', value) && update_date)
value[this.updatedProperty || 'updated_at'] = OnesyDate_1.default.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(Object.assign(Object.assign({}, defaults), this.query(query)), Object.assign(Object.assign({}, operators), ((!!Object.keys(update).length || operators['$set']) && {
$set: Object.assign(Object.assign({}, operators['$set']), update)
})), Object.assign({ 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 errors_1.OnesyMongoError(error);
}
}
async removeOne(query, options = {}) {
const collection = await this.collection();
const start = OnesyDate_1.default.utc.milliseconds;
try {
const defaults = this.getDefaults('removeOne');
const response = await collection.findOneAndDelete(Object.assign(Object.assign({}, defaults), this.query(query)), options);
return this.response(start, collection, 'removeOne', response.value);
}
catch (error) {
this.response(start, collection, 'removeOne');
throw new errors_1.OnesyMongoError(error);
}
}
async updateOneOrAdd(query, value, options_ = {}) {
const options = Object.assign({ add_date: true, update_date: true }, options_);
const { add_date, update_date } = options, optionsMongo = __rest(options, ["add_date", "update_date"]);
const collection = await this.collection();
const start = OnesyDate_1.default.utc.milliseconds;
try {
const defaults = this.getDefaults('updateOneOrAdd');
if (!(0, is_1.default)('object', value))
throw new errors_1.OnesyMongoError(`Value has to be an object with properties and values`);
if (update_date)
value[this.updatedProperty || 'updated_at'] = OnesyDate_1.default.utc.milliseconds;
let setOnInsert;
if (add_date)
setOnInsert = {
[this.addedProperty || 'added_at']: OnesyDate_1.default.utc.milliseconds
};
const response = await collection.findOneAndUpdate(Object.assign(Object.assign({}, defaults), this.query(query)), Object.assign({ $set: value }, (setOnInsert && { $setOnInsert: setOnInsert })), Object.assign({ upsert: true, returnDocument: 'after' }, optionsMongo));
return this.response(start, collection, 'updateOneOrAdd', response.value);
}
catch (error) {
this.response(start, collection, 'updateOneOrAdd');
throw new errors_1.OnesyMongoError(error);
}
}
async addMany(values_, options_ = {}) {
const options = Object.assign({ add_date: true }, options_);
const { add_date } = options, optionsMongo = __rest(options, ["add_date"]);
const collection = await this.collection();
const start = OnesyDate_1.default.utc.milliseconds;
try {
let values = values_.map(item => BaseCollection.value(item));
if (!(values === null || values === void 0 ? void 0 : values.length))
throw new errors_1.OnesyMongoError(`Values have to be a non empty array`);
if (add_date)
values = values.map(item => {
(0, setObjectValue_1.default)(item, this.addedProperty || 'added_at', OnesyDate_1.default.utc.milliseconds);
return item;
});
let response = await collection.insertMany(values, Object.assign({ ordered: false }, optionsMongo));
if (!options.original) {
const ids = Object.keys(response.insertedIds || {}).map(item => { var _a; return (_a = response.insertedIds) === null || _a === void 0 ? void 0 : _a[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 errors_1.OnesyMongoError(error);
}
}
async updateMany(query, value, options_ = {}) {
const options = Object.assign({ update_date: true }, options_);
const { update_date } = options, optionsMongo = __rest(options, ["update_date"]);
const collection = await this.collection();
const start = OnesyDate_1.default.utc.milliseconds;
try {
const defaults = this.getDefaults('updateMany');
if (value !== undefined && !(0, is_1.default)('object', value))
throw new errors_1.OnesyMongoError(`Value has to be an object with properties and values`);
if ((0, is_1.default)('object', value) && update_date)
value[this.updatedProperty || 'updated_at'] = OnesyDate_1.default.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(Object.assign(Object.assign({}, defaults), this.query(query)), Object.assign(Object.assign({}, operators), ((!!Object.keys(update).length || operators['$set']) && {
$set: Object.assign(Object.assign({}, operators['$set']), update)
})), Object.assign({}, optionsMongo));
return this.response(start, collection, 'updateMany', response);
}
catch (error) {
this.response(start, collection, 'updateMany');
throw new errors_1.OnesyMongoError(error);
}
}
async removeMany(query, options = {}) {
const collection = await this.collection();
const start = OnesyDate_1.default.utc.milliseconds;
try {
const defaults = this.getDefaults('removeMany');
const response = await collection.deleteMany(Object.assign(Object.assign({}, defaults), this.query(query)), Object.assign({ ordered: false }, options));
return this.response(start, collection, 'removeMany', response);
}
catch (error) {
this.response(start, collection, 'removeMany');
throw new errors_1.OnesyMongoError(error);
}
}
async bulkWrite(values = [], options_ = {}) {
const options = Object.assign({}, options_);
const collection = await this.collection();
const start = OnesyDate_1.default.utc.milliseconds;
try {
if (!(values === null || values === void 0 ? void 0 : values.length))
throw new errors_1.OnesyMongoError(`Values have to be a non empty array`);
const response = await collection.bulkWrite(values, Object.assign({ ordered: false }, options));
return this.response(start, collection, 'bulkWrite', response);
}
catch (error) {
this.response(start, collection, 'bulkWrite');
throw new errors_1.OnesyMongoError(error);
}
}
async lookups(value_, lookups, request) {
const value = (0, is_1.default)('array', value_) ? value_ : [value_];
if (!!value.length && !!(lookups === null || lookups === void 0 ? void 0 : 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 : (0, getObjectValue_1.default)(item, lookup.property);
ids.push(...this.getLookupIDs(valueProperty));
});
if (!!ids.length) {
// Search objects
const query = lookup.query || [];
if (BaseCollection.isOnesyQuery(query)) {
if ((0, is_1.default)('array', query.query)) {
query.query.unshift({
$match: {
_id: { $in: ids }
}
});
if (lookup.projection) {
query.query.push({
$project: Object.assign({}, lookup.projection)
});
}
}
}
else {
query.unshift({
$match: {
_id: { $in: ids }
}
});
if (lookup.projection) {
query.push({
$project: Object.assign({}, 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, array = false) {
const valueProperty = array ? object : !lookup.property ? object : (0, getObjectValue_1.default)(object, lookup.property);
// string
if ((0, is_1.default)('string', valueProperty)) {
const valueResponse = responseMap[valueProperty];
if (valueResponse !== undefined) {
if (lookup.property)
(0, setObjectValue_1.default)(mongoObject, lookup.property, valueResponse);
else
return valueResponse;
}
}
// mongoDB ObjectId
else if (mongodb.ObjectId.isValid(valueProperty)) {
const valueResponse = responseMap[valueProperty === null || valueProperty === void 0 ? void 0 : valueProperty.toString()];
if (valueResponse !== undefined) {
if (lookup.property)
(0, setObjectValue_1.default)(mongoObject, lookup.property, valueResponse);
else
return valueResponse;
}
}
// object
else if ((0, is_1.default)('object', valueProperty)) {
const id = (valueProperty === null || valueProperty === void 0 ? void 0 : valueProperty.id) || (valueProperty === null || valueProperty === void 0 ? void 0 : valueProperty._id);
const valueResponse = responseMap[id === null || id === void 0 ? void 0 : id.toString()];
const previous = (0, copy_1.default)((0, getObjectValue_1.default)(mongoObject, lookup.property));
if (lookup.override) {
lookup.override.forEach(item => {
(0, setObjectValue_1.default)(valueResponse, item, (0, getObjectValue_1.default)(previous, item));
});
}
if (valueResponse !== undefined) {
if (lookup.property)
(0, setObjectValue_1.default)(mongoObject, lookup.property, valueResponse);
else
return valueResponse;
}
}
// array
else if ((0, is_1.default)('array', valueProperty)) {
valueProperty.forEach((valuePropertyItem, index) => {
const lookupItem = Object.assign({}, lookup);
lookupItem.property = `${lookupItem.property || ''}${lookupItem.property ? '.' : ''}${index}`;
this.updateLookupProperty(mongoObject, valuePropertyItem, responseMap, lookupItem, true);
});
}
}
getLookupIDs(value) {
const ids = [];
if ((0, is_1.default)('string', value) ||
mongodb.ObjectId.isValid(value) ||
(0, is_1.default)('array', value) ||
(0, is_1.default)('object', value)) {
if ((0, is_1.default)('string', value))
ids.push(new mongodb.ObjectId(value));
else if (mongodb.ObjectId.isValid(value))
ids.push(value);
else if ((0, is_1.default)('object', value))
ids.push(new mongodb.ObjectId((value === null || value === void 0 ? void 0 : value.id) || (value === null || value === void 0 ? void 0 : value._id)));
else if ((0, is_1.default)('array', value))
ids.push(...value.flatMap(item => this.getLookupIDs(item)));
}
return ids;
}
toModel(value) {
if (!this.Model || [null, undefined].includes(value))
return value;
return (0, is_1.default)('array', value) ? value.map(item => new this.Model(item, false)) : new this.Model(value, false);
}
response(start, collection, method, value, req) {
if ((0, is_1.default)('number', start)) {
const arguments_ = [];
if (collection)
arguments_.push(`Collection: ${collection.collectionName}`);
if (method)
arguments_.push(`Method: ${method}`);
if (req === null || req === void 0 ? void 0 : req.id)
arguments_.push(`Request ID: ${req.id}`);
arguments_.push(`Duration: ${(0, duration_1.default)(OnesyDate_1.default.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, aggregate = false) {
var _a, _b, _c, _d;
if (BaseCollection.isOnesyQuery(query)) {
if (aggregate) {
return [
...((query === null || query === void 0 ? void 0 : query.query) || []),
...(((_b = (_a = query === null || query === void 0 ? void 0 : query.queries) === null || _a === void 0 ? void 0 : _a.aggregate) === null || _b === void 0 ? void 0 : _b[this.collectionName]) || [])
];
}
else {
return Object.assign(Object.assign({}, query === null || query === void 0 ? void 0 : query.query), (_d = (_c = query === null || query === void 0 ? void 0 : query.queries) === null || _c === void 0 ? void 0 : _c.find) === null || _d === void 0 ? void 0 : _d[this.collectionName]);
}
}
return aggregate ? (query || []) : query;
}
getDefaults(method) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z;
let value = ['aggregate', 'searchMany', 'searchOne'].includes(method) ? [] : {};
// static
if (['aggregate', 'searchMany', 'searchOne'].includes(method)) {
// query
if ((0, is_1.default)('array', (_a = BaseCollection.defaults) === null || _a === void 0 ? void 0 : _a.query))
value.push(...(_b = BaseCollection.defaults) === null || _b === void 0 ? void 0 : _b.query);
// queryArray
if ((0, is_1.default)('array', (_c = BaseCollection.defaults) === null || _c === void 0 ? void 0 : _c.queryArray))
value.push(...(_d = BaseCollection.defaults) === null || _d === void 0 ? void 0 : _d.queryArray);
// method
if ((0, is_1.default)('array', (_e = BaseCollection.defaults) === null || _e === void 0 ? void 0 : _e[method]))
value.push(...(_f = BaseCollection.defaults) === null || _f === void 0 ? void 0 : _f[method]);
}
else {
// query
if ((0, is_1.default)('object', (_g = BaseCollection.defaults) === null || _g === void 0 ? void 0 : _g.query))
value = Object.assign(Object.assign({}, value), (_h = BaseCollection.defaults) === null || _h === void 0 ? void 0 : _h.query);
// queryObject
if ((0, is_1.default)('object', (_j = BaseCollection.defaults) === null || _j === void 0 ? void 0 : _j.queryObject))
value = Object.assign(Object.assign({}, value), (_k = BaseCollection.defaults) === null || _k === void 0 ? void 0 : _k.queryObject);
// method
if ((0, is_1.default)('object', (_l = BaseCollection.defaults) === null || _l === void 0 ? void 0 : _l[method]))
value = Object.assign(Object.assign({}, value), (_m = BaseCollection.defaults) === null || _m === void 0 ? void 0 : _m[method]);
}
// instance
if (['aggregate', 'searchMany', 'searchOne'].includes(method)) {
// query
if ((0, is_1.default)('array', (_o = this.defaults) === null || _o === void 0 ? void 0 : _o.query))
value.push(...(_p = this.defaults) === null || _p === void 0 ? void 0 : _p.query);
// queryArray
if ((0, is_1.default)('array', (_q = this.defaults) === null || _q === void 0 ? void 0 : _q.queryArray))
value.push(...(_r = this.defaults) === null || _r === void 0 ? void 0 : _r.queryArray);
// method
if ((0, is_1.default)('array', (_s = this.defaults) === null || _s === void 0 ? void 0 : _s[method]))
value.push(...(_t = this.defaults) === null || _t === void 0 ? void 0 : _t[method]);
}
else {
// query
if ((0, is_1.default)('object', (_u = this.defaults) === null || _u === void 0 ? void 0 : _u.query))
value = Object.assign(Object.assign({}, value), (_v = this.defaults) === null || _v === void 0 ? void 0 : _v.query);
// queryObject
if ((0, is_1.default)('object', (_w = this.defaults) === null || _w === void 0 ? void 0 : _w.queryObject))
value = Object.assign(Object.assign({}, value), (_x = this.defaults) === null || _x === void 0 ? void 0 : _x.queryObject);
// method
if ((0, is_1.default)('object', (_y = this.defaults) === null || _y === void 0 ? void 0 : _y[method]))
value = Object.assign(Object.assign({}, value), (_z = this.defaults) === null || _z === void 0 ? void 0 : _z[method]);
}
}
static value(value) {
// Getter object method
if ((0, is_1.default)('function', value === null || value === void 0 ? void 0 : value.toObject))
return value.toObject();
return Object.assign({}, value);
}
static isOnesyQuery(value) {
return value instanceof models_1.Query || ((value === null || value === void 0 ? void 0 : value.hasOwnProperty('query')) && (value === null || value === void 0 ? void 0 : value.hasOwnProperty('queries')));
}
}
exports.BaseCollection = BaseCollection;
exports.default = BaseCollection;