2023-07-19 16:35:50 +02:00
import type { Knex } from 'knex' ;
2024-11-20 12:18:25 +01:00
import path from 'node:path' ;
2023-07-19 16:35:50 +02:00
import { Dialect , getDialect } from './dialects' ;
2023-09-20 15:52:30 +02:00
import { createSchemaProvider , SchemaProvider } from './schema' ;
2023-09-12 16:26:42 +02:00
import { createMetadata , Metadata } from './metadata' ;
2023-09-20 15:52:30 +02:00
import { createEntityManager , EntityManager } from './entity-manager' ;
2024-12-16 12:56:05 +01:00
import { createMigrationsProvider , MigrationProvider , type Migration } from './migrations' ;
2023-09-20 15:52:30 +02:00
import { createLifecyclesProvider , LifecycleProvider } from './lifecycles' ;
2023-07-19 16:35:50 +02:00
import { createConnection } from './connection' ;
import * as errors from './errors' ;
2023-09-21 16:49:53 +02:00
import { Callback , transactionCtx , TransactionObject } from './transaction-context' ;
2023-07-19 16:35:50 +02:00
import { validateDatabase } from './validations' ;
2024-01-25 10:49:04 +01:00
import type { Model } from './types' ;
2024-12-16 12:56:05 +01:00
import type { Identifiers } from './utils/identifiers' ;
import { createRepairManager , type RepairManager } from './repairs' ;
2023-07-19 16:35:50 +02:00
2023-10-09 10:54:13 +02:00
export { isKnexQuery } from './utils/knex' ;
2023-07-19 16:35:50 +02:00
interface Settings {
forceMigration? : boolean ;
runMigrations? : boolean ;
2024-03-13 15:40:30 +01:00
migrations : {
dir : string ;
} ;
2023-07-19 16:35:50 +02:00
[ key : string ] : unknown ;
}
2024-06-17 10:43:34 +02:00
export type Logger = Record <
'info' | 'warn' | 'error' | 'debug' ,
( message : string | Record < string , unknown > ) = > void
> ;
2023-07-19 16:35:50 +02:00
export interface DatabaseConfig {
connection : Knex.Config ;
2023-09-12 21:47:42 +02:00
settings : Settings ;
2024-06-17 10:43:34 +02:00
logger? : Logger ;
2023-07-19 16:35:50 +02:00
}
2021-05-10 15:36:09 +02:00
2024-03-13 15:40:30 +01:00
const afterCreate =
( db : Database ) = >
(
nativeConnection : unknown ,
done : ( error : Error | null , nativeConnection : unknown ) = > Promise < void >
) = > {
// run initialize for it since commands such as postgres SET and sqlite PRAGMA are per-connection
db . dialect . initialize ( nativeConnection ) . then ( ( ) = > {
return done ( null , nativeConnection ) ;
} ) ;
} ;
2021-05-10 15:36:09 +02:00
class Database {
2023-07-19 16:35:50 +02:00
connection : Knex ;
dialect : Dialect ;
config : DatabaseConfig ;
2023-09-12 16:26:42 +02:00
metadata : Metadata ;
2023-07-19 16:35:50 +02:00
2023-09-20 15:52:30 +02:00
schema : SchemaProvider ;
2023-07-19 16:35:50 +02:00
2023-09-20 15:52:30 +02:00
migrations : MigrationProvider ;
2023-07-19 16:35:50 +02:00
2023-09-20 15:52:30 +02:00
lifecycles : LifecycleProvider ;
2023-07-19 16:35:50 +02:00
2023-09-20 15:52:30 +02:00
entityManager : EntityManager ;
2023-07-19 16:35:50 +02:00
2024-12-16 12:56:05 +01:00
repair : RepairManager ;
2024-06-17 10:43:34 +02:00
logger : Logger ;
2023-07-19 16:35:50 +02:00
constructor ( config : DatabaseConfig ) {
2021-11-10 19:42:03 +01:00
this . config = {
2023-09-12 21:47:42 +02:00
. . . config ,
2021-11-10 19:42:03 +01:00
settings : {
2021-11-15 09:41:00 +01:00
forceMigration : true ,
2022-12-13 19:30:55 +00:00
runMigrations : true ,
2023-09-12 21:47:42 +02:00
. . . ( config . settings ? ? { } ) ,
2021-11-10 19:42:03 +01:00
} ,
} ;
2021-09-13 12:03:12 +02:00
2024-10-24 17:05:47 +02:00
this . logger = config . logger ? ? console ;
2021-06-28 12:34:29 +02:00
this . dialect = getDialect ( this ) ;
2024-10-24 17:05:47 +02:00
let knexConfig : Knex.Config = this . config . connection ;
// for object connections, we can configure the dialect synchronously
if ( typeof this . config . connection . connection !== 'function' ) {
this . dialect . configure ( ) ;
}
// for connection functions, we wrap it so that we can modify it with dialect configure before it reaches knex
else {
this . logger . warn (
'Knex connection functions are currently experimental. Attempting to access the connection object before database initialization will result in errors.'
) ;
knexConfig = {
. . . this . config . connection ,
connection : async ( ) = > {
// @ts-expect-error confirmed it was a function above
const conn = await this . config . connection . connection ( ) ;
this . dialect . configure ( conn ) ;
return conn ;
} ,
} ;
}
2021-09-13 12:03:12 +02:00
2024-03-25 18:02:30 +01:00
this . metadata = createMetadata ( [ ] ) ;
2021-09-13 12:03:12 +02:00
2024-10-24 17:05:47 +02:00
this . connection = createConnection ( knexConfig , {
2024-03-13 15:40:30 +01:00
pool : { afterCreate : afterCreate ( this ) } ,
} ) ;
2021-05-18 10:16:03 +02:00
2021-08-04 17:47:38 +02:00
this . schema = createSchemaProvider ( this ) ;
2021-05-10 15:36:09 +02:00
2021-09-20 19:15:50 +02:00
this . migrations = createMigrationsProvider ( this ) ;
this . lifecycles = createLifecyclesProvider ( this ) ;
2021-06-17 16:17:15 +02:00
this . entityManager = createEntityManager ( this ) ;
2024-12-16 12:56:05 +01:00
this . repair = createRepairManager ( this ) ;
2021-05-10 15:36:09 +02:00
}
2021-05-17 16:34:19 +02:00
2024-03-13 15:40:30 +01:00
async init ( { models } : { models : Model [ ] } ) {
2024-10-24 17:05:47 +02:00
if ( typeof this . config . connection . connection === 'function' ) {
/ *
* User code needs to be able to access ` connection.connection ` directly as if
* it were always an object . For a connection function , that doesn ' t happen
* until the pool is created , so we need to do that here
*
* TODO : In the next major version , we need to replace all internal code that
* directly references ` connection.connection ` prior to init , and make a breaking
* change that it cannot be relied on to exist before init so that we can call
* this feature stable .
* /
this . logger . debug ( 'Forcing Knex to make real connection to db' ) ;
// sqlite does not support connection pooling so acquireConnection doesn't work
if ( this . config . connection . client === 'sqlite' ) {
await this . connection . raw ( 'SELECT 1' ) ;
} else {
await this . connection . client . acquireConnection ( ) ;
}
}
2024-03-13 15:40:30 +01:00
this . metadata . loadModels ( models ) ;
await validateDatabase ( this ) ;
return this ;
}
2023-07-19 16:35:50 +02:00
query ( uid : string ) {
2021-06-22 17:13:11 +02:00
if ( ! this . metadata . has ( uid ) ) {
throw new Error ( ` Model ${ uid } not found ` ) ;
}
2021-06-17 16:17:15 +02:00
return this . entityManager . getRepository ( uid ) ;
}
2023-04-12 18:42:43 +02:00
inTransaction() {
return ! ! transactionCtx . get ( ) ;
}
2023-09-21 16:49:53 +02:00
transaction ( ) : Promise < TransactionObject > ;
transaction < TCallback extends Callback > ( c : TCallback ) : Promise < ReturnType < TCallback > > ;
async transaction < TCallback extends Callback > (
cb? : TCallback
) : Promise < ReturnType < TCallback > | TransactionObject > {
2023-01-13 12:23:30 +02:00
const notNestedTransaction = ! transactionCtx . get ( ) ;
2023-09-12 13:26:59 +02:00
const trx = notNestedTransaction
? await this . connection . transaction ( )
: ( transactionCtx . get ( ) as Knex . Transaction ) ;
2023-01-23 18:43:14 +02:00
async function commit() {
if ( notNestedTransaction ) {
2023-05-18 12:57:47 +02:00
await transactionCtx . commit ( trx ) ;
2023-01-23 18:43:14 +02:00
}
}
async function rollback() {
if ( notNestedTransaction ) {
2023-05-18 12:57:47 +02:00
await transactionCtx . rollback ( trx ) ;
2023-01-23 18:43:14 +02:00
}
}
2023-04-11 16:10:24 +02:00
2022-09-12 15:24:53 +02:00
if ( ! cb ) {
2023-05-18 12:57:47 +02:00
return { commit , rollback , get : ( ) = > trx } ;
2022-09-12 15:24:53 +02:00
}
2023-01-24 14:39:24 +02:00
return transactionCtx . run ( trx , async ( ) = > {
2022-09-12 15:24:53 +02:00
try {
2023-05-18 12:57:47 +02:00
const callbackParams = {
trx ,
commit ,
rollback ,
onCommit : transactionCtx.onCommit ,
onRollback : transactionCtx.onRollback ,
} ;
2023-01-24 14:39:24 +02:00
const res = await cb ( callbackParams ) ;
2023-01-23 18:43:14 +02:00
await commit ( ) ;
2022-09-12 15:24:53 +02:00
return res ;
} catch ( error ) {
2023-01-23 18:43:14 +02:00
await rollback ( ) ;
2022-09-12 15:24:53 +02:00
throw error ;
}
} ) ;
}
2023-09-12 21:47:42 +02:00
getSchemaName ( ) : string | undefined {
2023-07-19 16:35:50 +02:00
return this . connection . client . connectionSettings . schema ;
}
getConnection ( ) : Knex ;
2023-09-12 21:47:42 +02:00
getConnection ( tableName? : string ) : Knex . QueryBuilder ;
2023-07-19 16:35:50 +02:00
getConnection ( tableName? : string ) : Knex | Knex . QueryBuilder {
const schema = this . getSchemaName ( ) ;
2021-11-10 19:42:03 +01:00
const connection = tableName ? this . connection ( tableName ) : this . connection ;
return schema ? connection . withSchema ( schema ) : connection ;
}
2024-11-20 12:18:25 +01:00
// Returns basic info about the database connection
getInfo() {
const connectionSettings = this . connection ? . client ? . connectionSettings || { } ;
const client = this . dialect ? . client || '' ;
let displayName = '' ;
let schema ;
// For SQLite, get the relative filename
if ( client === 'sqlite' ) {
const absolutePath = connectionSettings ? . filename ;
if ( absolutePath ) {
displayName = path . relative ( process . cwd ( ) , absolutePath ) ;
}
}
// For other dialects, get the database name
else {
displayName = connectionSettings ? . database ;
schema = connectionSettings ? . schema ;
}
return {
displayName ,
schema ,
client ,
} ;
}
2021-11-10 19:42:03 +01:00
getSchemaConnection ( trx = this . connection ) {
2023-07-19 16:35:50 +02:00
const schema = this . getSchemaName ( ) ;
2021-11-10 19:42:03 +01:00
return schema ? trx . schema . withSchema ( schema ) : trx . schema ;
}
2023-07-19 16:35:50 +02:00
queryBuilder ( uid : string ) {
2021-09-13 12:03:12 +02:00
return this . entityManager . createQueryBuilder ( uid ) ;
}
2021-06-17 16:17:15 +02:00
async destroy() {
2021-08-04 17:47:38 +02:00
await this . lifecycles . clear ( ) ;
2021-06-17 16:17:15 +02:00
await this . connection . destroy ( ) ;
2021-05-17 16:34:19 +02:00
}
2021-05-10 15:36:09 +02:00
}
2019-09-20 12:44:24 +02:00
2024-03-25 18:02:30 +01:00
export { Database , errors } ;
2024-04-17 13:41:44 +02:00
export type { Model , Identifiers , Migration } ;