UNPKG

@athenna/database

Version:

The Athenna database handler for SQL/NoSQL.

1,196 lines (1,195 loc) 37.6 kB
/** * @athenna/database * * (c) João Lenon <lenon@athenna.io> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ import { Is, Json, Exec, Options } from '@athenna/common'; import { debug } from '#src/debug'; import { Log } from '@athenna/logger'; import { ObjectId } from '#src/helpers/ObjectId'; import { Driver } from '#src/database/drivers/Driver'; import { ModelSchema } from '#src/models/schemas/ModelSchema'; import { Transaction } from '#src/database/transactions/Transaction'; import { ConnectionFactory } from '#src/factories/ConnectionFactory'; import { WrongMethodException } from '#src/exceptions/WrongMethodException'; import { MONGO_OPERATIONS_DICTIONARY } from '#src/constants/MongoOperationsDictionary'; import { NotConnectedDatabaseException } from '#src/exceptions/NotConnectedDatabaseException'; import { NotImplementedMethodException } from '#src/exceptions/NotImplementedMethodException'; export class MongoDriver extends Driver { constructor() { super(...arguments); this.primaryKey = '_id'; this.session = null; /** * The where clause used in update queries. */ this._where = []; /** * The or where clause used in update queries. */ this._orWhere = []; /** * The aggregate pipeline to make mongo queries. */ this.pipeline = []; } /** * Set the mongo session that should be used by the driver. */ setSession(session) { this.session = session; return this; } /** * Connect to database. */ connect(options = {}) { options = Options.create(options, { force: false, saveOnFactory: true, connect: true }); if (!options.connect) { return; } if (this.isConnected && !options.force) { return; } const mongoose = this.getMongoose(); const configs = Config.get(`database.connections.${this.connection}`, {}); if (configs.debug !== undefined) { mongoose.set('debug', configs.debug); } const mongoOpts = Json.omit(configs, [ 'url', 'debug', 'driver', 'validations' ]); debug('creating new connection using mongoose. options defined: %o', { url: configs.url, debug: configs.debug, ...mongoOpts }); this.client = mongoose.createConnection(configs.url, mongoOpts); this.client.on('connected', () => { if (Config.is('rc.bootLogs', true)) { Log.channelOrVanilla('application').success(`Successfully connected to ({yellow} ${this.connection}) database connection`); } }); this.isConnected = true; this.isSavedOnFactory = options.saveOnFactory; if (this.isSavedOnFactory) { ConnectionFactory.setClient(this.connection, this.client); } this.qb = this.query(); } /** * Close the connection with database in this instance. */ async close() { if (!this.isConnected) { return; } await this.client.close(); this.qb = null; this.tableName = null; this.client = null; this.session = null; this.isConnected = false; ConnectionFactory.setClient(this.connection, null); } /** * Creates a new instance of query builder. */ query() { if (!this.isConnected) { throw new NotConnectedDatabaseException(); } return this.client.collection(this.tableName || 'lock'); } /** * Sync a model schema with database. */ async sync(schema) { const columns = {}; const mongoose = await import('mongoose'); schema.columns.forEach(column => { columns[column.name] = {}; if (column.type !== undefined) { columns[column.name].type = column.type; } if (column.isNullable !== undefined) { columns[column.name].required = column.isNullable; } if (column.length !== undefined) { columns[column.name].maxLength = column.length; } if (column.defaultTo !== undefined) { columns[column.name].default = column.defaultTo; } if (column.isIndex !== undefined) { columns[column.name].index = column.isIndex; } if (column.isSparse !== undefined) { columns[column.name].sparse = column.isSparse; } if (column.isUnique !== undefined) { columns[column.name].unique = column.isUnique; } if (columns[column.name].unique || columns[column.name].sparse) { columns[column.name].index = true; } }); /** * Relations will not be registered because * Athenna will handle them instead of mongoose. */ return this.client .model(schema.getModelName(), new mongoose.Schema(columns)) .createIndexes(); } /** * Create a new transaction. */ async startTransaction() { await this.client.asPromise(); const session = await this.client.startSession(); session.startTransaction(); return new Transaction(new MongoDriver(this.connection, this.client).setSession(session)); } /** * Commit the transaction. */ async commitTransaction() { await this.client.asPromise(); await this.session.commitTransaction(); await this.session.endSession(); this.tableName = null; this.client = null; this.session = null; this.isConnected = false; } /** * Rollback the transaction. */ async rollbackTransaction() { await this.client.asPromise(); await this.session.abortTransaction(); await this.session.endSession(); this.tableName = null; this.client = null; this.session = null; this.isConnected = false; } /** * Run database migrations. */ async runMigrations() { throw new NotImplementedMethodException(this.runMigrations.name, 'mongo'); } /** * Revert database migrations. */ async revertMigrations() { throw new NotImplementedMethodException(this.revertMigrations.name, 'mongo'); } /** * List all databases available. */ async getDatabases() { await this.client.asPromise(); const admin = this.client.db.admin(); const { databases } = await admin.listDatabases(); return databases.map(database => database.name); } /** * Get the current database name. */ async getCurrentDatabase() { await this.client.asPromise(); return this.client.db.databaseName; } /** * Verify if database exists. */ async hasDatabase(database) { await this.client.asPromise(); const databases = await this.getDatabases(); return databases.includes(database); } /** * Create a new database. */ async createDatabase() { throw new NotImplementedMethodException(this.createDatabase.name, 'mongo'); } /** * Drop some database. */ async dropDatabase(database) { await this.client.asPromise(); await this.client.useDb(database).dropDatabase(); } /** * List all tables available. */ async getTables() { await this.client.asPromise(); const collections = await this.client.db.listCollections().toArray(); return collections.map(collection => collection.name); } /** * Verify if table exists. */ async hasTable(table) { await this.client.asPromise(); const tables = await this.getTables(); return tables.includes(table); } /** * Create a new table in database. */ async createTable() { throw new NotImplementedMethodException(this.createTable.name, 'mongo'); } /** * Drop a table in database. */ async dropTable(table) { try { await this.client.asPromise(); await this.client.dropCollection(table); } catch (err) { debug('error happened while dropping table %s in MongoDriver: %o', err); } } /** * Remove all data inside some database table * and restart the identity of the table. */ async truncate(table) { await this.client.asPromise(); const collection = this.client.collection(table); await collection.deleteMany({}, { session: this.session }); } /** * Make a raw query in database. */ raw() { throw new NotImplementedMethodException(this.raw.name, 'mongo'); } /** * Calculate the average of a given column. */ async avg(column) { await this.client.asPromise(); const pipeline = this.createPipeline(); pipeline.push({ $group: { [this.primaryKey]: null, avg: { $avg: `$${column}` } } }); pipeline.push({ $project: { [this.primaryKey]: 0, avg: 1 } }); const result = await this.qb .aggregate(pipeline, { session: this.session }) .toArray(); if (Is.Empty(result)) { return null; } return `${result[0].avg}`; } /** * Calculate the average of a given column using distinct. */ async avgDistinct(column) { await this.client.asPromise(); const pipeline = this.createPipeline(); pipeline.push({ $group: { [this.primaryKey]: null, set: { $addToSet: `$${column}` } } }); pipeline.push({ $project: { [this.primaryKey]: 0, avg: { $avg: '$set' } } }); const result = await this.qb .aggregate(pipeline, { session: this.session }) .toArray(); if (Is.Empty(result)) { return null; } return `${result[0].avg}`; } /** * Get the max number of a given column. */ async max(column) { await this.client.asPromise(); const pipeline = this.createPipeline(); pipeline.push({ $group: { [this.primaryKey]: null, max: { $max: `$${column}` } } }); pipeline.push({ $project: { [this.primaryKey]: 0, max: 1 } }); const result = await this.qb .aggregate(pipeline, { session: this.session }) .toArray(); if (Is.Empty(result)) { return null; } return `${result[0].max}`; } /** * Get the min number of a given column. */ async min(column) { await this.client.asPromise(); const pipeline = this.createPipeline(); pipeline.push({ $group: { [this.primaryKey]: null, min: { $min: `$${column}` } } }); pipeline.push({ $project: { [this.primaryKey]: 0, min: 1 } }); const result = await this.qb .aggregate(pipeline, { session: this.session }) .toArray(); if (Is.Empty(result)) { return null; } return `${result[0].min}`; } /** * Sum all numbers of a given column. */ async sum(column) { await this.client.asPromise(); const pipeline = this.createPipeline(); pipeline.push({ $group: { [this.primaryKey]: null, sum: { $sum: `$${column}` } } }); pipeline.push({ $project: { [this.primaryKey]: 0, sum: 1 } }); const result = await this.qb .aggregate(pipeline, { session: this.session }) .toArray(); if (Is.Empty(result)) { return null; } return `${result[0].sum}`; } /** * Sum all numbers of a given column in distinct mode. */ async sumDistinct(column) { await this.client.asPromise(); const pipeline = this.createPipeline(); pipeline.push({ $group: { [this.primaryKey]: null, set: { $addToSet: `$${column}` } } }); pipeline.push({ $project: { [this.primaryKey]: 0, sum: { $sum: '$set' } } }); const result = await this.qb .aggregate(pipeline, { session: this.session }) .toArray(); if (Is.Empty(result)) { return null; } return `${result[0].sum}`; } /** * Increment a value of a given column. */ async increment(column) { await this.client.asPromise(); const where = this.createWhere(); await this.qb.updateMany(where, { $inc: { [column]: 1 } }, { session: this.session, upsert: false }); } /** * Decrement a value of a given column. */ async decrement(column) { await this.client.asPromise(); const where = this.createWhere(); await this.qb.updateMany(where, { $inc: { [column]: -1 } }, { session: this.session, upsert: false }); } /** * Calculate the average of a given column using distinct. */ async count(column = '*') { await this.client.asPromise(); const pipeline = this.createPipeline(); if (column !== '*') { pipeline.push({ $match: { [column]: { $ne: null } } }); } pipeline.push({ $group: { [this.primaryKey]: null, count: { $sum: 1 } } }); pipeline.push({ $project: { [this.primaryKey]: 0, count: 1 } }); const result = await this.qb .aggregate(pipeline, { session: this.session }) .toArray(); return `${result[0]?.count || 0}`; } /** * Calculate the average of a given column using distinct. */ async countDistinct(column) { await this.client.asPromise(); const pipeline = this.createPipeline(); if (column !== '*') { pipeline.push({ $match: { [column]: { $ne: null } } }); } pipeline.push({ $group: { [this.primaryKey]: null, set: { $addToSet: `$${column}` } } }); pipeline.push({ $project: { [this.primaryKey]: 0, count: { $size: `$set` } } }); const [{ count }] = await this.qb .aggregate(pipeline, { session: this.session }) .toArray(); return `${count}`; } /** * Find a value in database. */ async find() { await this.client.asPromise(); const pipeline = this.createPipeline(); const data = await this.qb .aggregate(pipeline, { session: this.session }) .toArray(); return data[0]; } /** * Find many values in database. */ async findMany() { await this.client.asPromise(); const pipeline = this.createPipeline(); return this.qb .aggregate(pipeline, { session: this.session }) .toArray(); } /** * Find many values in database and return as paginated response. */ async paginate(page = { page: 0, limit: 10, resourceUrl: '/' }, limit = 10, resourceUrl = '/') { await this.client.asPromise(); if (Is.Number(page)) { page = { page, limit, resourceUrl }; } const pipeline = this.createPipeline({ clearWhere: false, clearOrWhere: false, clearPipeline: false }); pipeline.push({ $group: { [this.primaryKey]: null, count: { $sum: 1 } } }); pipeline.push({ $project: { [this.primaryKey]: 0, count: 1 } }); const result = await this.qb .aggregate(pipeline, { session: this.session }) .toArray(); const count = result[0]?.count || 0; const data = await this.offset(page.page).limit(page.limit).findMany(); return Exec.pagination(data, count, page); } /** * Create a value in database. */ async create(data = {}) { if (Is.Array(data)) { throw new WrongMethodException('create', 'createMany'); } await this.client.asPromise(); const created = await this.createMany([data]); return created[0]; } /** * Create many values in database. */ async createMany(data = []) { if (!Is.Array(data)) { throw new WrongMethodException('createMany', 'create'); } await this.client.asPromise(); const { insertedIds } = await this.qb.insertMany(data, { session: this.session }); const insertedIdsArray = []; Object.keys(insertedIds).forEach(key => insertedIdsArray.push(insertedIds[key])); return this.whereIn(this.primaryKey, insertedIdsArray).findMany(); } /** * Create data or update if already exists. */ async createOrUpdate(data = {}) { await this.client.asPromise(); const pipeline = this.createPipeline(); const hasValue = (await this.qb.aggregate(pipeline, { session: this.session }).toArray())[0]; if (hasValue) { return this.where(this.primaryKey, hasValue[this.primaryKey]).update(data); } return this.create(data); } /** * Update a value in database. */ async update(data) { await this.client.asPromise(); const where = this.createWhere({ clearWhere: false, clearOrWhere: false }); const pipeline = this.createPipeline(); await this.qb.updateMany(where, { $set: data }, { upsert: false, session: this.session }); const result = await this.qb .aggregate(pipeline, { session: this.session }) .toArray(); if (result.length === 1) { return result[0]; } return result; } /** * Delete one value in database. */ async delete() { await this.client.asPromise(); await this.qb.deleteMany(this.createWhere(), { session: this.session }); } /** * Set the table that this query will be executed. */ table(table) { if (!this.isConnected) { throw new NotConnectedDatabaseException(); } this.tableName = table; this.qb = this.query(); return this; } /** * Log in console the actual query built. */ dump() { console.log({ where: this._where, orWhere: this._orWhere, pipeline: this.pipeline }); return this; } /** * Set the columns that should be selected on query. */ select(...columns) { if (columns.includes('*')) { return this; } if (!columns.includes('_id')) { const isAlreadyHide = !!this.pipeline .map(step => { if (!step.$project) { return false; } if (!step.$project._id) { return false; } if (step.$project._id === 0) { return false; } return true; }) .find(value => value === true); if (!isAlreadyHide) { this.pipeline.push({ $project: { _id: 0 } }); } } const $project = columns.reduce((previous, column) => { if (column.includes(`${this.tableName}.`)) { column = column.replace(`${this.tableName}.`, ''); } if (column.includes(' as ')) { const [select, alias] = column.split(' as '); previous[select] = 0; previous[alias] = `$${select}`; return previous; } previous[column] = 1; return previous; }, {}); this.pipeline.push({ $project }); return this; } /** * Set the columns that should be selected on query raw. */ selectRaw() { throw new NotImplementedMethodException(this.selectRaw.name, 'mongo'); } /** * Set the table that should be used on query. * Different from `table()` method, this method * doesn't change the driver table. */ from() { throw new NotImplementedMethodException(this.from.name, 'mongo'); } /** * Set the table that should be used on query raw. * Different from `table()` method, this method * doesn't change the driver table. */ fromRaw() { throw new NotImplementedMethodException(this.selectRaw.name, 'mongo'); } /** * Set a join statement in your query. */ join(table, column1, operation, column2) { let foreignField = column2 || operation || this.primaryKey; if (foreignField.includes('.')) { foreignField = foreignField.split('.')[1]; } let localField = column1 || this.primaryKey; if (localField.includes('.')) { localField = localField.split('.')[1]; } this.pipeline.push({ $lookup: { from: table, localField, foreignField, as: table } }); return this; } /** * Set a left join statement in your query. */ leftJoin(table, column1, operation, column2) { return this.join(table, column1, operation, column2); } /** * Set a right join statement in your query. */ rightJoin(table, column1, operation, column2) { return this.join(table, column1, operation, column2); } /** * Set a cross join statement in your query. */ crossJoin(table, column1, operation, column2) { return this.join(table, column1, operation, column2); } /** * Set a full outer join statement in your query. */ fullOuterJoin(table, column1, operation, column2) { return this.join(table, column1, operation, column2); } /** * Set a left outer join statement in your query. */ leftOuterJoin(table, column1, operation, column2) { return this.join(table, column1, operation, column2); } /** * Set a right outer join statement in your query. */ rightOuterJoin(table, column1, operation, column2) { return this.join(table, column1, operation, column2); } /** * Set a join raw statement in your query. */ joinRaw() { throw new NotImplementedMethodException(this.joinRaw.name, 'mongo'); } /** * Set a group by statement in your query. */ groupBy(...columns) { const $group = { [this.primaryKey]: {} }; columns.forEach(column => ($group[this.primaryKey][column] = `$${column}`)); this.pipeline.push({ $group }); this.pipeline.push({ $replaceRoot: { newRoot: `$${this.primaryKey}` } }); return this; } /** * Set a group by raw statement in your query. */ groupByRaw() { throw new NotImplementedMethodException(this.groupByRaw.name, 'mongo'); } /** * Set a having statement in your query. */ having(column, operation, value) { return this.where(column, operation, value); } /** * Set a having raw statement in your query. */ havingRaw() { throw new NotImplementedMethodException(this.havingRaw.name, 'mongo'); } /** * Set a having exists statement in your query. */ havingExists() { throw new NotImplementedMethodException(this.havingExists.name, 'mongo'); } /** * Set a having not exists statement in your query. */ havingNotExists() { throw new NotImplementedMethodException(this.havingNotExists.name, 'mongo'); } /** * Set a having in statement in your query. */ havingIn(column, values) { return this.whereIn(column, values); } /** * Set a having not in statement in your query. */ havingNotIn(column, values) { return this.whereNotIn(column, values); } /** * Set a having between statement in your query. */ havingBetween(column, values) { return this.whereBetween(column, values); } /** * Set a having not between statement in your query. */ havingNotBetween(column, values) { return this.whereNotBetween(column, values); } /** * Set a having null statement in your query. */ havingNull(column) { return this.whereNull(column); } /** * Set a having not null statement in your query. */ havingNotNull(column) { return this.whereNotNull(column); } /** * Set an or having statement in your query. */ orHaving(column, operation, value) { return this.orWhere(column, operation, value); } /** * Set an or having raw statement in your query. */ orHavingRaw() { throw new NotImplementedMethodException(this.orHavingRaw.name, 'mongo'); } /** * Set an or having exists statement in your query. */ orHavingExists() { throw new NotImplementedMethodException(this.orHavingExists.name, 'mongo'); } /** * Set an or having not exists statement in your query. */ orHavingNotExists() { throw new NotImplementedMethodException(this.orHavingNotExists.name, 'mongo'); } /** * Set an or having in statement in your query. */ orHavingIn(column, values) { return this.orWhereIn(column, values); } /** * Set an or having not in statement in your query. */ orHavingNotIn(column, values) { return this.orWhereNotIn(column, values); } /** * Set an or having between statement in your query. */ orHavingBetween(column, values) { return this.orWhereBetween(column, values); } /** * Set an or having not between statement in your query. */ orHavingNotBetween(column, values) { return this.orWhereNotBetween(column, values); } /** * Set an or having null statement in your query. */ orHavingNull(column) { return this.whereNull(column); } /** * Set an or having not null statement in your query. */ orHavingNotNull(column) { return this.whereNotNull(column); } /** * Set a where statement in your query. */ where(statement, operation, value) { if (Is.Function(statement)) { statement(this); return this; } if (operation === undefined) { this._where.push(statement); return this; } if (value === undefined) { this._where.push({ [statement]: this.setOperator(operation, '=') }); return this; } this._where.push({ [statement]: this.setOperator(value, operation) }); return this; } /** * Set a where not statement in your query. */ whereNot(statement, value) { return this.where(statement, '<>', value); } /** * Set a where raw statement in your query. */ whereRaw() { throw new NotImplementedMethodException(this.whereRaw.name, 'mongo'); } /** * Set a where exists statement in your query. */ whereExists() { throw new NotImplementedMethodException(this.whereExists.name, 'mongo'); } /** * Set a where not exists statement in your query. */ whereNotExists() { throw new NotImplementedMethodException(this.whereNotExists.name, 'mongo'); } /** * Set a where like statement in your query. */ whereLike(column, value) { return this.where(column, 'like', value); } /** * Set a where ILike statement in your query. */ whereILike(column, value) { return this.where(column, 'ilike', value); } /** * Set a where in statement in your query. */ whereIn(column, values) { values = values.flatMap(value => { if (ObjectId.isValidStringOrObject(value)) { return [String(value), new ObjectId(value)]; } return [value]; }); this._where.push({ [column]: { $in: values } }); return this; } /** * Set a where not in statement in your query. */ whereNotIn(column, values) { values = values.flatMap(value => { if (ObjectId.isValidStringOrObject(value)) { return [String(value), new ObjectId(value)]; } return [value]; }); this._where.push({ [column]: { $nin: values } }); return this; } /** * Set a where between statement in your query. */ whereBetween(column, values) { this._where.push({ [column]: { $gte: values[0], $lte: values[1] } }); return this; } /** * Set a where not between statement in your query. */ whereNotBetween(column, values) { this._where.push({ [column]: { $not: { $gte: values[0], $lte: values[1] } } }); return this; } /** * Set a where null statement in your query. */ whereNull(column) { this._where.push({ [column]: null }); return this; } /** * Set a where not null statement in your query. */ whereNotNull(column) { this._where.push({ [column]: { $ne: null } }); return this; } /** * Set a or where statement in your query. */ orWhere(statement, operation, value) { if (Is.Function(statement)) { statement(this); return this; } if (operation === undefined) { this._orWhere.push(statement); return this; } if (value === undefined) { this._orWhere.push({ [statement]: this.setOperator(operation, '=') }); return this; } this._orWhere.push({ [statement]: this.setOperator(value, operation) }); return this; } /** * Set an or where not statement in your query. */ orWhereNot(statement, value) { return this.orWhere(statement, '<>', value); } /** * Set a or where raw statement in your query. */ orWhereRaw() { throw new NotImplementedMethodException(this.orWhereRaw.name, 'mongo'); } /** * Set an or where exists statement in your query. */ orWhereExists() { throw new NotImplementedMethodException(this.orWhereExists.name, 'mongo'); } /** * Set an or where not exists statement in your query. */ orWhereNotExists() { throw new NotImplementedMethodException(this.orWhereNotExists.name, 'mongo'); } /** * Set an or where like statement in your query. */ orWhereLike(column, value) { return this.orWhere(column, 'like', value); } /** * Set an or where ILike statement in your query. */ orWhereILike(column, value) { return this.orWhere(column, 'ilike', value); } /** * Set an or where in statement in your query. */ orWhereIn(column, values) { values = values.flatMap(value => { if (ObjectId.isValidStringOrObject(value)) { return [String(value), new ObjectId(value)]; } return [value]; }); this._orWhere.push({ [column]: { $in: values } }); return this; } /** * Set an or where not in statement in your query. */ orWhereNotIn(column, values) { values = values.flatMap(value => { if (ObjectId.isValidStringOrObject(value)) { return [String(value), new ObjectId(value)]; } return [value]; }); this._orWhere.push({ [column]: { $nin: values } }); return this; } /** * Set an or where between statement in your query. */ orWhereBetween(column, values) { this._orWhere.push({ [column]: { $gte: values[0], $lte: values[1] } }); return this; } /** * Set an or where not between statement in your query. */ orWhereNotBetween(column, values) { this._orWhere.push({ [column]: { $not: { $gte: values[0], $lte: values[1] } } }); return this; } /** * Set an or where null statement in your query. */ orWhereNull(column) { this._orWhere.push({ [column]: null }); return this; } /** * Set an or where not null statement in your query. */ orWhereNotNull(column) { this._orWhere.push({ [column]: { $ne: null } }); return this; } /** * Set an order by statement in your query. */ orderBy(column, direction = 'ASC') { this.pipeline.push({ $sort: { [column]: direction.toLowerCase() === 'asc' ? 1 : -1 } }); return this; } /** * Set an order by raw statement in your query. */ orderByRaw() { throw new NotImplementedMethodException(this.orderByRaw.name, 'mongo'); } /** * Order the results easily by the latest date. By default, the result will * be ordered by the table's "createdAt" column. */ latest(column = 'createdAt') { return this.orderBy(column, 'DESC'); } /** * Order the results easily by the oldest date. By default, the result will * be ordered by the table's "createdAt" column. */ oldest(column = 'createdAt') { return this.orderBy(column, 'ASC'); } /** * Set the skip number in your query. */ offset(number) { this.pipeline.push({ $skip: number }); return this; } /** * Set the limit number in your query. */ limit(number) { this.pipeline.push({ $limit: number }); return this; } /** * Set the mongo operation in value. */ setOperator(value, operator) { if (operator === '=') { return value; } const mongoOperator = MONGO_OPERATIONS_DICTIONARY[operator]; const object = { [mongoOperator]: value }; if (operator === 'like' || operator === 'ilike') { let valueRegexString = value.replace(/%/g, ''); if (!value.startsWith('%') && value.endsWith('%')) { valueRegexString = `^${valueRegexString}`; } else if (value.startsWith('%') && !value.endsWith('%')) { valueRegexString = `${valueRegexString}$`; } object[mongoOperator] = new RegExp(valueRegexString); } if (operator === 'ilike') { object.$options = 'i'; } return object; } /** * Creates the where clause with where and orWhere. */ createWhere(options = {}) { options = Options.create(options, { clearWhere: true, clearOrWhere: true }); const where = {}; if (!Is.Empty(this._where)) { where.$and = Json.copy(this._where).map(condition => { const keysToSwap = Object.keys(condition).filter(key => { const value = condition[key]; if (ObjectId.isValidStringOrObject(value)) { return true; } return false; }); keysToSwap.forEach(key => { if (!condition.$or) { condition.$or = []; } const objectId = condition[key]; condition.$or.push({ [key]: String(objectId) }, { [key]: new ObjectId(objectId) }); delete condition[key]; }); return condition; }); } if (!Is.Empty(this._orWhere)) { where.$or = Json.copy(this._orWhere).map(condition => { const keysToSwap = Object.keys(condition).filter(key => { const value = condition[key]; if (ObjectId.isValidStringOrObject(value)) { return true; } return false; }); keysToSwap.forEach(key => { if (!condition.$or) { condition.$or = []; } const objectId = condition[key]; condition.$or.push({ [key]: String(objectId) }, { [key]: new ObjectId(objectId) }); delete condition[key]; }); return condition; }); } if (options.clearWhere) { this._where = []; } if (options.clearOrWhere) { this._orWhere = []; } return where; } /** * Creates the aggregation pipeline. */ createPipeline(options = {}) { options = Options.create(options, { clearWhere: true, clearOrWhere: true, clearPipeline: true }); const pipeline = Json.copy(this.pipeline); if (options.clearPipeline) { this.pipeline = []; } pipeline.push({ $match: this.createWhere(options) }); return pipeline; } }