Skip to content

Commit 4d22c13

Browse files
Merge pull request #24 from powersync-ja/web-sdk-common-changes
[Fix] Common SDK fixes for Web and React Native
2 parents b3a0f2a + 8bcbcd4 commit 4d22c13

File tree

17 files changed

+832
-580
lines changed

17 files changed

+832
-580
lines changed

.changeset/beige-vans-hunt.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@journeyapps/powersync-sdk-react-native': patch
3+
---
4+
5+
Fixed: `get`, `getAll` and `getOptional` should execute inside a readLock for concurrency

.changeset/five-turtles-dream.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@journeyapps/powersync-sdk-common': patch
3+
---
4+
5+
- Removed `user-id` header from backend connector and remote headers.
6+
- Added `waitForReady` method on PowerSyncDatabase client which resolves once initialization is complete.

.changeset/rare-zoos-walk.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@journeyapps/powersync-react': patch
3+
---
4+
5+
Fixed: Added correct typings for React hooks. Previously hooks would return `any`.

apps/supabase-todolist

packages/powersync-attachments/package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,14 @@
2020
"dependencies": {
2121
"@journeyapps/powersync-sdk-common": "0.1.1"
2222
},
23-
"repository": "https://github.com/journeyapps/powersync-react-native-sdk",
23+
"repository": {
24+
"type": "git",
25+
"url": "git+https://github.com/powersync-ja/powersync-react-native-sdk.git"
26+
},
2427
"author": "JOURNEYAPPS",
2528
"license": "Apache-2.0",
2629
"homepage": "https://docs.powersync.co/resources/api-reference",
2730
"bugs": {
28-
"url": "https://github.com/journeyapps/powersync-react-native-sdk/issues"
31+
"url": "https://github.com/powersync-ja/powersync-react-native-sdk/issues"
2932
}
3033
}

packages/powersync-react/package.json

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,25 @@
1616
"clean": "rm -rf lib tsconfig.tsbuildinfo",
1717
"watch": "tsc -b -w"
1818
},
19-
"repository": "https://github.com/journeyapps/powersync-react-native-sdk",
19+
"repository": {
20+
"type": "git",
21+
"url": "git+https://github.com/powersync-ja/powersync-react-native-sdk.git"
22+
},
2023
"author": "JOURNEYAPPS",
2124
"license": "Apache-2.0",
2225
"bugs": {
23-
"url": "https://github.com/journeyapps/powersync-react-native-sdk/issues"
26+
"url": "https://github.com/powersync-ja/powersync-react-native-sdk/issues"
2427
},
2528
"homepage": "https://docs.powersync.co/resources/api-reference",
2629
"dependencies": {
27-
"@journeyapps/powersync-sdk-common": "^0.1.0"
30+
"@journeyapps/powersync-sdk-common": "0.1.1"
2831
},
2932
"peerDependencies": {
3033
"react": "*"
3134
},
3235
"devDependencies": {
36+
"@types/react": "^18.2.34",
3337
"react": "18.2.0",
34-
"typescript": "^4.1.3"
38+
"typescript": "^5.1.3"
3539
}
3640
}

packages/powersync-react/src/hooks/usePowerSyncQuery.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
1-
import React from "react";
2-
import { usePowerSync } from "./PowerSyncContext";
1+
import React from 'react';
2+
import { usePowerSync } from './PowerSyncContext';
33

44
/**
55
* A hook to access a single static query.
66
* For an updated result, use usePowerSyncWatchedQuery instead
77
*/
8-
export const usePowerSyncQuery = <T = any>(
9-
sqlStatement: string,
10-
parameters: any[] = []
11-
): T[] => {
8+
export const usePowerSyncQuery = <T = any>(sqlStatement: string, parameters: any[] = []): T[] => {
129
const powerSync = usePowerSync();
1310
if (!powerSync) {
1411
return [];
@@ -19,10 +16,10 @@ export const usePowerSyncQuery = <T = any>(
1916
const [data, setData] = React.useState<T[]>([]);
2017

2118
React.useEffect(() => {
22-
powerSync.execute(sqlStatement, parameters).then((result) => {
23-
setData(result.rows?._array ?? []);
19+
powerSync.readLock(async (tx) => {
20+
const result = await tx.getAll<T>(sqlStatement, parameters);
21+
setData(result);
2422
});
25-
//
2623
}, [powerSync, sqlStatement, memoizedParams]);
2724

2825
return data;

packages/powersync-sdk-common/package.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,12 @@
1313
"files": [
1414
"lib"
1515
],
16-
"repository": "https://github.com/journeyapps/powersync-react-native-sdk",
16+
"repository": {
17+
"type": "git",
18+
"url": "git+https://github.com/powersync-ja/powersync-react-native-sdk.git"
19+
},
1720
"bugs": {
18-
"url": "https://github.com/journeyapps/powersync-react-native-sdk/issues"
21+
"url": "https://github.com/powersync-ja/powersync-react-native-sdk/issues"
1922
},
2023
"homepage": "https://docs.powersync.co/resources/api-reference",
2124
"scripts": {
@@ -27,7 +30,7 @@
2730
"@types/node": "^20.5.9",
2831
"@types/object-hash": "^3.0.4",
2932
"@types/uuid": "^3.0.0",
30-
"typescript": "^4.1.3"
33+
"typescript": "^5.1.3"
3134
},
3235
"dependencies": {
3336
"async-mutex": "^0.4.0",

packages/powersync-sdk-common/src/client/AbstractPowerSyncDatabase.ts

Lines changed: 58 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@ export interface WatchOnChangeEvent {
3535
changedTables: string[];
3636
}
3737

38-
export interface PowerSyncDBListener extends StreamingSyncImplementationListener {}
38+
export interface PowerSyncDBListener extends StreamingSyncImplementationListener {
39+
initialized: () => void;
40+
}
3941

4042
const POWERSYNC_TABLE_MATCH = /(^ps_data__|^ps_data_local__)/;
4143

@@ -61,6 +63,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
6163
protected static transactionMutex: Mutex = new Mutex();
6264

6365
closed: boolean;
66+
ready: boolean;
6467

6568
currentStatus?: SyncStatus;
6669
syncStreamImplementation?: AbstractStreamingSyncImplementation;
@@ -69,14 +72,16 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
6972
private abortController: AbortController | null;
7073
protected bucketStorageAdapter: BucketStorageAdapter;
7174
private syncStatusListenerDisposer?: () => void;
72-
protected initialized: Promise<void>;
75+
protected _isReadyPromise: Promise<void> | null;
7376

7477
constructor(protected options: PowerSyncDatabaseOptions) {
7578
super();
76-
this.currentStatus = null;
79+
this._isReadyPromise = null;
80+
this.bucketStorageAdapter = this.generateBucketStorageAdapter();
7781
this.closed = true;
82+
this.currentStatus = null;
7883
this.options = { ...DEFAULT_POWERSYNC_DB_OPTIONS, ...options };
79-
this.bucketStorageAdapter = this.generateBucketStorageAdapter();
84+
this.ready = false;
8085
this.sdkVersion = '';
8186
}
8287

@@ -98,16 +103,40 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
98103

99104
protected abstract generateBucketStorageAdapter(): BucketStorageAdapter;
100105

106+
/**
107+
* @returns A promise which will resolve once initialization is completed.
108+
*/
109+
async waitForReady(): Promise<void> {
110+
if (this.ready) {
111+
return;
112+
}
113+
114+
return (
115+
this._isReadyPromise ||
116+
(this._isReadyPromise = new Promise((resolve) => {
117+
const l = this.registerListener({
118+
initialized: () => {
119+
this.ready = true;
120+
resolve();
121+
l?.();
122+
}
123+
});
124+
}))
125+
);
126+
}
127+
101128
abstract _init(): Promise<void>;
129+
130+
/**
131+
* This performs the total initialization process.
132+
*/
102133
async init() {
103-
this.initialized = (async () => {
104-
await this._init();
105-
await this.bucketStorageAdapter.init();
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()'] ?? '';
109-
})();
110-
await this.initialized;
134+
await this._init();
135+
await this.bucketStorageAdapter.init();
136+
await this.database.execute('SELECT powersync_replace_schema(?)', [JSON.stringify(this.schema.toJSON())]);
137+
const version = await this.options.database.execute('SELECT powersync_rs_version()');
138+
this.sdkVersion = version.rows?.item(0)['powersync_rs_version()'] ?? '';
139+
this.iterateListeners((cb) => cb.initialized?.());
111140
}
112141

113142
/**
@@ -117,7 +146,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
117146
// close connection if one is open
118147
await this.disconnect();
119148

120-
await this.initialized;
149+
await this.waitForReady();
121150
this.syncStreamImplementation = this.generateSyncStreamImplementation(connector);
122151
this.syncStatusListenerDisposer = this.syncStreamImplementation.registerListener({
123152
statusChanged: (status) => {
@@ -175,7 +204,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
175204
* must be constructed.
176205
*/
177206
async close() {
178-
await this.initialized;
207+
await this.waitForReady();
179208

180209
await this.disconnect();
181210
this.database.close();
@@ -305,31 +334,31 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
305334
* Execute a statement and optionally return results
306335
*/
307336
async execute(sql: string, parameters?: any[]) {
308-
await this.initialized;
337+
await this.waitForReady();
309338
return this.database.execute(sql, parameters);
310339
}
311340

312341
/**
313342
* Execute a read-only query and return results
314343
*/
315344
async getAll<T>(sql: string, parameters?: any[]): Promise<T[]> {
316-
await this.initialized;
345+
await this.waitForReady();
317346
return this.database.getAll(sql, parameters);
318347
}
319348

320349
/**
321350
* Execute a read-only query and return the first result, or null if the ResultSet is empty.
322351
*/
323352
async getOptional<T>(sql: string, parameters?: any[]): Promise<T | null> {
324-
await this.initialized;
353+
await this.waitForReady();
325354
return this.database.getOptional(sql, parameters);
326355
}
327356

328357
/**
329358
* Execute a read-only query and return the first result, error if the ResultSet is empty.
330359
*/
331360
async get<T>(sql: string, parameters?: any[]): Promise<T> {
332-
await this.initialized;
361+
await this.waitForReady();
333362
return this.database.get(sql, parameters);
334363
}
335364

@@ -339,7 +368,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
339368
* In most cases, [readTransaction] should be used instead.
340369
*/
341370
async readLock<T>(callback: (db: DBAdapter) => Promise<T>) {
342-
await this.initialized;
371+
await this.waitForReady();
343372
return mutexRunExclusive(AbstractPowerSyncDatabase.transactionMutex, () => callback(this.database));
344373
}
345374

@@ -348,7 +377,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
348377
* In most cases, [writeTransaction] should be used instead.
349378
*/
350379
async writeLock<T>(callback: (db: DBAdapter) => Promise<T>) {
351-
await this.initialized;
380+
await this.waitForReady();
352381
return mutexRunExclusive(AbstractPowerSyncDatabase.transactionMutex, async () => {
353382
const res = await callback(this.database);
354383
_.defer(() => this.syncStreamImplementation?.triggerCrudUpload());
@@ -360,7 +389,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
360389
callback: (tx: Transaction) => Promise<T>,
361390
lockTimeout: number = DEFAULT_LOCK_TIMEOUT_MS
362391
): Promise<T> {
363-
await this.initialized;
392+
await this.waitForReady();
364393
return this.database.readTransaction(
365394
async (tx) => {
366395
const res = await callback({ ...tx });
@@ -375,7 +404,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
375404
callback: (tx: Transaction) => Promise<T>,
376405
lockTimeout: number = DEFAULT_LOCK_TIMEOUT_MS
377406
): Promise<T> {
378-
await this.initialized;
407+
await this.waitForReady();
379408
return this.database.writeTransaction(
380409
async (tx) => {
381410
const res = await callback(tx);
@@ -389,7 +418,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
389418

390419
async *watch(sql: string, parameters?: any[], options?: SQLWatchOptions): AsyncIterable<QueryResult> {
391420
//Fetch initial data
392-
yield await this.execute(sql, parameters);
421+
yield await this.executeReadOnly(sql, parameters);
393422

394423
const resolvedTables = options?.tables ?? [];
395424
if (!options?.tables) {
@@ -408,7 +437,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
408437
...(options ?? {}),
409438
tables: resolvedTables
410439
})) {
411-
yield await this.execute(sql, parameters);
440+
yield await this.executeReadOnly(sql, parameters);
412441
}
413442
}
414443

@@ -459,4 +488,9 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
459488
return () => dispose();
460489
});
461490
}
491+
492+
private async executeReadOnly(sql: string, params: any[]) {
493+
await this.waitForReady();
494+
return this.database.readLock((tx) => tx.execute(sql, params));
495+
}
462496
}
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
export interface PowerSyncCredentials {
22
endpoint: string;
33
token: string;
4-
userID?: string;
54
expiresAt?: Date;
65
}

0 commit comments

Comments
 (0)