@@ -46,6 +46,13 @@ export const DEFAULT_POWERSYNC_DB_OPTIONS = {
4646 logger : Logger . get ( 'PowerSyncDatabase' )
4747} ;
4848
49+ /**
50+ * Requesting nested or recursive locks can block the application in some circumstances.
51+ * This default lock timeout will act as a failsafe to throw an error if a lock cannot
52+ * be obtained.
53+ */
54+ export const DEFAULT_LOCK_TIMEOUT_MS = 120_000 ; // 2 mins
55+
4956export abstract class AbstractPowerSyncDatabase extends BaseObserver < PowerSyncDBListener > {
5057 /**
5158 * Transactions should be queued in the DBAdapter, but we also want to prevent
@@ -70,9 +77,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
7077 this . closed = true ;
7178 this . options = { ...DEFAULT_POWERSYNC_DB_OPTIONS , ...options } ;
7279 this . bucketStorageAdapter = this . generateBucketStorageAdapter ( ) ;
73- this . sdkVersion = this . options . database . execute ( 'SELECT powersync_rs_version()' ) . rows ?. item ( 0 ) [
74- 'powersync_rs_version()'
75- ] ;
80+ this . sdkVersion = '' ;
7681 }
7782
7883 get schema ( ) {
@@ -98,7 +103,9 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
98103 this . initialized = ( async ( ) => {
99104 await this . _init ( ) ;
100105 await this . bucketStorageAdapter . init ( ) ;
101- await this . database . executeAsync ( 'SELECT powersync_replace_schema(?)' , [ JSON . stringify ( this . schema . toJSON ( ) ) ] ) ;
106+ await this . database . execute ( 'SELECT powersync_replace_schema(?)' , [ JSON . stringify ( this . schema . toJSON ( ) ) ] ) ;
107+ const version = await this . options . database . execute ( 'SELECT powersync_rs_version()' ) ;
108+ this . sdkVersion = version . rows ?. item ( 0 ) [ 'powersync_rs_version()' ] ?? '' ;
102109 } ) ( ) ;
103110 await this . initialized ;
104111 }
@@ -111,7 +118,6 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
111118 await this . disconnect ( ) ;
112119
113120 await this . initialized ;
114-
115121 this . syncStreamImplementation = this . generateSyncStreamImplementation ( connector ) ;
116122 this . syncStatusListenerDisposer = this . syncStreamImplementation . registerListener ( {
117123 statusChanged : ( status ) => {
@@ -142,20 +148,20 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
142148 await this . disconnect ( ) ;
143149
144150 // TODO DB name, verify this is necessary with extension
145- await this . database . transaction ( async ( tx ) => {
146- await tx . executeAsync ( 'DELETE FROM ps_oplog WHERE 1' ) ;
147- await tx . executeAsync ( 'DELETE FROM ps_crud WHERE 1' ) ;
148- await tx . executeAsync ( 'DELETE FROM ps_buckets WHERE 1' ) ;
151+ await this . database . writeTransaction ( async ( tx ) => {
152+ await tx . execute ( 'DELETE FROM ps_oplog WHERE 1' ) ;
153+ await tx . execute ( 'DELETE FROM ps_crud WHERE 1' ) ;
154+ await tx . execute ( 'DELETE FROM ps_buckets WHERE 1' ) ;
149155
150- const existingTableRows = await tx . executeAsync (
156+ const existingTableRows = await tx . execute (
151157 "SELECT name FROM sqlite_master WHERE type='table' AND name GLOB 'ps_data_*'"
152158 ) ;
153159
154160 if ( ! existingTableRows . rows . length ) {
155161 return ;
156162 }
157163 for ( const row of existingTableRows . rows . _array ) {
158- await tx . executeAsync ( `DELETE FROM ${ row . name } WHERE 1` ) ;
164+ await tx . execute ( `DELETE FROM ${ row . name } WHERE 1` ) ;
159165 }
160166 } ) ;
161167 }
@@ -181,14 +187,12 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
181187 async getUploadQueueStats ( includeSize ?: boolean ) : Promise < UploadQueueStats > {
182188 return this . readTransaction ( async ( tx ) => {
183189 if ( includeSize ) {
184- const result = await tx . executeAsync (
185- 'SELECT SUM(cast(data as blob) + 20) as size, count(*) as count FROM ps_crud'
186- ) ;
190+ const result = await tx . execute ( 'SELECT SUM(cast(data as blob) + 20) as size, count(*) as count FROM ps_crud' ) ;
187191
188192 const row = result . rows . item ( 0 ) ;
189193 return new UploadQueueStats ( row ?. count ?? 0 , row ?. size ?? 0 ) ;
190194 } else {
191- const result = await tx . executeAsync ( 'SELECT count(*) as count FROM ps_crud' ) ;
195+ const result = await tx . execute ( 'SELECT count(*) as count FROM ps_crud' ) ;
192196 const row = result . rows . item ( 0 ) ;
193197 return new UploadQueueStats ( row ?. count ?? 0 ) ;
194198 }
@@ -213,7 +217,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
213217 * and a single transaction may be split over multiple batches.
214218 */
215219 async getCrudBatch ( limit : number ) : Promise < CrudBatch | null > {
216- const result = await this . database . executeAsync ( 'SELECT id, tx_id, data FROM ps_crud ORDER BY id ASC LIMIT ?' , [
220+ const result = await this . database . execute ( 'SELECT id, tx_id, data FROM ps_crud ORDER BY id ASC LIMIT ?' , [
217221 limit + 1
218222 ] ) ;
219223
@@ -231,11 +235,11 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
231235 const last = all [ all . length - 1 ] ;
232236 return new CrudBatch ( all , haveMore , async ( writeCheckpoint ?: string ) => {
233237 await this . writeTransaction ( async ( tx ) => {
234- await tx . executeAsync ( 'DELETE FROM ps_crud WHERE id <= ?' , [ last . clientId ] ) ;
235- if ( writeCheckpoint != null && ( await tx . executeAsync ( 'SELECT 1 FROM ps_crud LIMIT 1' ) ) == null ) {
236- await tx . executeAsync ( "UPDATE ps_buckets SET target_op = ? WHERE name='$local'" , [ writeCheckpoint ] ) ;
238+ await tx . execute ( 'DELETE FROM ps_crud WHERE id <= ?' , [ last . clientId ] ) ;
239+ if ( writeCheckpoint != null && ( await tx . execute ( 'SELECT 1 FROM ps_crud LIMIT 1' ) ) == null ) {
240+ await tx . execute ( "UPDATE ps_buckets SET target_op = ? WHERE name='$local'" , [ writeCheckpoint ] ) ;
237241 } else {
238- await tx . executeAsync ( "UPDATE ps_buckets SET target_op = ? WHERE name='$local'" , [
242+ await tx . execute ( "UPDATE ps_buckets SET target_op = ? WHERE name='$local'" , [
239243 this . bucketStorageAdapter . getMaxOpId ( )
240244 ] ) ;
241245 }
@@ -258,7 +262,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
258262 */
259263 async getNextCrudTransaction ( ) : Promise < CrudTransaction > {
260264 return await this . readTransaction ( async ( tx ) => {
261- const first = await tx . executeAsync ( 'SELECT id, tx_id, data FROM ps_crud ORDER BY id ASC LIMIT 1' ) ;
265+ const first = await tx . execute ( 'SELECT id, tx_id, data FROM ps_crud ORDER BY id ASC LIMIT 1' ) ;
262266
263267 if ( ! first . rows . length ) {
264268 return null ;
@@ -269,9 +273,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
269273 if ( ! txId ) {
270274 all = [ CrudEntry . fromRow ( first . rows . item ( 0 ) ) ] ;
271275 } else {
272- const result = await tx . executeAsync ( 'SELECT id, tx_id, data FROM ps_crud WHERE tx_id = ? ORDER BY id ASC' , [
273- txId
274- ] ) ;
276+ const result = await tx . execute ( 'SELECT id, tx_id, data FROM ps_crud WHERE tx_id = ? ORDER BY id ASC' , [ txId ] ) ;
275277 all = result . rows . _array . map ( ( row ) => CrudEntry . fromRow ( row ) ) ;
276278 }
277279
@@ -281,14 +283,14 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
281283 all ,
282284 async ( writeCheckpoint ?: string ) => {
283285 await this . writeTransaction ( async ( tx ) => {
284- await tx . executeAsync ( 'DELETE FROM ps_crud WHERE id <= ?' , [ last . clientId ] ) ;
286+ await tx . execute ( 'DELETE FROM ps_crud WHERE id <= ?' , [ last . clientId ] ) ;
285287 if ( writeCheckpoint ) {
286- const check = await tx . executeAsync ( 'SELECT 1 FROM ps_crud LIMIT 1' ) ;
288+ const check = await tx . execute ( 'SELECT 1 FROM ps_crud LIMIT 1' ) ;
287289 if ( ! check . rows ?. length ) {
288- await tx . executeAsync ( "UPDATE ps_buckets SET target_op = ? WHERE name='$local'" , [ writeCheckpoint ] ) ;
290+ await tx . execute ( "UPDATE ps_buckets SET target_op = ? WHERE name='$local'" , [ writeCheckpoint ] ) ;
289291 }
290292 } else {
291- await tx . executeAsync ( "UPDATE ps_buckets SET target_op = ? WHERE name='$local'" , [
293+ await tx . execute ( "UPDATE ps_buckets SET target_op = ? WHERE name='$local'" , [
292294 this . bucketStorageAdapter . getMaxOpId ( )
293295 ] ) ;
294296 }
@@ -303,36 +305,32 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
303305 * Execute a statement and optionally return results
304306 */
305307 async execute ( sql : string , parameters ?: any [ ] ) {
306- const res = await this . writeLock ( ( tx ) => tx . executeAsync ( sql , parameters ) ) ;
307- return res ;
308+ await this . initialized ;
309+ return this . database . execute ( sql , parameters ) ;
308310 }
309311
310312 /**
311313 * Execute a read-only query and return results
312314 */
313315 async getAll < T > ( sql : string , parameters ?: any [ ] ) : Promise < T [ ] > {
314- const res = await this . readTransaction ( ( tx ) => tx . executeAsync ( sql , parameters ) ) ;
315- return res . rows ?. _array ?? [ ] ;
316+ await this . initialized ;
317+ return this . database . getAll ( sql , parameters ) ;
316318 }
317319
318320 /**
319321 * Execute a read-only query and return the first result, or null if the ResultSet is empty.
320322 */
321323 async getOptional < T > ( sql : string , parameters ?: any [ ] ) : Promise < T | null > {
322- const res = await this . readTransaction ( ( tx ) => tx . executeAsync ( sql , parameters ) ) ;
323- return res . rows ?. item ( 0 ) ?? null ;
324+ await this . initialized ;
325+ return this . database . getOptional ( sql , parameters ) ;
324326 }
325327
326328 /**
327329 * Execute a read-only query and return the first result, error if the ResultSet is empty.
328330 */
329331 async get < T > ( sql : string , parameters ?: any [ ] ) : Promise < T > {
330- const res = await this . readTransaction ( ( tx ) => tx . executeAsync ( sql , parameters ) ) ;
331- const first = res . rows ?. item ( 0 ) ;
332- if ( ! first ) {
333- throw new Error ( 'Result set is empty' ) ;
334- }
335- return first ;
332+ await this . initialized ;
333+ return this . database . get ( sql , parameters ) ;
336334 }
337335
338336 /**
@@ -358,40 +356,43 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
358356 } ) ;
359357 }
360358
361- async readTransaction < T > ( callback : ( tx : Transaction ) => Promise < T > , lockTimeout ?: number ) : Promise < T > {
359+ async readTransaction < T > (
360+ callback : ( tx : Transaction ) => Promise < T > ,
361+ lockTimeout : number = DEFAULT_LOCK_TIMEOUT_MS
362+ ) : Promise < T > {
362363 await this . initialized ;
363- return this . runLockedTransaction (
364- AbstractPowerSyncDatabase . transactionMutex ,
364+ return this . database . readTransaction (
365365 async ( tx ) => {
366- const res = await callback ( tx ) ;
367- await tx . rollbackAsync ( ) ;
366+ const res = await callback ( { ... tx } ) ;
367+ await tx . rollback ( ) ;
368368 return res ;
369369 } ,
370- lockTimeout
370+ { timeoutMs : lockTimeout }
371371 ) ;
372372 }
373373
374- async writeTransaction < T > ( callback : ( tx : Transaction ) => Promise < T > , lockTimeout ?: number ) : Promise < T > {
374+ async writeTransaction < T > (
375+ callback : ( tx : Transaction ) => Promise < T > ,
376+ lockTimeout : number = DEFAULT_LOCK_TIMEOUT_MS
377+ ) : Promise < T > {
375378 await this . initialized ;
376- return this . runLockedTransaction (
377- AbstractPowerSyncDatabase . transactionMutex ,
379+ return this . database . writeTransaction (
378380 async ( tx ) => {
379381 const res = await callback ( tx ) ;
380- await tx . commitAsync ( ) ;
382+ await tx . commit ( ) ;
381383 _ . defer ( ( ) => this . syncStreamImplementation ?. triggerCrudUpload ( ) ) ;
382384 return res ;
383385 } ,
384- lockTimeout
386+ { timeoutMs : lockTimeout }
385387 ) ;
386388 }
387389
388- async * watch ( sql : string , parameters : any [ ] , options ?: SQLWatchOptions ) : AsyncIterable < QueryResult > {
390+ async * watch ( sql : string , parameters ? : any [ ] , options ?: SQLWatchOptions ) : AsyncIterable < QueryResult > {
389391 //Fetch initial data
390392 yield await this . execute ( sql , parameters ) ;
391393
392394 const resolvedTables = options ?. tables ?? [ ] ;
393395 if ( ! options ?. tables ) {
394- // TODO get tables from sql if not specified
395396 const explained = await this . getAll ( `EXPLAIN ${ sql } ` , parameters ) ;
396397 const rootPages = _ . chain ( explained )
397398 . filter ( ( row ) => row [ 'opcode' ] == 'OpenRead' && row [ 'p3' ] == 0 && _ . isNumber ( row [ 'p2' ] ) )
@@ -401,7 +402,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
401402 `SELECT tbl_name FROM sqlite_master WHERE rootpage IN (SELECT json_each.value FROM json_each(?))` ,
402403 [ JSON . stringify ( rootPages ) ]
403404 ) ;
404- tables . forEach ( ( t ) => resolvedTables . push ( t . tbl_name . replace ( / ^ p s _ d a t a _ _ / , '' ) ) ) ;
405+ tables . forEach ( ( t ) => resolvedTables . push ( t . tbl_name . replace ( POWERSYNC_TABLE_MATCH , '' ) ) ) ;
405406 }
406407 for await ( const event of this . onChange ( {
407408 ...( options ?? { } ) ,
@@ -458,27 +459,4 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
458459 return ( ) => dispose ( ) ;
459460 } ) ;
460461 }
461-
462- private runLockedTransaction < T > (
463- mutex : Mutex ,
464- callback : ( tx : Transaction ) => Promise < T > ,
465- lockTimeout ?: number
466- ) : Promise < T > {
467- return mutexRunExclusive (
468- mutex ,
469- ( ) => {
470- return new Promise < T > ( async ( resolve , reject ) => {
471- try {
472- await this . database . transaction ( async ( tx ) => {
473- const r = await callback ( tx ) ;
474- resolve ( r ) ;
475- } ) ;
476- } catch ( ex ) {
477- reject ( ex ) ;
478- }
479- } ) ;
480- } ,
481- { timeoutMs : lockTimeout }
482- ) ;
483- }
484462}
0 commit comments