UNPKG

@adonisjs/lucid

Version:

SQL ORM built on top of Active Record pattern

227 lines (226 loc) 6.71 kB
/* * @adonisjs/lucid * * (c) Harminder Virk <virk@adonisjs.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ import slash from 'slash'; import { join, extname } from 'node:path'; import { Exception, fsReadAll, isScriptFile } from '@poppinss/utils'; import { fileURLToPath, pathToFileURL } from 'node:url'; import * as errors from '../errors.js'; import { DateTime } from 'luxon'; import equal from 'fast-deep-equal'; /** * Ensure that relation is defined */ export function ensureRelation(name, relation) { if (!relation) { throw new Exception(`Cannot process unregistered relationship ${name}`, { status: 500 }); } return true; } /** * Ensure a key value is not null or undefined inside an object. */ export function ensureValue(collection, key, missingCallback) { const value = collection[key]; if (value === undefined || value === null) { missingCallback(); return; } return value; } /** * Collects values for a key inside an array. Similar to `Array.map`, but * reports missing values. */ export function collectValues(payload, key, missingCallback) { return payload.map((row) => { return ensureValue(row, key, missingCallback); }); } /** * Transform value if it is an instance of DateTime, so it can be processed by query builder */ export function transformDateValue(value, dialect) { if (DateTime.isDateTime(value)) { return value.toFormat(dialect.dateTimeFormat); } return value; } /** * Compare two values deeply whether they are equal or not */ export function compareValues(valueA, valueB) { if (DateTime.isDateTime(valueA) || DateTime.isDateTime(valueB)) { return DateTime.isDateTime(valueA) && DateTime.isDateTime(valueB) ? valueA.equals(valueB) : valueA === valueB; } else { return equal(valueA, valueB); } } /** * Raises exception when a relationship `booted` property is false. */ export function ensureRelationIsBooted(relation) { if (!relation.booted) { throw new errors.E_RUNTIME_EXCEPTION([ 'Relationship is not booted. Make sure to call boot first', ]); } } /** * Returns the value for a key from the model instance and raises descriptive * exception when the value is missing */ export function getValue(model, key, relation, action = 'preload') { return ensureValue(model, key, () => { throw new Exception(`Cannot ${action} "${relation.relationName}", value of "${relation.model.name}.${key}" is undefined`, { status: 500 }); }); } /** * Helper to find if value is a valid Object or * not */ export function isObject(value) { return value !== null && typeof value === 'object' && !Array.isArray(value); } /** * Drops duplicate values from an array */ export function unique(value) { if (!Array.isArray(value)) { return []; } return [...new Set(value)]; } /** * Returns a diff of rows to be updated or inserted when performing * a many to many `attach` */ export function syncDiff(original, incoming) { const diff = Object.keys(incoming).reduce((result, incomingRowId) => { const originalRow = original[incomingRowId]; const incomingRow = incoming[incomingRowId]; /** * When there isn't any matching row, we need to insert * the upcoming row */ if (!originalRow) { result.added[incomingRowId] = incomingRow; } else if (Object.keys(incomingRow).find((key) => incomingRow[key] !== originalRow[key])) { /** * If any of the row attributes are different, then we must * update that row */ result.updated[incomingRowId] = incomingRow; } return result; }, { added: {}, updated: {} }); return diff; } /** * Invokes a callback by wrapping it inside managed transaction * when passed client is not transaction itself. */ export async function managedTransaction(client, callback) { const isManagedTransaction = !client.isTransaction; const trx = client.isTransaction ? client : await client.transaction(); if (!isManagedTransaction) { return callback(trx); } try { const response = await callback(trx); await trx.commit(); return response; } catch (error) { await trx.rollback(); throw error; } } /** * Returns the sql method for a DDL statement */ export function getDDLMethod(sql) { sql = sql.toLowerCase(); if (sql.startsWith('create')) { return 'create'; } if (sql.startsWith('alter')) { return 'alter'; } if (sql.startsWith('drop')) { return 'drop'; } return 'unknown'; } /** * Normalizes the cherry-picking object to always be an object with * `pick` and `omit` properties */ export function normalizeCherryPickObject(fields) { if (Array.isArray(fields)) { return { pick: fields, omit: [], }; } return { pick: fields.pick, omit: fields.omit, }; } /** * Sources files from a given directory */ export async function sourceFiles(fromLocation, directory, naturalSort) { const absDirectoryPath = fileURLToPath(new URL(directory, fromLocation)); let files = await fsReadAll(absDirectoryPath, { filter: isScriptFile, ignoreMissingRoot: true, }); /** * Sort files */ if (naturalSort) { files = files.sort((a, b) => a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' })); } else { files = files.sort(); } return { directory, files: files.map((file) => { const name = join(directory, file.replace(RegExp(`${extname(file)}$`), '')); return { /** * Absolute path to the file. Needed to ready the schema source */ absPath: join(absDirectoryPath, file), /** * Normalizing name to always have unix slashes. */ name: slash(name), /** * Import schema file */ async getSource() { const exports = await import(pathToFileURL(this.absPath).href); if (!exports.default) { throw new Error(`Missing default export from "${this.name}" schema file`); } return exports.default; }, }; }), }; }