|
| 1 | +import { Arkorm, DB, Model, defineConfig } from 'arkormx' |
| 2 | + |
| 3 | +import type { ArkormConfig } from 'arkormx' |
| 4 | +import { Arkstack } from '@arkstack/contract' |
| 5 | +import type { DatabaseConfig } from './types' |
| 6 | +import { createAdapter } from './kysely' |
| 7 | +import { createArkormCurrentPageResolver } from 'resora' |
| 8 | +import { existsSync } from 'node:fs' |
| 9 | +import { outputDir } from '@arkstack/common' |
| 10 | +import path from 'node:path' |
| 11 | +import { resolveConnection } from './config' |
| 12 | + |
| 13 | +/** |
| 14 | + * Default ArkORM paths matching the scaffolded application structure. Apps with |
| 15 | + * a different layout can override them by adding an `arkormx.config.ts`. |
| 16 | + */ |
| 17 | +const defaultPaths = (): NonNullable<ArkormConfig['paths']> => { |
| 18 | + const dist = path.relative(Arkstack.rootDir(), outputDir()) |
| 19 | + |
| 20 | + return { |
| 21 | + models: './src/app/models', |
| 22 | + factories: './src/database/factories', |
| 23 | + seeders: './src/database/seeders', |
| 24 | + migrations: './src/database/migrations', |
| 25 | + buildOutput: dist, |
| 26 | + } |
| 27 | +} |
| 28 | + |
| 29 | +/** |
| 30 | + * Whether the application provides its own `arkormx.config.{ts,js}`. When it |
| 31 | + * does, ArkORM loads it and it takes precedence over the framework defaults. |
| 32 | + */ |
| 33 | +const hasUserArkormConfig = (): boolean => { |
| 34 | + const root = Arkstack.rootDir() |
| 35 | + |
| 36 | + return existsSync(path.join(root, 'arkormx.config.ts')) |
| 37 | + || existsSync(path.join(root, 'arkormx.config.js')) |
| 38 | +} |
| 39 | + |
| 40 | +export interface DefineArkormConfigOptions extends Partial<ArkormConfig> { |
| 41 | + /** |
| 42 | + * The application's database configuration. When provided (and no explicit |
| 43 | + * `adapter` is set), the adapter is built from the resolved connection. |
| 44 | + */ |
| 45 | + database?: DatabaseConfig |
| 46 | + /** |
| 47 | + * The connection name to bind. Defaults to `database.default`. |
| 48 | + */ |
| 49 | + connection?: string |
| 50 | +} |
| 51 | + |
| 52 | +/** |
| 53 | + * Build the ArkORM config object for an application from its database |
| 54 | + * configuration. Use this only when authoring an explicit `arkormx.config.ts`; |
| 55 | + * by default the framework configures ArkORM for you (see {@link bootArkorm}). |
| 56 | + * |
| 57 | + * @param options Arkorm config plus the app's database config. |
| 58 | + */ |
| 59 | +export const defineArkormConfig = (options: DefineArkormConfigOptions = {}) => { |
| 60 | + const { database, connection, adapter, pagination, ...rest } = options |
| 61 | + |
| 62 | + let resolvedAdapter = adapter |
| 63 | + |
| 64 | + if (!resolvedAdapter && database) { |
| 65 | + const name = connection ?? database.default |
| 66 | + const resolved = database.connections[name] |
| 67 | + |
| 68 | + if (!resolved) { |
| 69 | + throw new Error(`Database connection "${name}" is not configured.`) |
| 70 | + } |
| 71 | + |
| 72 | + resolvedAdapter = createAdapter(resolved) |
| 73 | + } |
| 74 | + |
| 75 | + return defineConfig({ |
| 76 | + ...rest, |
| 77 | + ...(resolvedAdapter ? { adapter: resolvedAdapter } : {}), |
| 78 | + pagination: { |
| 79 | + resolveCurrentPage: createArkormCurrentPageResolver(), |
| 80 | + ...(pagination ?? {}), |
| 81 | + }, |
| 82 | + }) |
| 83 | +} |
| 84 | + |
| 85 | +export interface BootArkormOptions extends Partial<ArkormConfig> { |
| 86 | + /** The connection name to bind. Defaults to `database.default`. */ |
| 87 | + connection?: string |
| 88 | +} |
| 89 | + |
| 90 | +/** |
| 91 | + * Configure ArkORM natively from `src/config/database.ts`, so applications work |
| 92 | + * without an `arkormx.config.ts`. |
| 93 | + * |
| 94 | + * Builds the adapter from the default (or named) connection, registers the |
| 95 | + * conventional paths and the resora pagination resolver, and binds models. A |
| 96 | + * user-provided `arkormx.config.{ts,js}` always wins — in that case this is a |
| 97 | + * no-op and ArkORM loads the file instead. |
| 98 | + * |
| 99 | + * @param options Optional overrides merged over the derived config. |
| 100 | + * @returns Whether the framework applied its configuration. |
| 101 | + */ |
| 102 | +export const bootArkorm = (options: BootArkormOptions = {}): boolean => { |
| 103 | + if (hasUserArkormConfig()) { |
| 104 | + return false |
| 105 | + } |
| 106 | + |
| 107 | + const { connection, adapter, paths, pagination, ...rest } = options |
| 108 | + const resolvedAdapter = adapter ?? createAdapter(resolveConnection(connection)) |
| 109 | + |
| 110 | + Arkorm.configure({ |
| 111 | + adapter: resolvedAdapter, |
| 112 | + paths: { ...defaultPaths(), ...(paths ?? {}) }, |
| 113 | + pagination: { |
| 114 | + resolveCurrentPage: createArkormCurrentPageResolver(), |
| 115 | + ...(pagination ?? {}), |
| 116 | + }, |
| 117 | + outputExt: 'ts', |
| 118 | + ...rest, |
| 119 | + }) |
| 120 | + |
| 121 | + Model.setAdapter(resolvedAdapter) |
| 122 | + DB.setAdapter(resolvedAdapter) |
| 123 | + |
| 124 | + return true |
| 125 | +} |
0 commit comments