diff --git a/packages/powersync_core/lib/powersync_core.dart b/packages/powersync_core/lib/powersync_core.dart index d931d4f4..54e67156 100644 --- a/packages/powersync_core/lib/powersync_core.dart +++ b/packages/powersync_core/lib/powersync_core.dart @@ -9,7 +9,6 @@ export 'src/database/powersync_database.dart'; export 'src/exceptions.dart'; export 'src/log.dart'; export 'src/open_factory.dart'; -export 'src/powersync_database.dart'; export 'src/schema.dart'; export 'src/sync_status.dart'; export 'src/uuid.dart'; diff --git a/packages/powersync_core/lib/src/database/core_version.dart b/packages/powersync_core/lib/src/database/core_version.dart new file mode 100644 index 00000000..b0d5ff1c --- /dev/null +++ b/packages/powersync_core/lib/src/database/core_version.dart @@ -0,0 +1,68 @@ +import 'package:sqlite_async/sqlite3_common.dart'; + +/// A parsed (major, minor, patch) version triple representing a version of the +/// loaded core extension. +extension type const PowerSyncCoreVersion((int, int, int) _tuple) { + int get major => _tuple.$1; + int get minor => _tuple.$2; + int get patch => _tuple.$3; + + int compareTo(PowerSyncCoreVersion other) { + return switch (major.compareTo(other.major)) { + 0 => switch (minor.compareTo(other.minor)) { + 0 => patch.compareTo(other.patch), + var other => other, + }, + var other => other, + }; + } + + bool operator <(PowerSyncCoreVersion other) => compareTo(other) < 0; + bool operator >=(PowerSyncCoreVersion other) => compareTo(other) >= 0; + + String get versionString => '$major.$minor.$patch'; + + void checkSupported() { + const isWeb = bool.fromEnvironment('dart.library.js_interop'); + + if (this < minimum || this >= maximumExclusive) { + var message = + 'Unsupported powersync extension version. This version of the ' + 'PowerSync SDK needs >=${minimum.versionString} ' + '<${maximumExclusive.versionString}, ' + 'but detected version $versionString.'; + if (isWeb) { + message += + '\nTry downloading the updated assets: https://docs.powersync.com/client-sdk-references/flutter/flutter-web-support#assets'; + } + + throw SqliteException(1, message); + } + } + + /// Parses the output of `powersync_rs_version()`, e.g. `0.3.9/5d64f366`, into + /// a [PowerSyncCoreVersion]. + static PowerSyncCoreVersion parse(String version) { + try { + final [major, minor, patch] = + version.split(RegExp(r'[./]')).take(3).map(int.parse).toList(); + + return PowerSyncCoreVersion((major, minor, patch)); + } catch (e) { + throw SqliteException(1, + 'Unsupported powersync extension version. Need >=0.2.0 <1.0.0, got: $version. Details: $e'); + } + } + + /// The minimum version of the sqlite core extensions we support. We check + /// this version when opening databases to fail early and with an actionable + /// error message. + // Note: When updating this, also update the download URL in + // scripts/init_powersync_core_binary.dart and the version ref in + // packages/sqlite3_wasm_build/build.sh + static const minimum = PowerSyncCoreVersion((0, 3, 11)); + + /// The first version of the core extensions that this version of the Dart + /// SDK doesn't support. + static const maximumExclusive = PowerSyncCoreVersion((1, 0, 0)); +} diff --git a/packages/powersync_core/lib/src/database/powersync_db_mixin.dart b/packages/powersync_core/lib/src/database/powersync_db_mixin.dart index 564e8f5a..6add250b 100644 --- a/packages/powersync_core/lib/src/database/powersync_db_mixin.dart +++ b/packages/powersync_core/lib/src/database/powersync_db_mixin.dart @@ -7,6 +7,7 @@ import 'package:powersync_core/sqlite_async.dart'; import 'package:powersync_core/src/abort_controller.dart'; import 'package:powersync_core/src/connector.dart'; import 'package:powersync_core/src/crud.dart'; +import 'package:powersync_core/src/database/core_version.dart'; import 'package:powersync_core/src/powersync_update_notification.dart'; import 'package:powersync_core/src/schema.dart'; import 'package:powersync_core/src/schema_logic.dart'; @@ -93,23 +94,7 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection { 1, 'The powersync extension is not loaded correctly. Details: $e'); } - // Parse version - List versionInts; - try { - versionInts = - version.split(RegExp(r'[./]')).take(3).map(int.parse).toList(); - } catch (e) { - throw SqliteException(1, - 'Unsupported powersync extension version. Need >=0.2.0 <1.0.0, got: $version. Details: $e'); - } - - // Validate >=0.2.0 <1.0.0 - if (versionInts[0] != 0 || - (versionInts[1] < 2) || - (versionInts[1] == 2 && versionInts[2] < 0)) { - throw SqliteException(1, - 'Unsupported powersync extension version. Need >=0.2.0 <1.0.0, got: $version'); - } + PowerSyncCoreVersion.parse(version).checkSupported(); } /// Wait for initialization to complete. diff --git a/packages/powersync_core/lib/src/powersync_database.dart b/packages/powersync_core/lib/src/powersync_database.dart deleted file mode 100644 index 144a7df4..00000000 --- a/packages/powersync_core/lib/src/powersync_database.dart +++ /dev/null @@ -1 +0,0 @@ -export 'package:powersync_core/src/database/powersync_database.dart'; diff --git a/packages/powersync_core/test/database/core_version_test.dart b/packages/powersync_core/test/database/core_version_test.dart new file mode 100644 index 00000000..84827189 --- /dev/null +++ b/packages/powersync_core/test/database/core_version_test.dart @@ -0,0 +1,39 @@ +import 'package:powersync_core/src/database/core_version.dart'; +import 'package:sqlite3/common.dart'; +import 'package:test/test.dart'; + +void main() { + group('PowerSyncCoreVersion', () { + test('parse', () { + expect(PowerSyncCoreVersion.parse('0.3.9/5d64f366'), (0, 3, 9)); + }); + + test('compare', () { + void expectLess(String a, String b) { + final parsedA = PowerSyncCoreVersion.parse(a); + final parsedB = PowerSyncCoreVersion.parse(b); + + expect(parsedA.compareTo(parsedB), -1); + expect(parsedB.compareTo(parsedA), 1); + + expect(parsedA.compareTo(parsedA), 0); + expect(parsedB.compareTo(parsedB), 0); + } + + expectLess('0.1.0', '1.0.0'); + expectLess('1.0.0', '1.2.0'); + expectLess('0.3.9', '0.3.11'); + }); + + test('checkSupported', () { + expect(PowerSyncCoreVersion.parse('0.3.10').checkSupported, + throwsA(isA())); + expect(PowerSyncCoreVersion.parse('1.0.0').checkSupported, + throwsA(isA())); + + PowerSyncCoreVersion.minimum.checkSupported(); + expect(PowerSyncCoreVersion.maximumExclusive.checkSupported, + throwsA(isA())); + }); + }); +} diff --git a/packages/sqlite3_wasm_build/build.sh b/packages/sqlite3_wasm_build/build.sh index 86a97c1f..4c2e8d24 100755 --- a/packages/sqlite3_wasm_build/build.sh +++ b/packages/sqlite3_wasm_build/build.sh @@ -2,7 +2,7 @@ set -e SQLITE_VERSION="2.7.4" -POWERSYNC_CORE_VERSION="0.3.10" +POWERSYNC_CORE_VERSION="0.3.11" SQLITE_PATH="sqlite3.dart" if [ -d "$SQLITE_PATH" ]; then diff --git a/scripts/init_powersync_core_binary.dart b/scripts/init_powersync_core_binary.dart index c49a3821..030ce9ed 100644 --- a/scripts/init_powersync_core_binary.dart +++ b/scripts/init_powersync_core_binary.dart @@ -6,7 +6,7 @@ import 'dart:io'; import 'package:melos/melos.dart'; final sqliteUrl = - 'https://github.com/powersync-ja/powersync-sqlite-core/releases/download/v0.3.10'; + 'https://github.com/powersync-ja/powersync-sqlite-core/releases/download/v0.3.11'; void main() async { final sqliteCoreFilename = getLibraryForPlatform();