11import { DBAdapter , SQLOpenFactory , SQLOpenOptions } from '@powersync/common' ;
22import { OPSQLiteDBAdapter } from './OPSqliteAdapter' ;
33import { DEFAULT_SQLITE_OPTIONS , SqliteOptions } from './SqliteOptions' ;
4+ import { OPSQLiteConnection } from './OPSQLiteConnection' ;
5+ import { DB } from '@op-engineering/op-sqlite' ;
46
57export interface OPSQLiteOpenFactoryOptions extends SQLOpenOptions {
68 sqliteOptions ?: SqliteOptions ;
79}
810export class OPSqliteOpenFactory implements SQLOpenFactory {
911 private sqliteOptions : Required < SqliteOptions > ;
12+ private adapters : Set < OPSQLiteDBAdapter > = new Set ( ) ;
1013
1114 constructor ( protected options : OPSQLiteOpenFactoryOptions ) {
1215 this . sqliteOptions = {
@@ -16,10 +19,85 @@ export class OPSqliteOpenFactory implements SQLOpenFactory {
1619 }
1720
1821 openDB ( ) : DBAdapter {
19- return new OPSQLiteDBAdapter ( {
22+ const adapter = new OPSQLiteDBAdapter ( {
2023 name : this . options . dbFilename ,
2124 dbLocation : this . options . dbLocation ,
2225 sqliteOptions : this . sqliteOptions
2326 } ) ;
27+ this . adapters . add ( adapter ) ;
28+ ( adapter as any ) . abortController . signal . addEventListener ( 'abort' , ( ) => {
29+ this . adapters . delete ( adapter ) ;
30+ } ) ;
31+
32+ return adapter ;
33+ }
34+
35+ /**
36+ * Opens a direct op-sqlite DB connection. This can be used concurrently with PowerSyncDatabase.
37+ *
38+ * This can be used to execute synchronous queries, or to access other op-sqlite functionality directly.
39+ *
40+ * Update notifications are propagated to any other PowerSyncDatabase opened with this factory.
41+ *
42+ * If a write statement or transaction is currently open on any of the other adapters, any
43+ * write statements on this connection will block until the others are done. This may create a deadlock,
44+ * since this also blocks the JavaScript thread. For that reason, do any write statements in a
45+ * writeLock() on the PowerSyncDatabase.
46+ *
47+ * Read statements can execute concurrently with write statements, so does not have the same risk.
48+ *
49+ * This is not recommended for most use cases, as synchronous queries block the JavaScript thread,
50+ * and the code is not portable to other platforms.
51+ */
52+ async openDirectConnection ( ) : Promise < DB > {
53+ const adapter = new OPSQLiteDBAdapter ( {
54+ name : this . options . dbFilename ,
55+ dbLocation : this . options . dbLocation ,
56+ sqliteOptions : {
57+ ...this . sqliteOptions ,
58+ readConnections : 0 ,
59+ // Configure the BUSY_TIMEOUT to be very short, since this is a direct connection.
60+ // In general, we should not wait for a lock when using any synchronous queries,
61+ // since any locks won't be released while we lock the JS thread.
62+ lockTimeoutMs : 50
63+ }
64+ } ) ;
65+ await ( adapter as any ) . initialized ;
66+
67+ const connection = ( adapter as any ) . writeConnection as OPSQLiteConnection ;
68+ connection . registerListener ( {
69+ tablesUpdated : ( updateNotification ) => {
70+ // Pass on to all other adapters.
71+ this . adapters . forEach ( ( adapter ) => {
72+ adapter . iterateListeners ( ( listener ) => {
73+ listener . tablesUpdated ?.( updateNotification ) ;
74+ } ) ;
75+ } ) ;
76+ }
77+ } ) ;
78+ const database = ( connection as any ) . DB as DB ;
79+
80+ database . commitHook ( ( ) => {
81+ // This is effectively a "pre-commit" hook, so changes may not actually reflect yet.
82+ // To make sure the changes reflect, we first get start a new write transaction (not just a
83+ // write lock, since we need to get a lock on the actual SQLite file).
84+ const firstAdapter = [ ...this . adapters ] [ 0 ] ;
85+ if ( firstAdapter != null && connection . hasUpdates ( ) ) {
86+ firstAdapter
87+ . writeLock ( async ( tx ) => {
88+ // Slightly less overhead than writeTransaction().
89+ await tx . execute ( 'BEGIN EXCLUSIVE; ROLLBACK;' ) ;
90+ } )
91+ . catch ( ( e ) => {
92+ // Ignore
93+ } )
94+ . finally ( ( ) => {
95+ // This triggers the listeners registered above
96+ connection . flushUpdates ( ) ;
97+ } ) ;
98+ }
99+ } ) ;
100+
101+ return database ;
24102 }
25103}
0 commit comments