| 32 | } |
| 33 | |
| 34 | export const openOwnedLocalDatabase = async <const TTables extends FumaTables>( |
| 35 | input: OpenOwnedLocalDatabaseOptions<TTables>, |
| 36 | ): Promise<OwnedLocalDatabase<TTables>> => { |
| 37 | const ownership = await acquireDataDirOwnership(input.dataDir); |
| 38 | let db: SqliteFumaDb<TTables> | null = null; |
| 39 | |
| 40 | try { |
| 41 | // Use the real directory captured by the ownership primitive so a symlinked |
| 42 | // input path cannot be repointed between lock acquisition and data.db open. |
| 43 | const dataDir = dirname(ownership.lockPath); |
| 44 | const sqlitePath = join(dataDir, "data.db"); |
| 45 | |
| 46 | const migration = await migrateLocalV1ToV2IfNeeded({ |
| 47 | sqlitePath, |
| 48 | tables: input.tables, |
| 49 | namespace: input.namespace, |
| 50 | tenantId: input.tenantId, |
| 51 | ...(input.oauthMetadataFetch !== undefined |
| 52 | ? { oauthMetadataFetch: input.oauthMetadataFetch } |
| 53 | : {}), |
| 54 | ...(input.oauthMetadataTimeoutMs !== undefined |
| 55 | ? { oauthMetadataTimeoutMs: input.oauthMetadataTimeoutMs } |
| 56 | : {}), |
| 57 | }); |
| 58 | |
| 59 | const openedDb = await createSqliteFumaDb({ |
| 60 | tables: input.tables, |
| 61 | namespace: input.namespace, |
| 62 | path: sqlitePath, |
| 63 | ...(input.version !== undefined ? { version: input.version } : {}), |
| 64 | }); |
| 65 | db = openedDb; |
| 66 | |
| 67 | let closed = false; |
| 68 | return { |
| 69 | dataDir, |
| 70 | sqlitePath, |
| 71 | lockPath: ownership.lockPath, |
| 72 | db: openedDb, |
| 73 | migration, |
| 74 | close: async () => { |
| 75 | if (closed) return; |
| 76 | closed = true; |
| 77 | try { |
| 78 | await openedDb.close(); |
| 79 | } finally { |
| 80 | await ownership.release(); |
| 81 | } |
| 82 | }, |
| 83 | }; |
| 84 | } catch (cause) { |
| 85 | try { |
| 86 | if (db) await db.close(); |
| 87 | } finally { |
| 88 | await ownership.release(); |
| 89 | } |
| 90 | throw cause; |
| 91 | } |