@@ -632,35 +632,72 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
632
632
* @returns A transaction of CRUD operations to upload, or null if there are none
633
633
*/
634
634
async getNextCrudTransaction ( ) : Promise < CrudTransaction | null > {
635
- return await this . readTransaction ( async ( tx ) => {
636
- const first = await tx . getOptional < CrudEntryJSON > (
637
- `SELECT id, tx_id, data FROM ${ PSInternalTable . CRUD } ORDER BY id ASC LIMIT 1`
638
- ) ;
635
+ for await ( const transaction of this . getCrudTransactions ( ) ) {
636
+ return transaction ;
637
+ }
639
638
640
- if ( ! first ) {
641
- return null ;
642
- }
643
- const txId = first . tx_id ;
639
+ return null ;
640
+ }
644
641
645
- let all : CrudEntry [ ] ;
646
- if ( ! txId ) {
647
- all = [ CrudEntry . fromRow ( first ) ] ;
648
- } else {
649
- const result = await tx . getAll < CrudEntryJSON > (
650
- `SELECT id, tx_id, data FROM ${ PSInternalTable . CRUD } WHERE tx_id = ? ORDER BY id ASC` ,
651
- [ txId ]
652
- ) ;
653
- all = result . map ( ( row ) => CrudEntry . fromRow ( row ) ) ;
642
+ /**
643
+ * Returns an async iterator of completed transactions with local writes against the database.
644
+ *
645
+ * This is typically used from the {@link PowerSyncBackendConnector.uploadData} callback. Each entry emitted by the
646
+ * returned flow is a full transaction containing all local writes made while that transaction was active.
647
+ *
648
+ * Unlike {@link getNextCrudTransaction}, which always returns the oldest transaction that hasn't been
649
+ * {@link CrudTransaction.complete}d yet, this flow can be used to collect multiple transactions. Calling
650
+ * {@link CrudTransaction.complete} will mark _all_ transactions emitted by the flow until that point as completed.
651
+ *
652
+ * This can be used to upload multiple transactions in a single batch, e.g with:
653
+ *
654
+ * ```TypeScript
655
+ * let lastTransaction: CrudTransaction | null = null;
656
+ * let batch: CrudEntry[] = [];
657
+ *
658
+ * for await (const transaction of database.getCrudTransactions()) {
659
+ * batch.push(...transaction.crud);
660
+ * lastTransaction = transaction;
661
+ *
662
+ * if (batch.length > 10) {
663
+ * break;
664
+ * }
665
+ * }
666
+ * ```
667
+ *
668
+ * If there is no local data to upload, the async iterator complete without emitting any items.
669
+ */
670
+ async * getCrudTransactions ( ) : AsyncIterable < CrudTransaction > {
671
+ let lastCrudItemId = - 1 ;
672
+ const sql = `
673
+ WITH RECURSIVE crud_entries AS (
674
+ SELECT id, tx_id, data FROM ps_crud WHERE id = (SELECT min(id) FROM ps_crud WHERE id > ?)
675
+ UNION ALL
676
+ SELECT ps_crud.id, ps_crud.tx_id, ps_crud.data FROM ps_crud
677
+ INNER JOIN crud_entries ON crud_entries.id + 1 = rowid
678
+ WHERE crud_entries.tx_id = ps_crud.tx_id
679
+ )
680
+ SELECT * FROM crud_entries;
681
+ ` ;
682
+
683
+ while ( true ) {
684
+ const nextTransaction = await this . database . getAll < CrudEntryJSON > ( sql , [ lastCrudItemId ] ) ;
685
+ if ( nextTransaction . length == 0 ) {
686
+ break ;
654
687
}
655
688
656
- const last = all [ all . length - 1 ] ;
689
+ const items = nextTransaction . map ( ( row ) => CrudEntry . fromRow ( row ) ) ;
690
+ const last = items [ items . length - 1 ] ;
691
+ const txId = last . transactionId ;
657
692
658
- return new CrudTransaction (
659
- all ,
693
+ yield new CrudTransaction (
694
+ items ,
660
695
async ( writeCheckpoint ?: string ) => this . handleCrudCheckpoint ( last . clientId , writeCheckpoint ) ,
661
696
txId
662
697
) ;
663
- } ) ;
698
+
699
+ lastCrudItemId = last . clientId ;
700
+ }
664
701
}
665
702
666
703
/**
0 commit comments