Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fix] Common SDK fixes for Web and React Native #24

Merged
merged 22 commits into from
Nov 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading