import { App, type CreateAppConfig } from "bknd"; import { config as $config, $console } from "bknd/core"; import type { MiddlewareHandler } from "hono"; import type { AdminControllerOptions } from "modules/server/AdminController"; import { Connection } from "bknd/data"; export { Connection } from "bknd/data"; export type BkndConfig = CreateAppConfig & { app?: CreateAppConfig | ((args: Args) => CreateAppConfig); onBuilt?: (app: App) => Promise; beforeBuild?: (app: App) => Promise; buildConfig?: Parameters[0]; }; export type FrameworkBkndConfig = BkndConfig; export type CreateAdapterAppOptions = { force?: boolean; id?: string; }; export type FrameworkOptions = CreateAdapterAppOptions; export type RuntimeOptions = CreateAdapterAppOptions; export type RuntimeBkndConfig = BkndConfig & { distPath?: string; serveStatic?: MiddlewareHandler | [string, MiddlewareHandler]; adminOptions?: AdminControllerOptions | false; }; export type DefaultArgs = { [key: string]: any; }; export function makeConfig( config: BkndConfig, args?: Args, ): CreateAppConfig { let additionalConfig: CreateAppConfig = {}; const { app, ...rest } = config; if (app) { if (typeof app === "function") { if (!args) { throw new Error("args is required when config.app is a function"); } additionalConfig = app(args); } else { additionalConfig = app; } } return { ...rest, ...additionalConfig }; } // a map that contains all apps by id const apps = new Map(); export async function createAdapterApp( config: Config = {} as Config, args?: Args, opts?: CreateAdapterAppOptions, ): Promise { const id = opts?.id ?? "app"; let app = apps.get(id); if (!app || opts?.force) { const appConfig = makeConfig(config, args); if (!appConfig.connection || !Connection.isConnection(appConfig.connection)) { let connection: Connection | undefined; if (Connection.isConnection(config.connection)) { connection = config.connection; } else { const sqlite = (await import("bknd/adapter/sqlite")).sqlite; const conf = config.connection ?? { url: ":memory:" }; connection = sqlite(conf); $console.info(`Using ${connection.name} connection`, conf.url); } appConfig.connection = connection; } app = App.create(appConfig); apps.set(id, app); } return app; } export async function createFrameworkApp( config: FrameworkBkndConfig = {}, args?: Args, opts?: FrameworkOptions, ): Promise { const app = await createAdapterApp(config, args, opts); if (!app.isBuilt()) { if (config.onBuilt) { app.emgr.onEvent( App.Events.AppBuiltEvent, async () => { await config.onBuilt?.(app); }, "sync", ); } await config.beforeBuild?.(app); await app.build(config.buildConfig); } return app; } export async function createRuntimeApp( { serveStatic, adminOptions, ...config }: RuntimeBkndConfig = {}, args?: Args, opts?: RuntimeOptions, ): Promise { const app = await createAdapterApp(config, args, opts); if (!app.isBuilt()) { app.emgr.onEvent( App.Events.AppBuiltEvent, async () => { if (serveStatic) { const [path, handler] = Array.isArray(serveStatic) ? serveStatic : [$config.server.assets_path + "*", serveStatic]; app.modules.server.get(path, handler); } await config.onBuilt?.(app); if (adminOptions !== false) { app.registerAdminController(adminOptions); } }, "sync", ); await config.beforeBuild?.(app); await app.build(config.buildConfig); } return app; }