UNPKG

mevn-orm

Version:
291 lines (290 loc) 9.25 kB
import knexModule from 'knex'; const knexFactory = knexModule.knex ?? knexModule; const toError = (error) => { if (error instanceof Error) { return error; } return new Error(String(error)); }; /** Active Knex instance. Available after calling {@link configure} or {@link configureDatabase}. */ let DB; let defaultMigrationConfig = {}; /** * Returns the active Knex instance. * * @returns Configured Knex client. * @throws When {@link configure} or {@link configureDatabase} has not been called. */ const getDB = () => { if (!DB) { throw new Error('Mevn ORM is not configured. Call configure({ client, connection, ... }) before using Model.'); } return DB; }; /** * Initialises the ORM with a raw Knex config object or an existing Knex instance. * * @param config - Knex configuration or a pre-built Knex instance. * @returns The active Knex client (also available as {@link DB}). */ const configure = (config) => { if (typeof config === 'function') { DB = config; return DB; } DB = knexFactory(config); return DB; }; /** * Sets default migration options used by {@link makeMigration}, {@link migrateLatest}, * and other migration helpers. * * @param config - Knex migrator config (typically `directory` and `extension`). * @returns A copy of the stored migration config. * * @example * ```ts * setMigrationConfig({ directory: './migrations', extension: 'ts' }) * ``` */ const setMigrationConfig = (config) => { defaultMigrationConfig = { ...config }; return { ...defaultMigrationConfig }; }; /** * Returns the currently configured default migration options. * * @returns Copy of the migration config set via {@link setMigrationConfig}. */ const getMigrationConfig = () => ({ ...defaultMigrationConfig }); const resolveMigrationConfig = (config) => ({ ...defaultMigrationConfig, ...(config ?? {}), }); const getConfiguredClient = (config) => { const configuredClient = config.client ?? config.dialect; if (!configuredClient) { throw new Error('Missing required field "client".'); } return configuredClient; }; const normalizeClient = (client) => { switch (client) { // Prefer better-sqlite3: the legacy sqlite3 package needs native builds. case 'sqlite3': case 'sqlite': return 'better-sqlite3'; case 'mysql': return 'mysql2'; case 'postgres': case 'postgresql': case 'pg': return 'pg'; case 'oracle': return 'oracledb'; default: return client; } }; const requireField = (value, field, client) => { if (value === undefined || value === null || value === '') { throw new Error(`Missing required field "${field}" for client "${client}".`); } }; const buildConnection = (config) => { if (config.connection !== undefined) { return config.connection; } if (config.connectionString) { return config.connectionString; } const configuredClient = getConfiguredClient(config); const client = normalizeClient(configuredClient); if (client === 'sqlite3' || client === 'better-sqlite3') { requireField(config.filename, 'filename', configuredClient); return { filename: config.filename }; } if (client === 'mssql') { const server = config.host; requireField(server, 'host', configuredClient); requireField(config.user, 'user', configuredClient); requireField(config.database, 'database', configuredClient); const connection = { server: server, user: config.user, database: config.database, }; if (typeof config.port === 'number') { connection.port = config.port; } if (config.password !== undefined) { connection.password = config.password; } if (config.ssl) { connection.options = { encrypt: true }; } return connection; } requireField(config.host, 'host', configuredClient); requireField(config.user, 'user', configuredClient); requireField(config.database, 'database', configuredClient); const connection = { host: config.host, user: config.user, database: config.database, }; if (typeof config.port === 'number') { connection.port = config.port; } if (config.password !== undefined) { connection.password = config.password; } if (config.ssl !== undefined) { connection.ssl = config.ssl; } return connection; }; /** * Builds a Knex config from simple database options. * * Prefer `client` over the deprecated `dialect` field. Connection details can be * supplied as a `connection` object/string or as top-level `host`/`filename` fields. * * @param config - Simplified database configuration. * @returns Knex config ready for {@link configure}. */ const createKnexConfig = (config) => { const client = normalizeClient(getConfiguredClient(config)); const base = { client, connection: buildConnection(config), }; if (config.pool) { base.pool = config.pool; } if (typeof config.debug === 'boolean') { base.debug = config.debug; } if (client === 'better-sqlite3') { base.useNullAsDefault = true; } return base; }; /** * Initialises the ORM from simple database options. * * This is the recommended entry point for most applications. * * @param config - Simplified database configuration with `client` and connection fields. * @returns The active Knex client (also available as {@link DB}). * * @example * ```ts * configureDatabase({ * client: 'better-sqlite3', * connection: { filename: './dev.sqlite' } * }) * ``` * * SQLite note: `client: 'sqlite3'` and `client: 'sqlite'` are accepted for * compatibility but resolve to the Knex `better-sqlite3` driver. Prefer * installing and configuring `better-sqlite3`; the older `sqlite3` package is * discouraged because it requires native builds. */ const configureDatabase = (config) => configure(createKnexConfig(config)); /** * Generates a Knex migration file and returns its absolute path. * * @param name - Migration name (e.g. `create_users_table`). * @param config - Optional per-call migrator overrides. * @returns Path to the created migration file. */ const makeMigration = async (name, config) => { try { return await getDB().migrate.make(name, resolveMigrationConfig(config)); } catch (error) { throw toError(error); } }; /** * Runs all pending migrations. * * @param config - Optional per-call migrator overrides. * @returns Batch number and filenames of migrations executed in this run. */ const migrateLatest = async (config) => { try { const [batch, log] = await getDB().migrate.latest(resolveMigrationConfig(config)); return { batch, log }; } catch (error) { throw toError(error); } }; /** * Rolls back the most recent migration batch. * * @param config - Optional per-call migrator overrides. * @param all - When `true`, rolls back all completed batches. * @returns Batch number and filenames of migrations rolled back. */ const migrateRollback = async (config, all = false) => { try { const [batch, log] = all ? await getDB().migrate.rollback(resolveMigrationConfig(config), true) : await getDB().migrate.rollback(resolveMigrationConfig(config)); return { batch, log }; } catch (error) { throw toError(error); } }; /** * Returns the current migration version recorded by Knex. * * @param config - Optional per-call migrator overrides. * @returns Latest applied migration name, or `'none'` when no migrations have run. */ const migrateCurrentVersion = async (config) => { try { return await getDB().migrate.currentVersion(resolveMigrationConfig(config)); } catch (error) { throw toError(error); } }; /** * Lists completed and pending migration filenames. * * @param config - Optional per-call migrator overrides. * @returns Object with `completed` and `pending` migration filename arrays. */ const migrateList = async (config) => { try { const [completed, pending] = await getDB().migrate.list(resolveMigrationConfig(config)); const toName = (entry) => { if (typeof entry === 'string') { return entry; } if (entry && typeof entry === 'object') { if ('name' in entry) { return String(entry.name); } if ('file' in entry) { return String(entry.file); } } return String(entry); }; return { completed: completed.map(toName), pending: pending.map(toName), }; } catch (error) { throw toError(error); } }; export { DB, getDB, configure, createKnexConfig, configureDatabase, setMigrationConfig, getMigrationConfig, makeMigration, migrateLatest, migrateRollback, migrateCurrentVersion, migrateList, };