Skip to content

Commit

Permalink
Merge pull request #24 from powersync-ja/web-sdk-common-changes
Browse files Browse the repository at this point in the history
[Fix] Common SDK fixes for Web and React Native
  • Loading branch information
stevensJourney authored Nov 6, 2023
2 parents b3a0f2a + 8bcbcd4 commit 4d22c13
Show file tree
Hide file tree
Showing 17 changed files with 832 additions and 580 deletions.
5 changes: 5 additions & 0 deletions .changeset/beige-vans-hunt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@journeyapps/powersync-sdk-react-native': patch
---

Fixed: `get`, `getAll` and `getOptional` should execute inside a readLock for concurrency
6 changes: 6 additions & 0 deletions .changeset/five-turtles-dream.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@journeyapps/powersync-sdk-common': patch
---

- Removed `user-id` header from backend connector and remote headers.
- Added `waitForReady` method on PowerSyncDatabase client which resolves once initialization is complete.
5 changes: 5 additions & 0 deletions .changeset/rare-zoos-walk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@journeyapps/powersync-react': patch
---

Fixed: Added correct typings for React hooks. Previously hooks would return `any`.
2 changes: 1 addition & 1 deletion apps/supabase-todolist
7 changes: 5 additions & 2 deletions packages/powersync-attachments/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,14 @@
"dependencies": {
"@journeyapps/powersync-sdk-common": "0.1.1"
},
"repository": "https://github.com/journeyapps/powersync-react-native-sdk",
"repository": {
"type": "git",
"url": "git+https://github.com/powersync-ja/powersync-react-native-sdk.git"
},
"author": "JOURNEYAPPS",
"license": "Apache-2.0",
"homepage": "https://docs.powersync.co/resources/api-reference",
"bugs": {
"url": "https://github.com/journeyapps/powersync-react-native-sdk/issues"
"url": "https://github.com/powersync-ja/powersync-react-native-sdk/issues"
}
}
12 changes: 8 additions & 4 deletions packages/powersync-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,25 @@
"clean": "rm -rf lib tsconfig.tsbuildinfo",
"watch": "tsc -b -w"
},
"repository": "https://github.com/journeyapps/powersync-react-native-sdk",
"repository": {
"type": "git",
"url": "git+https://github.com/powersync-ja/powersync-react-native-sdk.git"
},
"author": "JOURNEYAPPS",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/journeyapps/powersync-react-native-sdk/issues"
"url": "https://github.com/powersync-ja/powersync-react-native-sdk/issues"
},
"homepage": "https://docs.powersync.co/resources/api-reference",
"dependencies": {
"@journeyapps/powersync-sdk-common": "^0.1.0"
"@journeyapps/powersync-sdk-common": "0.1.1"
},
"peerDependencies": {
"react": "*"
},
"devDependencies": {
"@types/react": "^18.2.34",
"react": "18.2.0",
"typescript": "^4.1.3"
"typescript": "^5.1.3"
}
}
15 changes: 6 additions & 9 deletions packages/powersync-react/src/hooks/usePowerSyncQuery.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import React from "react";
import { usePowerSync } from "./PowerSyncContext";
import React from 'react';
import { usePowerSync } from './PowerSyncContext';

/**
* A hook to access a single static query.
* For an updated result, use usePowerSyncWatchedQuery instead
*/
export const usePowerSyncQuery = <T = any>(
sqlStatement: string,
parameters: any[] = []
): T[] => {
export const usePowerSyncQuery = <T = any>(sqlStatement: string, parameters: any[] = []): T[] => {
const powerSync = usePowerSync();
if (!powerSync) {
return [];
Expand All @@ -19,10 +16,10 @@ export const usePowerSyncQuery = <T = any>(
const [data, setData] = React.useState<T[]>([]);

React.useEffect(() => {
powerSync.execute(sqlStatement, parameters).then((result) => {
setData(result.rows?._array ?? []);
powerSync.readLock(async (tx) => {
const result = await tx.getAll<T>(sqlStatement, parameters);
setData(result);
});
//
}, [powerSync, sqlStatement, memoizedParams]);

return data;
Expand Down
9 changes: 6 additions & 3 deletions packages/powersync-sdk-common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@
"files": [
"lib"
],
"repository": "https://github.com/journeyapps/powersync-react-native-sdk",
"repository": {
"type": "git",
"url": "git+https://github.com/powersync-ja/powersync-react-native-sdk.git"
},
"bugs": {
"url": "https://github.com/journeyapps/powersync-react-native-sdk/issues"
"url": "https://github.com/powersync-ja/powersync-react-native-sdk/issues"
},
"homepage": "https://docs.powersync.co/resources/api-reference",
"scripts": {
Expand All @@ -27,7 +30,7 @@
"@types/node": "^20.5.9",
"@types/object-hash": "^3.0.4",
"@types/uuid": "^3.0.0",
"typescript": "^4.1.3"
"typescript": "^5.1.3"
},
"dependencies": {
"async-mutex": "^0.4.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ export interface WatchOnChangeEvent {
changedTables: string[];
}

export interface PowerSyncDBListener extends StreamingSyncImplementationListener {}
export interface PowerSyncDBListener extends StreamingSyncImplementationListener {
initialized: () => void;
}

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

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

closed: boolean;
ready: boolean;

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

constructor(protected options: PowerSyncDatabaseOptions) {
super();
this.currentStatus = null;
this._isReadyPromise = null;
this.bucketStorageAdapter = this.generateBucketStorageAdapter();
this.closed = true;
this.currentStatus = null;
this.options = { ...DEFAULT_POWERSYNC_DB_OPTIONS, ...options };
this.bucketStorageAdapter = this.generateBucketStorageAdapter();
this.ready = false;
this.sdkVersion = '';
}

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

protected abstract generateBucketStorageAdapter(): BucketStorageAdapter;

/**
* @returns A promise which will resolve once initialization is completed.
*/
async waitForReady(): Promise<void> {
if (this.ready) {
return;
}

return (
this._isReadyPromise ||
(this._isReadyPromise = new Promise((resolve) => {
const l = this.registerListener({
initialized: () => {
this.ready = true;
resolve();
l?.();
}
});
}))
);
}

abstract _init(): Promise<void>;

/**
* This performs the total initialization process.
*/
async init() {
this.initialized = (async () => {
await this._init();
await this.bucketStorageAdapter.init();
await this.database.execute('SELECT powersync_replace_schema(?)', [JSON.stringify(this.schema.toJSON())]);
const version = await this.options.database.execute('SELECT powersync_rs_version()');
this.sdkVersion = version.rows?.item(0)['powersync_rs_version()'] ?? '';
})();
await this.initialized;
await this._init();
await this.bucketStorageAdapter.init();
await this.database.execute('SELECT powersync_replace_schema(?)', [JSON.stringify(this.schema.toJSON())]);
const version = await this.options.database.execute('SELECT powersync_rs_version()');
this.sdkVersion = version.rows?.item(0)['powersync_rs_version()'] ?? '';
this.iterateListeners((cb) => cb.initialized?.());
}

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

await this.initialized;
await this.waitForReady();
this.syncStreamImplementation = this.generateSyncStreamImplementation(connector);
this.syncStatusListenerDisposer = this.syncStreamImplementation.registerListener({
statusChanged: (status) => {
Expand Down Expand Up @@ -175,7 +204,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
* must be constructed.
*/
async close() {
await this.initialized;
await this.waitForReady();

await this.disconnect();
this.database.close();
Expand Down Expand Up @@ -305,31 +334,31 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
* Execute a statement and optionally return results
*/
async execute(sql: string, parameters?: any[]) {
await this.initialized;
await this.waitForReady();
return this.database.execute(sql, parameters);
}

/**
* Execute a read-only query and return results
*/
async getAll<T>(sql: string, parameters?: any[]): Promise<T[]> {
await this.initialized;
await this.waitForReady();
return this.database.getAll(sql, parameters);
}

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

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

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

Expand All @@ -348,7 +377,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
* In most cases, [writeTransaction] should be used instead.
*/
async writeLock<T>(callback: (db: DBAdapter) => Promise<T>) {
await this.initialized;
await this.waitForReady();
return mutexRunExclusive(AbstractPowerSyncDatabase.transactionMutex, async () => {
const res = await callback(this.database);
_.defer(() => this.syncStreamImplementation?.triggerCrudUpload());
Expand All @@ -360,7 +389,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
callback: (tx: Transaction) => Promise<T>,
lockTimeout: number = DEFAULT_LOCK_TIMEOUT_MS
): Promise<T> {
await this.initialized;
await this.waitForReady();
return this.database.readTransaction(
async (tx) => {
const res = await callback({ ...tx });
Expand All @@ -375,7 +404,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
callback: (tx: Transaction) => Promise<T>,
lockTimeout: number = DEFAULT_LOCK_TIMEOUT_MS
): Promise<T> {
await this.initialized;
await this.waitForReady();
return this.database.writeTransaction(
async (tx) => {
const res = await callback(tx);
Expand All @@ -389,7 +418,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB

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

const resolvedTables = options?.tables ?? [];
if (!options?.tables) {
Expand All @@ -408,7 +437,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
...(options ?? {}),
tables: resolvedTables
})) {
yield await this.execute(sql, parameters);
yield await this.executeReadOnly(sql, parameters);
}
}

Expand Down Expand Up @@ -459,4 +488,9 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
return () => dispose();
});
}

private async executeReadOnly(sql: string, params: any[]) {
await this.waitForReady();
return this.database.readLock((tx) => tx.execute(sql, params));
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
export interface PowerSyncCredentials {
endpoint: string;
token: string;
userID?: string;
expiresAt?: Date;
}
Original file line number Diff line number Diff line change
Expand Up @@ -163,10 +163,6 @@ export class SqliteBucketStorage implements BucketStorageAdapter {
*/
private async updateObjectsFromBuckets(checkpoint: Checkpoint) {
return this.writeTransaction(async (tx) => {
/**
* It's best to execute this on the same thread
* https://github.com/journeyapps/powersync-sqlite-core/blob/40554dc0e71864fe74a0cb00f1e8ca4e328ff411/crates/sqlite/sqlite/sqlite3.h#L2578
*/
const { insertId: result } = await tx.execute('INSERT INTO powersync_operations(op, data) VALUES(?, ?)', [
'sync_local',
''
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ export abstract class AbstractRemote {
const credentials = await this.getCredentials();
return {
'content-type': 'application/json',
'User-Id': credentials.userID ?? '',
Authorization: `Token ${credentials.token}}`
};
}
Expand Down
10 changes: 9 additions & 1 deletion packages/powersync-sdk-react-native/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# PowerSync SDK for React Native

[PowerSync](https://powersync.co) is a service and set of SDKs that keeps Postgres databases in sync with on-device SQLite databases. See a summary of features [here](https://docs.powersync.co/resources/api-reference#react-native-and-expo).
[PowerSync](https://powersync.co) is a service and set of SDKs that keeps Postgres databases in sync with on-device SQLite databases. See a summary of features [here](https://docs.powersync.co/client-sdk-references/react-native-and-expo).

## Beta Release
This React Native SDK package is currently in a beta release.
Expand Down Expand Up @@ -178,5 +178,13 @@ Uncomment the following from
// });
// client.addPlugin(networkFlipperPlugin);
```

Disable the dev client network inspector
`android/gradle.properties`
```
# Enable network inspector
EX_DEV_CLIENT_NETWORK_INSPECTOR=false
```

## iOS
Testing offline mode on an iOS simulator by disabling the host machine's entire internet connection will cause the device to remain offline even after the internet connection has been restored. This issue seems to affect all network requests in an application.
Loading

0 comments on commit 4d22c13

Please sign in to comment.