Skip to content

Commit 06b74ad

Browse files
committed
Restore RN compatibility
1 parent 0f42227 commit 06b74ad

File tree

3 files changed

+49
-29
lines changed

3 files changed

+49
-29
lines changed

packages/common/src/client/AbstractPowerSyncDatabase.ts

Lines changed: 37 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { UploadQueueStats } from '../db/crud/UploadQueueStatus.js';
1515
import { Schema } from '../db/schema/Schema.js';
1616
import { BaseObserver } from '../utils/BaseObserver.js';
1717
import { ControlledExecutor } from '../utils/ControlledExecutor.js';
18-
import { throttleTrailing } from '../utils/async.js';
18+
import { symbolAsyncIterator, throttleTrailing } from '../utils/async.js';
1919
import { ConnectionManager } from './ConnectionManager.js';
2020
import { CustomQuery } from './CustomQuery.js';
2121
import { ArrayQueryDefinition, Query } from './Query.js';
@@ -632,11 +632,8 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
632632
* @returns A transaction of CRUD operations to upload, or null if there are none
633633
*/
634634
async getNextCrudTransaction(): Promise<CrudTransaction | null> {
635-
for await (const transaction of this.getCrudTransactions()) {
636-
return transaction;
637-
}
638-
639-
return null;
635+
const iterator = this.getCrudTransactions()[symbolAsyncIterator]();
636+
return (await iterator.next()).value;
640637
}
641638

642639
/**
@@ -652,9 +649,9 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
652649
*
653650
* This can be used to upload multiple transactions in a single batch, e.g with:
654651
*
655-
* ```TypeScript
656-
* let lastTransaction: CrudTransaction | null = null;
657-
* let batch: CrudEntry[] = [];
652+
* ```JavaScript
653+
* let lastTransaction = null;
654+
* let batch = [];
658655
*
659656
* for await (const transaction of database.getCrudTransactions()) {
660657
* batch.push(...transaction.crud);
@@ -667,10 +664,15 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
667664
* ```
668665
*
669666
* If there is no local data to upload, the async iterator complete without emitting any items.
667+
*
668+
* Note that iterating over async iterables requires a [polyfill](https://github.com/powersync-ja/powersync-js/tree/main/packages/react-native#babel-plugins-watched-queries)
669+
* for React Native.
670670
*/
671-
async *getCrudTransactions(): AsyncIterable<CrudTransaction> {
672-
let lastCrudItemId = -1;
673-
const sql = `
671+
getCrudTransactions(): AsyncIterable<CrudTransaction, null> {
672+
return {
673+
[symbolAsyncIterator]: () => {
674+
let lastCrudItemId = -1;
675+
const sql = `
674676
WITH RECURSIVE crud_entries AS (
675677
SELECT id, tx_id, data FROM ps_crud WHERE id = (SELECT min(id) FROM ps_crud WHERE id > ?)
676678
UNION ALL
@@ -681,24 +683,30 @@ WITH RECURSIVE crud_entries AS (
681683
SELECT * FROM crud_entries;
682684
`;
683685

684-
while (true) {
685-
const nextTransaction = await this.database.getAll<CrudEntryJSON>(sql, [lastCrudItemId]);
686-
if (nextTransaction.length == 0) {
687-
break;
688-
}
689-
690-
const items = nextTransaction.map((row) => CrudEntry.fromRow(row));
691-
const last = items[items.length - 1];
692-
const txId = last.transactionId;
693-
694-
yield new CrudTransaction(
695-
items,
696-
async (writeCheckpoint?: string) => this.handleCrudCheckpoint(last.clientId, writeCheckpoint),
697-
txId
698-
);
686+
return {
687+
next: async () => {
688+
const nextTransaction = await this.database.getAll<CrudEntryJSON>(sql, [lastCrudItemId]);
689+
if (nextTransaction.length == 0) {
690+
return { done: true, value: null };
691+
}
699692

700-
lastCrudItemId = last.clientId;
701-
}
693+
const items = nextTransaction.map((row) => CrudEntry.fromRow(row));
694+
const last = items[items.length - 1];
695+
const txId = last.transactionId;
696+
lastCrudItemId = last.clientId;
697+
698+
return {
699+
done: false,
700+
value: new CrudTransaction(
701+
items,
702+
async (writeCheckpoint?: string) => this.handleCrudCheckpoint(last.clientId, writeCheckpoint),
703+
txId
704+
)
705+
};
706+
}
707+
};
708+
}
709+
};
702710
}
703711

704712
/**

packages/common/src/utils/async.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
/**
2+
* A ponyfill for `Symbol.asyncIterator` that is compatible with the
3+
* [recommended polyfill](https://github.com/Azure/azure-sdk-for-js/blob/%40azure/core-asynciterator-polyfill_1.0.2/sdk/core/core-asynciterator-polyfill/src/index.ts#L4-L6)
4+
* we recommend for React Native.
5+
*
6+
* As long as we use this symbol (instead of `for await` and `async *`) in this package, we can be compatible with async
7+
* iterators without requiring them.
8+
*/
9+
export const symbolAsyncIterator: typeof Symbol.asyncIterator =
10+
Symbol.asyncIterator ?? Symbol.for('Symbol.asyncIterator');
11+
112
/**
213
* Throttle a function to be called at most once every "wait" milliseconds,
314
* on the trailing edge.

packages/react-native/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ npx expo install @journeyapps/react-native-quick-sqlite
3737
Watched queries can be used with either a callback response or Async Iterator response.
3838

3939
Watched queries using the Async Iterator response format require support for Async Iterators.
40+
`PowerSyncDatabase.getCrudTransactions()` also returns an Async Iterator and requires this workaround.
4041

4142
Expo apps currently require polyfill and Babel plugins in order to use this functionality.
4243

0 commit comments

Comments
 (0)