Skip to content

Commit acd3960

Browse files
committed
Add crudThrottleTime to create a throttled update notification stream for sync worker
1 parent 6e61c38 commit acd3960

File tree

8 files changed

+87
-24
lines changed

8 files changed

+87
-24
lines changed

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,4 @@ jobs:
3636
tag="${{ github.ref_name }}"
3737
body="Release $tag"
3838
gh release create --draft "$tag" --title "$tag" --notes "$body" --generate-notes
39-
gh release upload "${{ github.ref_name }}" packages/powersync/assets/powersync_db.worker.js
39+
gh release upload "${{ github.ref_name }}" packages/powersync/assets/powersync_db.worker.js packages/powersync/assets/powersync_sync.worker.js

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ assets/*
1919
powersync_db.worker.js
2020
powersync_db.worker.js*
2121
sqlite3.wasm
22+
powersync_sync.worker.js
23+
powersync_sync.worker.js*
2224

2325
#Core binaries
2426
*.dylib

packages/powersync/bin/setup_web.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ void main(List<String> arguments) async {
2525
final wasmPath = '${root.toFilePath()}$outputDir/sqlite3.wasm';
2626

2727
final workerPath = '${root.toFilePath()}$outputDir/powersync_db.worker.js';
28+
final syncWorkerPath =
29+
'${root.toFilePath()}$outputDir/powersync_sync.worker.js';
2830

2931
final packageConfigFile = File.fromUri(
3032
root.resolve('.dart_tool/package_config.json'),
@@ -57,7 +59,11 @@ void main(List<String> arguments) async {
5759
final workerUrl =
5860
'https://github.com/powersync-ja/powersync.dart/releases/download/powersync-v$powersyncVersion/powersync_db.worker.js';
5961

62+
final syncWorkerUrl =
63+
'https://github.com/powersync-ja/powersync.dart/releases/download/powersync-v$powersyncVersion/powersync_sync.worker.js';
64+
6065
await downloadFile(httpClient, workerUrl, workerPath);
66+
await downloadFile(httpClient, syncWorkerUrl, syncWorkerPath);
6167
}
6268

6369
final sqlitePackageName = 'sqlite3';

packages/powersync/lib/src/database/web/web_powersync_database.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ class PowerSyncDatabaseImpl
150150
sync = await SyncWorkerHandle.start(
151151
this,
152152
connector,
153+
crudThrottleTime.inMilliseconds,
153154
Uri.base.resolve('/powersync_sync.worker.js'),
154155
);
155156
} catch (e) {

packages/powersync/lib/src/web/sync_controller.dart

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,14 @@ import 'sync_worker_protocol.dart';
1212
class SyncWorkerHandle implements StreamingSync {
1313
final PowerSyncDatabaseImpl _database;
1414
final PowerSyncBackendConnector _connector;
15+
final int _crudThrottleTimeMs;
1516

1617
late final WorkerCommunicationChannel _channel;
1718

1819
final StreamController<SyncStatus> _status = StreamController.broadcast();
1920

20-
SyncWorkerHandle._(this._database, this._connector, MessagePort sendToWorker,
21-
SharedWorker worker) {
21+
SyncWorkerHandle._(this._database, this._connector, this._crudThrottleTimeMs,
22+
MessagePort sendToWorker, SharedWorker worker) {
2223
_channel = WorkerCommunicationChannel(
2324
port: sendToWorker,
2425
errors: EventStreamProviders.errorEvent.forTarget(worker),
@@ -69,10 +70,14 @@ class SyncWorkerHandle implements StreamingSync {
6970
});
7071
}
7172

72-
static Future<SyncWorkerHandle> start(PowerSyncDatabaseImpl database,
73-
PowerSyncBackendConnector connector, Uri workerUri) async {
73+
static Future<SyncWorkerHandle> start(
74+
PowerSyncDatabaseImpl database,
75+
PowerSyncBackendConnector connector,
76+
int crudThrottleTimeMs,
77+
Uri workerUri) async {
7478
final worker = SharedWorker(workerUri.toString().toJS);
75-
final handle = SyncWorkerHandle._(database, connector, worker.port, worker);
79+
final handle = SyncWorkerHandle._(
80+
database, connector, crudThrottleTimeMs, worker.port, worker);
7681

7782
// Make sure that the worker is working, or throw immediately.
7883
await handle._channel.ping();
@@ -95,6 +100,7 @@ class SyncWorkerHandle implements StreamingSync {
95100

96101
@override
97102
Future<void> streamingSync() async {
98-
await _channel.startSynchronization(_database.openFactory.path);
103+
await _channel.startSynchronization(
104+
_database.openFactory.path, _crudThrottleTimeMs);
99105
}
100106
}

packages/powersync/lib/src/web/sync_worker.dart

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/// This file needs to be compiled to JavaScript with the command
2-
/// dart compile js -O4 packages/powersync/lib/src/web/sync_worker.worker.dart -o assets/db_worker.js
2+
/// dart compile js -O4 packages/powersync/lib/src/web/sync_worker.dart -o assets/powersync_sync.worker.js
33
/// The output should then be included in each project's `web` directory
44
library;
55

@@ -9,12 +9,13 @@ import 'dart:js_interop';
99
import 'package:async/async.dart';
1010
import 'package:fetch_client/fetch_client.dart';
1111
import 'package:powersync/powersync.dart';
12+
import 'package:powersync/sqlite_async.dart';
13+
import 'package:powersync/src/database/powersync_db_mixin.dart';
1214
import 'package:powersync/src/streaming_sync.dart';
1315
import 'package:sqlite_async/web.dart';
1416
import 'package:web/web.dart' hide RequestMode;
1517

1618
import '../bucket_storage.dart';
17-
import '../database/powersync_db_mixin.dart';
1819
import 'sync_worker_protocol.dart';
1920

2021
final _logger = autoLogger;
@@ -40,10 +41,10 @@ class _SyncWorker {
4041
});
4142
}
4243

43-
_SyncRunner referenceSyncTask(
44-
String databaseIdentifier, _ConnectedClient client) {
44+
_SyncRunner referenceSyncTask(String databaseIdentifier,
45+
int crudThrottleTimeMs, _ConnectedClient client) {
4546
return _requestedSyncTasks.putIfAbsent(databaseIdentifier, () {
46-
return _SyncRunner(databaseIdentifier);
47+
return _SyncRunner(databaseIdentifier, crudThrottleTimeMs);
4748
})
4849
..registerClient(client);
4950
}
@@ -63,7 +64,8 @@ class _ConnectedClient {
6364
switch (type) {
6465
case SyncWorkerMessageType.startSynchronization:
6566
final request = payload as StartSynchronization;
66-
_runner = _worker.referenceSyncTask(request.databaseName, this);
67+
_runner = _worker.referenceSyncTask(
68+
request.databaseName, request.crudThrottleTimeMs, this);
6769
return (JSObject(), null);
6870
case SyncWorkerMessageType.abortSynchronization:
6971
_runner?.unregisterClient(this);
@@ -103,6 +105,7 @@ class _ConnectedClient {
103105

104106
class _SyncRunner {
105107
final String identifier;
108+
final int crudThrottleTimeMs;
106109

107110
final StreamGroup<_RunnerEvent> _group = StreamGroup();
108111
final StreamController<_RunnerEvent> _mainEvents = StreamController();
@@ -111,7 +114,7 @@ class _SyncRunner {
111114
_ConnectedClient? databaseHost;
112115
final connections = <_ConnectedClient>[];
113116

114-
_SyncRunner(this.identifier) {
117+
_SyncRunner(this.identifier, this.crudThrottleTimeMs) {
115118
_group.add(_mainEvents.stream);
116119

117120
Future(() async {
@@ -209,13 +212,26 @@ class _SyncRunner {
209212
}
210213
});
211214

215+
final tables = ['ps_crud'];
216+
final crudThrottleTime = Duration(milliseconds: crudThrottleTimeMs);
217+
Stream<UpdateNotification> crudStream =
218+
powerSyncUpdateNotifications(Stream.empty());
219+
if (database.updates != null) {
220+
final filteredStream = database.updates!
221+
.transform(UpdateNotification.filterTablesTransformer(tables));
222+
crudStream = UpdateNotification.throttleStream(
223+
filteredStream,
224+
crudThrottleTime,
225+
addOne: UpdateNotification.empty(),
226+
);
227+
}
228+
212229
sync = StreamingSyncImplementation(
213230
adapter: BucketStorage(database),
214231
credentialsCallback: client.channel.credentialsCallback,
215232
invalidCredentialsCallback: client.channel.invalidCredentialsCallback,
216233
uploadCrud: client.channel.uploadCrud,
217-
updateStream: powerSyncUpdateNotifications(
218-
database.updates ?? const Stream.empty()),
234+
crudUpdateTriggerStream: crudStream,
219235
retryDelay: Duration(seconds: 3),
220236
client: FetchClient(mode: RequestMode.cors),
221237
identifier: identifier,

packages/powersync/lib/src/web/sync_worker_protocol.dart

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,13 @@ extension type SyncWorkerMessage._(JSObject _) implements JSObject {
6262
extension type StartSynchronization._(JSObject _) implements JSObject {
6363
external factory StartSynchronization({
6464
required String databaseName,
65+
required int crudThrottleTimeMs,
6566
required int requestId,
6667
});
6768

6869
external String get databaseName;
6970
external int get requestId;
71+
external int get crudThrottleTimeMs;
7072
}
7173

7274
@anonymous
@@ -313,11 +315,15 @@ final class WorkerCommunicationChannel {
313315
await _numericRequest(SyncWorkerMessageType.ping);
314316
}
315317

316-
Future<void> startSynchronization(String databaseName) async {
318+
Future<void> startSynchronization(
319+
String databaseName, int crudThrottleTimeMs) async {
317320
final (id, completion) = _newRequest();
318321
port.postMessage(SyncWorkerMessage(
319322
type: SyncWorkerMessageType.startSynchronization.name,
320-
payload: StartSynchronization(databaseName: databaseName, requestId: id),
323+
payload: StartSynchronization(
324+
databaseName: databaseName,
325+
crudThrottleTimeMs: crudThrottleTimeMs,
326+
requestId: id),
321327
));
322328
await completion;
323329
}

scripts/compile_webworker.dart

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,27 +9,52 @@ Future<void> main() async {
99

1010
/// The monorepo root assets directory
1111
final workerFilename = 'powersync_db.worker.js';
12-
final outputPath =
12+
final dbWorkerOutputPath =
1313
path.join(repoRoot, 'packages/powersync/assets/$workerFilename');
1414

1515
final workerSourcePath = path.join(
1616
repoRoot, './packages/powersync/lib/src/web/powersync_db.worker.dart');
1717

1818
// And compile worker code
19-
final process = await Process.run(
19+
final dbWorkerProcess = await Process.run(
2020
Platform.executable,
2121
[
2222
'compile',
2323
'js',
2424
'-o',
25-
outputPath,
25+
dbWorkerOutputPath,
2626
'-O4',
2727
workerSourcePath,
2828
],
2929
workingDirectory: cwd);
3030

31-
if (process.exitCode != 0) {
32-
throw Exception('Could not compile worker: ${process.stdout.toString()}');
31+
if (dbWorkerProcess.exitCode != 0) {
32+
throw Exception(
33+
'Could not compile db worker: ${dbWorkerProcess.stdout.toString()}');
34+
}
35+
36+
final syncWorkerFilename = 'powersync_sync.worker.js';
37+
final syncWorkerOutputPath =
38+
path.join(repoRoot, 'packages/powersync/assets/$syncWorkerFilename');
39+
40+
final syncWorkerSourcePath =
41+
path.join(repoRoot, './packages/powersync/lib/src/web/sync_worker.dart');
42+
43+
final syncWorkerProcess = await Process.run(
44+
Platform.executable,
45+
[
46+
'compile',
47+
'js',
48+
'-o',
49+
syncWorkerOutputPath,
50+
'-O4',
51+
syncWorkerSourcePath,
52+
],
53+
workingDirectory: cwd);
54+
55+
if (syncWorkerProcess.exitCode != 0) {
56+
throw Exception(
57+
'Could not compile sync worker: ${dbWorkerProcess.stdout.toString()}');
3358
}
3459

3560
// Copy this to all demo apps web folders
@@ -44,6 +69,7 @@ Future<void> main() async {
4469
continue;
4570
}
4671
final demoOutputPath = path.join(demoWebDir, workerFilename);
47-
File(outputPath).copySync(demoOutputPath);
72+
File(dbWorkerOutputPath).copySync(demoOutputPath);
73+
File(syncWorkerOutputPath).copySync(demoOutputPath);
4874
}
4975
}

0 commit comments

Comments
 (0)