| 33 | } |
| 34 | |
| 35 | export const createSqliteFumaDb = async <const TTables extends FumaTables>( |
| 36 | options: CreateSqliteFumaDbOptions<TTables>, |
| 37 | ): Promise<SqliteFumaDb<TTables>> => { |
| 38 | const version = options.version ?? "1.0.0"; |
| 39 | // libSQL opens a connection (not a shared in-process handle), so the |
| 40 | // foreign_keys + WAL PRAGMAs are applied on this connection inside |
| 41 | // openLocalLibsql. |
| 42 | const client = await openLocalLibsql(options.path); |
| 43 | |
| 44 | const schema = createDrizzleRuntimeSchemaFromTables({ |
| 45 | tables: options.tables, |
| 46 | namespace: options.namespace, |
| 47 | version, |
| 48 | provider: "sqlite", |
| 49 | }); |
| 50 | const drizzleDb = drizzle({ client, schema }); |
| 51 | |
| 52 | // CREATE TABLE IF NOT EXISTS for fresh files, plus ALTER TABLE ADD COLUMN for |
| 53 | // every nullable column the running schema declares — so files created by an |
| 54 | // earlier baseline gain new columns on boot instead of 500ing on first query. |
| 55 | // This is the same bring-up the self-host and Cloudflare hosts run; keeping |
| 56 | // local on it stops per-column drift between hosts. Idempotent. |
| 57 | await ensureDrizzleRuntimeSchemaFromTables(drizzleDb, { |
| 58 | tables: options.tables, |
| 59 | namespace: options.namespace, |
| 60 | version, |
| 61 | provider: "sqlite", |
| 62 | }); |
| 63 | |
| 64 | // Defensive column adds for libSQL files created by earlier v2 baselines. |
| 65 | // The generic bring-up above already evolves every nullable column the live |
| 66 | // schema declares, so these are mostly redundant now; they are kept as an |
| 67 | // explicit safety net and to cover any column the schema no longer declares. |
| 68 | // Idempotent. |
| 69 | const connectionColumns = await client.execute("PRAGMA table_info('connection')"); |
| 70 | if ( |
| 71 | connectionColumns.rows.length > 0 && |
| 72 | !connectionColumns.rows.some((column) => column["name"] === "oauth_client_owner") |
| 73 | ) { |
| 74 | await client.execute("ALTER TABLE connection ADD COLUMN oauth_client_owner TEXT"); |
| 75 | } |
| 76 | if ( |
| 77 | connectionColumns.rows.length > 0 && |
| 78 | !connectionColumns.rows.some((column) => column["name"] === "identity_override") |
| 79 | ) { |
| 80 | await client.execute("ALTER TABLE connection ADD COLUMN identity_override TEXT"); |
| 81 | } |
| 82 | |
| 83 | const oauthClientColumns = await client.execute("PRAGMA table_info('oauth_client')"); |
| 84 | if ( |
| 85 | oauthClientColumns.rows.length > 0 && |
| 86 | !oauthClientColumns.rows.some((column) => column["name"] === "origin_kind") |
| 87 | ) { |
| 88 | await client.execute("ALTER TABLE oauth_client ADD COLUMN origin_kind TEXT"); |
| 89 | } |
| 90 | if ( |
| 91 | oauthClientColumns.rows.length > 0 && |
| 92 | !oauthClientColumns.rows.some((column) => column["name"] === "origin_integration") |