Skip to content
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
2e9df4b
improve queue behaviour
stevensJourney May 22, 2025
c4eccb8
cleanup
stevensJourney May 22, 2025
5ef22b1
simplify logic
stevensJourney May 22, 2025
f5df9dc
cleanup logic
stevensJourney May 26, 2025
1d155d9
Merge branch 'main' into connection-dequeue
stevensJourney May 26, 2025
be6b2b4
cleanup
stevensJourney May 26, 2025
828fae9
cleanup
stevensJourney May 26, 2025
8a911b7
Update packages/web/tests/stream.test.ts
stevensJourney May 26, 2025
601b484
cleanup common rollup
stevensJourney May 26, 2025
b234bca
cleanup listener
stevensJourney May 26, 2025
308bef4
cleanup
stevensJourney May 26, 2025
e561dda
use shared ConnectionManager in order to queue connections for multip…
stevensJourney May 28, 2025
4e4350f
cleanup connection manager
stevensJourney May 28, 2025
57289b7
remove only call
stevensJourney May 28, 2025
3d57e28
fix upload test
stevensJourney May 28, 2025
5074bb7
Merge branch 'main' into connection-dequeue
stevensJourney May 28, 2025
a0dfaf6
test timeout on ci
stevensJourney May 28, 2025
fe2bba9
stability improvements
stevensJourney May 28, 2025
9d37979
Fix reconnect bugs
stevensJourney May 29, 2025
bf881fc
code cleanup
stevensJourney May 29, 2025
b054bf1
Add code comments
stevensJourney May 29, 2025
5f62829
fix react native ios reconnect
stevensJourney May 29, 2025
eb4ef3b
Merge branch 'main' into connection-dequeue
stevensJourney May 29, 2025
5419661
fix typo
stevensJourney May 29, 2025
c165795
add logger to RN demo
stevensJourney May 29, 2025
b495ca7
fix multiple tab connection contention issue
stevensJourney Jun 2, 2025
73ce560
Merge branch 'main' into connection-dequeue
stevensJourney Jun 2, 2025
7659533
improve retry delay logic. Update diagnostics app error detection.
stevensJourney Jun 4, 2025
13a58e4
Report Sync errors in all pages. Fix bug where changes in sync errors…
stevensJourney Jun 4, 2025
641c9b4
update changeset
stevensJourney Jun 4, 2025
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/cyan-penguins-smell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@powersync/react-native': minor
---

Fixed issue where iOS WebSockets could fail to reconnect after a connection issue.
8 changes: 8 additions & 0 deletions .changeset/empty-pants-give.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@powersync/react-native': minor
'@powersync/common': minor
'@powersync/node': minor
'@powersync/web': minor
---

Improved behaviour when connect is called multiple times in quick succession. Updating client parameters should now be more responsive.
5 changes: 5 additions & 0 deletions .changeset/fuzzy-beers-occur.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@powersync/diagnostics-app': patch
---

Fix bug where clicking signOut would not disconnect from the PowerSync service. Updated implementation to fetch sync errors from the SyncStatus.
5 changes: 5 additions & 0 deletions .changeset/fuzzy-ties-double.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@powersync/common': patch
---

Fixed bug where changes in SyncStatus downloadError and uploadError might not be reported.
11 changes: 7 additions & 4 deletions demos/react-native-supabase-todolist/library/powersync/system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@ import React from 'react';
import { SupabaseStorageAdapter } from '../storage/SupabaseStorageAdapter';

import { type AttachmentRecord } from '@powersync/attachments';
import { configureFts } from '../fts/fts_setup';
import { KVStorage } from '../storage/KVStorage';
import { AppConfig } from '../supabase/AppConfig';
import { SupabaseConnector } from '../supabase/SupabaseConnector';
import { AppSchema } from './AppSchema';
import { PhotoAttachmentQueue } from './PhotoAttachmentQueue';
import { configureFts } from '../fts/fts_setup';

const logger = createBaseLogger();
logger.useDefaults();
logger.setLevel(LogLevel.INFO);
logger.setLevel(LogLevel.DEBUG);

export class System {
kvStorage: KVStorage;
Expand All @@ -31,19 +31,22 @@ export class System {
schema: AppSchema,
database: {
dbFilename: 'sqlite.db'
}
},
logger
});
/**
* The snippet below uses OP-SQLite as the default database adapter.
* You will have to uninstall `@journeyapps/react-native-quick-sqlite` and
* install both `@powersync/op-sqlite` and `@op-engineering/op-sqlite` to use this.
*
* ```typescript
* import { OPSqliteOpenFactory } from '@powersync/op-sqlite'; // Add this import
*
* const factory = new OPSqliteOpenFactory({
* dbFilename: 'sqlite.db'
* dbFilename: 'sqlite.db'
* });
* this.powersync = new PowerSyncDatabase({ database: factory, schema: AppSchema });
* ```
*/

if (AppConfig.supabaseBucket) {
Expand Down
9 changes: 6 additions & 3 deletions packages/common/rollup.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ import json from '@rollup/plugin-json';
import nodeResolve from '@rollup/plugin-node-resolve';
import terser from '@rollup/plugin-terser';

/**
* @returns {import('rollup').RollupOptions}
*/
export default (commandLineArgs) => {
const sourcemap = (commandLineArgs.sourceMap || 'true') == 'true';
const sourceMap = (commandLineArgs.sourceMap || 'true') == 'true';

// Clears rollup CLI warning https://github.com/rollup/rollup/issues/2694
delete commandLineArgs.sourceMap;
Expand All @@ -15,7 +18,7 @@ export default (commandLineArgs) => {
output: {
file: 'dist/bundle.mjs',
format: 'esm',
sourcemap: sourcemap
sourcemap: sourceMap
},
plugins: [
json(),
Expand All @@ -27,7 +30,7 @@ export default (commandLineArgs) => {
// Used by can-ndjson-stream
TextDecoder: ['text-encoding', 'TextDecoder']
}),
terser()
terser({ sourceMap })
],
// This makes life easier
external: [
Expand Down
81 changes: 47 additions & 34 deletions packages/common/src/client/AbstractPowerSyncDatabase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ import {
UpdateNotification,
isBatchedUpdateNotification
} from '../db/DBAdapter.js';
import { FULL_SYNC_PRIORITY } from '../db/crud/SyncProgress.js';
import { SyncPriorityStatus, SyncStatus } from '../db/crud/SyncStatus.js';
import { UploadQueueStats } from '../db/crud/UploadQueueStatus.js';
import { Schema } from '../db/schema/Schema.js';
import { BaseObserver } from '../utils/BaseObserver.js';
import { ControlledExecutor } from '../utils/ControlledExecutor.js';
import { mutexRunExclusive } from '../utils/mutex.js';
import { throttleTrailing } from '../utils/async.js';
import { mutexRunExclusive } from '../utils/mutex.js';
import { ConnectionManager } from './ConnectionManager.js';
import { SQLOpenFactory, SQLOpenOptions, isDBAdapter, isSQLOpenFactory, isSQLOpenOptions } from './SQLOpenFactory.js';
import { PowerSyncBackendConnector } from './connection/PowerSyncBackendConnector.js';
import { runOnSchemaChange } from './runOnSchemaChange.js';
Expand All @@ -32,7 +34,6 @@ import {
type PowerSyncConnectionOptions,
type RequiredAdditionalConnectionOptions
} from './sync/stream/AbstractStreamingSyncImplementation.js';
import { FULL_SYNC_PRIORITY } from '../db/crud/SyncProgress.js';

export interface DisconnectAndClearOptions {
/** When set to false, data in local-only tables is preserved. */
Expand Down Expand Up @@ -165,17 +166,22 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
*/
currentStatus: SyncStatus;

syncStreamImplementation?: StreamingSyncImplementation;
sdkVersion: string;

protected bucketStorageAdapter: BucketStorageAdapter;
private syncStatusListenerDisposer?: () => void;
protected _isReadyPromise: Promise<void>;
protected connectionManager: ConnectionManager;

get syncStreamImplementation() {
return this.connectionManager.syncStreamImplementation;
}

protected _schema: Schema;

private _database: DBAdapter;

protected runExclusiveMutex: Mutex;

constructor(options: PowerSyncDatabaseOptionsWithDBAdapter);
constructor(options: PowerSyncDatabaseOptionsWithOpenFactory);
constructor(options: PowerSyncDatabaseOptionsWithSettings);
Expand Down Expand Up @@ -206,7 +212,33 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
this._schema = schema;
this.ready = false;
this.sdkVersion = '';
this.runExclusiveMutex = new Mutex();
// Start async init
this.connectionManager = new ConnectionManager({
createSyncImplementation: async (connector, options) => {
await this.waitForReady();

return this.runExclusive(async () => {
const sync = this.generateSyncStreamImplementation(connector, this.resolvedConnectionOptions(options));
const onDispose = sync.registerListener({
statusChanged: (status) => {
this.currentStatus = new SyncStatus({
...status.toJSON(),
hasSynced: this.currentStatus?.hasSynced || !!status.lastSyncedAt
});
this.iterateListeners((cb) => cb.statusChanged?.(this.currentStatus));
}
});
await sync.waitForReady();

return {
sync,
onDispose
};
});
},
logger: this.logger
});
this._isReadyPromise = this.initialize();
}

Expand Down Expand Up @@ -425,34 +457,19 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
};
}

/**
* Locking mechanism for exclusively running critical portions of connect/disconnect operations.
* Locking here is mostly only important on web for multiple tab scenarios.
*/
protected runExclusive<T>(callback: () => Promise<T>): Promise<T> {
return this.runExclusiveMutex.runExclusive(callback);
}

/**
* Connects to stream of events from the PowerSync instance.
*/
async connect(connector: PowerSyncBackendConnector, options?: PowerSyncConnectionOptions) {
await this.waitForReady();

// close connection if one is open
await this.disconnect();
if (this.closed) {
throw new Error('Cannot connect using a closed client');
}

const resolvedConnectOptions = this.resolvedConnectionOptions(options);

this.syncStreamImplementation = this.generateSyncStreamImplementation(connector, resolvedConnectOptions);
this.syncStatusListenerDisposer = this.syncStreamImplementation.registerListener({
statusChanged: (status) => {
this.currentStatus = new SyncStatus({
...status.toJSON(),
hasSynced: this.currentStatus?.hasSynced || !!status.lastSyncedAt
});
this.iterateListeners((cb) => cb.statusChanged?.(this.currentStatus));
}
});

await this.syncStreamImplementation.waitForReady();
this.syncStreamImplementation.triggerCrudUpload();
await this.syncStreamImplementation.connect(options);
return this.connectionManager.connect(connector, options);
}

/**
Expand All @@ -461,11 +478,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
* Use {@link connect} to connect again.
*/
async disconnect() {
await this.waitForReady();
await this.syncStreamImplementation?.disconnect();
this.syncStatusListenerDisposer?.();
await this.syncStreamImplementation?.dispose();
this.syncStreamImplementation = undefined;
return this.connectionManager.disconnect();
}

/**
Expand Down Expand Up @@ -512,7 +525,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
await this.disconnect();
}

await this.syncStreamImplementation?.dispose();
await this.connectionManager.close();
await this.database.close();
this.closed = true;
}
Expand Down
Loading
Loading