diff --git a/.github/workflows/dev-packages.yml b/.github/workflows/dev-packages.yml
index e63e2fe6..dedef039 100644
--- a/.github/workflows/dev-packages.yml
+++ b/.github/workflows/dev-packages.yml
@@ -24,7 +24,7 @@ jobs:
with:
dotnet-version: "10.0.x"
- - name: Install MAUI Workloads
+ - name: Install native workloads
run: dotnet workload restore
- name: Download PowerSync extension
@@ -33,9 +33,6 @@ jobs:
- name: Restore dependencies
run: dotnet restore
- - name: Install Android SDK for net8.0-android
- run: dotnet build PowerSync/PowerSync.Common -t:InstallAndroidDependencies -f net8.0-android -p:AndroidSdkDirectory="$ANDROID_HOME" -p:AcceptAndroidSdkLicenses=True
-
- name: Extract Common Package Version from CHANGELOG.md
shell: bash
run: |
@@ -50,20 +47,6 @@ jobs:
- name: Run Pack For Common
run: dotnet pack PowerSync/PowerSync.Common -c Release -o ${{ github.workspace }}/output
- - name: Extract MAUI Package Version from CHANGELOG.md
- shell: bash
- run: |
- MAUI_VERSION=$(awk '/^## [0-9]+\.[0-9]+\.[0-9]+-dev(\.[0-9]+)?$/ {print $2; exit}' PowerSync/PowerSync.Maui/CHANGELOG.md)
- if [[ -z "$MAUI_VERSION" ]]; then
- echo "Error: Invalid dev version found in PowerSync.Maui/CHANGELOG.md. Expected format: x.x.x-dev.x"
- exit 1
- fi
- echo "Detected Version: $MAUI_VERSION"
- echo "VERSION=$MAUI_VERSION" >> $GITHUB_ENV
-
- - name: Run Pack For MAUI
- run: dotnet pack PowerSync/PowerSync.Maui -c Release -o ${{ github.workspace }}/output
-
- name: Upload Build Artifacts
uses: actions/upload-artifact@v4
with:
@@ -90,7 +73,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
- dotnet-version: "8.0.x"
+ dotnet-version: "10.0.x"
- name: Install Sign CLI tool
run: dotnet tool install --tool-path . --prerelease sign
@@ -142,7 +125,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
- dotnet-version: "8.0.x"
+ dotnet-version: "10.0.x"
- name: Validate artifact signatures
run: dotnet nuget verify ${{ github.workspace }}/SignedArtifacts/**/*.nupkg
@@ -158,9 +141,3 @@ jobs:
dotnet nuget push "${{ github.workspace }}\SignedArtifacts\PowerSync.Common*.nupkg"
--api-key "${{ steps.login.outputs.NUGET_API_KEY }}"
--source https://api.nuget.org/v3/index.json
-
- - name: Run Push For Maui
- run: >
- dotnet nuget push "${{ github.workspace }}\SignedArtifacts\PowerSync.Maui*.nupkg"
- --api-key "${{ steps.login.outputs.NUGET_API_KEY }}"
- --source https://api.nuget.org/v3/index.json
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 71c0874a..dfd311e4 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -15,7 +15,6 @@ jobs:
outputs:
common_version: ${{ steps.extract_version.outputs.common_version }}
- maui_version: ${{ steps.extract_maui_version.outputs.maui_version }}
steps:
- name: Checkout Repository
@@ -28,7 +27,7 @@ jobs:
with:
dotnet-version: "10.0.x"
- - name: Install MAUI Workloads
+ - name: Install native workloads
run: dotnet workload restore
- name: Download PowerSync extension
@@ -37,9 +36,6 @@ jobs:
- name: Restore dependencies
run: dotnet restore
- - name: Install Android SDK for net8.0-android
- run: dotnet build PowerSync/PowerSync.Common -t:InstallAndroidDependencies -f net8.0-android -p:AndroidSdkDirectory="$ANDROID_HOME" -p:AcceptAndroidSdkLicenses=True
-
- name: Extract Common Package Version from CHANGELOG.md
id: extract_version
shell: bash
@@ -56,22 +52,6 @@ jobs:
- name: Run Pack For Common
run: dotnet pack PowerSync/PowerSync.Common -c Release -o ${{ github.workspace }}/output
- - name: Extract MAUI Package Version from CHANGELOG.md
- id: extract_maui_version
- shell: bash
- run: |
- MAUI_VERSION=$(awk '/^## [0-9]+\.[0-9]+\.[0-9]+/ {print $2; exit}' PowerSync/PowerSync.Maui/CHANGELOG.md)
- if [[ -z "$MAUI_VERSION" ]]; then
- echo "Error: Invalid version found in PowerSync.Maui/CHANGELOG.md. Expected format: '## x.x.x'"
- exit 1
- fi
- echo "Detected Version: $MAUI_VERSION"
- echo "VERSION=$MAUI_VERSION" >> $GITHUB_ENV
- echo "maui_version=$MAUI_VERSION" >> $GITHUB_OUTPUT
-
- - name: Run Pack For MAUI
- run: dotnet pack PowerSync/PowerSync.Maui -c Release -o ${{ github.workspace }}/output
-
- name: Upload Build Artifacts
uses: actions/upload-artifact@v4
with:
@@ -98,7 +78,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
- dotnet-version: "8.0.x"
+ dotnet-version: "10.0.x"
- name: Install Sign CLI tool
run: dotnet tool install --tool-path . --prerelease sign
@@ -150,7 +130,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
- dotnet-version: "8.0.x"
+ dotnet-version: "10.0.x"
- name: Validate artifact signatures
run: dotnet nuget verify ${{ github.workspace }}/SignedArtifacts/**/*.nupkg
@@ -181,24 +161,3 @@ jobs:
--target main \
--generate-notes \
SignedArtifacts/PowerSync.Common*.nupkg
-
- - name: Run Push For Maui
- run: >
- dotnet nuget push "${{ github.workspace }}\SignedArtifacts\PowerSync.Maui*.nupkg"
- --api-key "${{ steps.login.outputs.NUGET_API_KEY }}"
- --source https://api.nuget.org/v3/index.json
-
- - name: Create GitHub Release For Maui
- continue-on-error: true
- shell: bash
- env:
- GH_TOKEN: ${{ github.token }}
- GH_REPO: ${{ github.repository }}
- MAUI_VERSION: ${{ needs.build.outputs.maui_version }}
- run: |
- gh release create "PowerSync.Maui@${MAUI_VERSION}" \
- --draft \
- --title "PowerSync.Maui@${MAUI_VERSION}" \
- --target main \
- --generate-notes \
- SignedArtifacts/PowerSync.Maui*.nupkg
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 110b65f1..a2920b91 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -6,7 +6,7 @@ on:
jobs:
build:
strategy:
- matrix:
+ matrix:
runner: [windows-latest, windows-11-arm]
name: Test Packages ${{ matrix.runner }}
runs-on: ${{ matrix.runner }}
@@ -18,16 +18,16 @@ jobs:
- name: Setup .NET SDK
uses: actions/setup-dotnet@v5
with:
- dotnet-version: '10.0'
+ dotnet-version: "10.0"
- - name: Install MAUI Workloads
+ - name: Install native workloads
run: dotnet workload restore
-
+
- name: Download PowerSync extension
- run: dotnet run --project Tools/Setup
+ run: dotnet run --project Tools/Setup
- name: Restore dependencies
run: dotnet restore
-
+
- name: Run tests
run: dotnet test -v n --framework net10.0
diff --git a/.gitignore b/.gitignore
index 688d8e68..48437c78 100644
--- a/.gitignore
+++ b/.gitignore
@@ -58,13 +58,8 @@ TestResults/
*.db-shm
*.db-wal
-#Core binaries - add rules after ps extension downloading is supported
-*.dylib
-*.dll
-*.so
-*.xcframework
-.env
-*NativeLibs
+# PowerSync runtimes installed by Tools/Setup
+**/runtimes/
# Ignore user id file
user_id.txt
diff --git a/PowerSync/PowerSync.Common/MDSQLite/MDSQLiteAdapter.cs b/PowerSync/PowerSync.Common/MDSQLite/MDSQLiteAdapter.cs
index cdb4ec9e..7fa4d16e 100644
--- a/PowerSync/PowerSync.Common/MDSQLite/MDSQLiteAdapter.cs
+++ b/PowerSync/PowerSync.Common/MDSQLite/MDSQLiteAdapter.cs
@@ -12,6 +12,10 @@ namespace PowerSync.Common.MDSQLite;
using PowerSync.Common.DB;
using PowerSync.Common.Utils;
+#if IOS || MACCATALYST
+using Foundation;
+#endif
+
public class MDSQLiteAdapterOptions()
{
public string Name { get; set; } = null!;
@@ -154,13 +158,25 @@ protected virtual void LoadExtensions(SqliteConnection db)
///
/// Loads the bundled PowerSync core SQLite extension. Override on
- /// platform-specific adapters (e.g. MAUI iOS/Android) where the native library
- /// lives outside the desktop runtime path.
+ /// platform-specific adapters where the native library lives
+ /// outside the standard runtimes path.
///
protected virtual void LoadDefaultPowerSyncExtension(SqliteConnection db)
{
- var path = PowerSyncPathResolver.GetNativeLibraryPath(AppContext.BaseDirectory);
- db.LoadExtension(path, "sqlite3_powersync_init");
+ string extensionPath;
+
+#if IOS || MACCATALYST
+ var bundlePath =
+ NSBundle.FromIdentifier("co.powersync.sqlitecore")?.BundlePath
+ ?? throw new Exception("Could not find PowerSync SQLite extension bundle path");
+ extensionPath = Path.Combine(bundlePath, "powersync-sqlite-core");
+#elif ANDROID
+ extensionPath = "libpowersync";
+#else
+ extensionPath = PowerSyncDesktopPathResolver.GetNativeLibraryPath(AppContext.BaseDirectory);
+#endif
+
+ db.LoadExtension(extensionPath, "sqlite3_powersync_init");
}
public async Task Close()
diff --git a/PowerSync/PowerSync.Common/PowerSync.Common.csproj b/PowerSync/PowerSync.Common/PowerSync.Common.csproj
index 7310aa85..68a2b46f 100644
--- a/PowerSync/PowerSync.Common/PowerSync.Common.csproj
+++ b/PowerSync/PowerSync.Common/PowerSync.Common.csproj
@@ -38,26 +38,40 @@
+
-
-
+
-
-
-
-
+
+
+
+
PreserveNewest
-
-
-
-
+
+
+
+
+ PreserveNewest
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
-
+
diff --git a/PowerSync/PowerSync.Common/PowerSync.Common.targets b/PowerSync/PowerSync.Common/PowerSync.Common.targets
deleted file mode 100644
index 0c5bcba2..00000000
--- a/PowerSync/PowerSync.Common/PowerSync.Common.targets
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/PowerSync/PowerSync.Common/Utils/PowerSyncPathResolver.cs b/PowerSync/PowerSync.Common/Utils/PowerSyncDesktopPathResolver.cs
similarity index 97%
rename from PowerSync/PowerSync.Common/Utils/PowerSyncPathResolver.cs
rename to PowerSync/PowerSync.Common/Utils/PowerSyncDesktopPathResolver.cs
index 8aa7c3a4..f6c07f77 100644
--- a/PowerSync/PowerSync.Common/Utils/PowerSyncPathResolver.cs
+++ b/PowerSync/PowerSync.Common/Utils/PowerSyncDesktopPathResolver.cs
@@ -2,7 +2,7 @@ namespace PowerSync.Common.Utils;
using System.Runtime.InteropServices;
-public static class PowerSyncPathResolver
+public static class PowerSyncDesktopPathResolver
{
public static string GetNativeLibraryPath(string packagePath)
{
diff --git a/PowerSync/PowerSync.Maui/CHANGELOG.md b/PowerSync/PowerSync.Maui/CHANGELOG.md
index 09ce912c..39bffd2f 100644
--- a/PowerSync/PowerSync.Maui/CHANGELOG.md
+++ b/PowerSync/PowerSync.Maui/CHANGELOG.md
@@ -2,8 +2,7 @@
## 1.0.1
-- Full release.
-- Upstream PowerSync.Common version bump (See Powersync.Common changelog 1.0.1 for more information)
+- ⚠️ Deprecate `PowerSync.Maui`. Use `PowerSync.Common` and `MDSQLiteAdapter` / `MDSQLiteDBOpenFactory` instead.
## 1.0.0 (unlisted)
diff --git a/PowerSync/PowerSync.Maui/PowerSync.Maui.csproj b/PowerSync/PowerSync.Maui/PowerSync.Maui.csproj
index 751532b2..a8a4703e 100644
--- a/PowerSync/PowerSync.Maui/PowerSync.Maui.csproj
+++ b/PowerSync/PowerSync.Maui/PowerSync.Maui.csproj
@@ -1,5 +1,4 @@
-
net9.0;net10.0;net9.0-android;net10.0-android
$(TargetFrameworks);net9.0-ios;net10.0-ios;net9.0-maccatalyst;net10.0-maccatalyst
@@ -19,51 +18,15 @@
https://github.com/powersync-ja/powersync-dotnet/PowerSync/PowerSync.Maui/CHANGELOG.md
powersync local-first local-storage state-management offline sql db persistence sqlite sync
icon.png
- NU5100;1591
README.md
- true
- true
-
+
-
- Designer
-
-
-
-
-
-
-
-
- Framework
- False
-
-
-
-
-
-
- Framework
- False
-
-
-
-
-
-
-
-
-
-
diff --git a/PowerSync/PowerSync.Maui/README.md b/PowerSync/PowerSync.Maui/README.md
index bc75882c..af6137a4 100644
--- a/PowerSync/PowerSync.Maui/README.md
+++ b/PowerSync/PowerSync.Maui/README.md
@@ -1,45 +1,3 @@
# PowerSync SDK .NET MAUI
-This package provides .NET Multi-platform App UI (MAUI) integration for PowerSync, designed to work with the PowerSync.Common package for cross-platform mobile and desktop applications.
-
-## ⚠️ Project Status & Release Note
-
-This package is in beta and is considered ready for production use for tested use cases. See our feature status definitions [here](https://docs.powersync.com/resources/feature-status).
-
-## Installation
-
-This package is published on [NuGet](https://www.nuget.org/packages/PowerSync.Maui) and requires PowerSync.Common to also be installed.
-
-```bash
-dotnet add package PowerSync.Maui
-dotnet add package PowerSync.Common
-```
-
-## Usage
-
-Initialization differs slightly from our Common SDK when using MAUI.
-
-```csharp
-
-private record ListResult(string id, string name, string owner_id, string created_at);
-
-static async Task Main() {
-
- // Ensures the DB file is stored in a platform appropriate location
- var dbPath = Path.Combine(FileSystem.AppDataDirectory, "maui-example.db");
- var factory = new MAUISQLiteDBOpenFactory(new MDSQLiteOpenFactoryOptions()
- {
- DbFilename = dbPath
- });
-
- var Db = new PowerSyncDatabase(new PowerSyncDatabaseOptions()
- {
- Database = factory, // Supply a factory
- Schema = AppSchema.PowerSyncSchema,
- });
-
- await db.Init();
-
- var lists = await db.GetAll("select * from lists");
-}
-```
+- ⚠️ This package is deprecated. Use `PowerSync.Common` and `MDSQLiteAdapter` / `MDSQLiteDBOpenFactory` instead.
diff --git a/PowerSync/PowerSync.Maui/SQLite/MAUISQLiteAdapter.cs b/PowerSync/PowerSync.Maui/SQLite/MAUISQLiteAdapter.cs
deleted file mode 100644
index 0d6e3993..00000000
--- a/PowerSync/PowerSync.Maui/SQLite/MAUISQLiteAdapter.cs
+++ /dev/null
@@ -1,55 +0,0 @@
-namespace PowerSync.Maui.SQLite;
-
-using Microsoft.Data.Sqlite;
-
-using PowerSync.Common.MDSQLite;
-
-// iOS/MacCatalyst specific imports
-#if IOS || MACCATALYST
-using Foundation;
-#endif
-
-public class MAUISQLiteAdapter : MDSQLiteAdapter
-{
- public MAUISQLiteAdapter(MDSQLiteAdapterOptions options) : base(options)
- {
- }
-
- // The bundled PowerSync extension lives in a platform-specific location on
- // iOS/MacCatalyst/Android — the desktop runtime path used by the base class
- // does not resolve to it. Override only the PowerSync-extension load hook;
- // user-supplied custom extensions still flow through MDSQLiteAdapter.LoadExtensions
- // unchanged, so consumers can freely combine the bundled extension (via the
- // LoadPowerSyncExtension flag) with their own.
- protected override void LoadDefaultPowerSyncExtension(SqliteConnection db)
- {
-#if IOS || MACCATALYST
- LoadExtensionApple(db);
-#elif ANDROID
- db.LoadExtension("libpowersync");
-#else
- base.LoadDefaultPowerSyncExtension(db);
-#endif
- }
-
- private static void LoadExtensionApple(SqliteConnection db)
- {
-#if IOS || MACCATALYST
- var bundlePath = Foundation.NSBundle.FromIdentifier("co.powersync.sqlitecore")?.BundlePath;
- if (bundlePath == null)
- {
- throw new Exception("Could not find PowerSync SQLite extension bundle path");
- }
-
- var filePath =
- Path.Combine(bundlePath, "powersync-sqlite-core");
-
- using var loadExtension = db.CreateCommand();
- loadExtension.CommandText = "SELECT load_extension(@path, @entryPoint)";
- loadExtension.Parameters.AddWithValue("@path", filePath);
- loadExtension.Parameters.AddWithValue("@entryPoint", "sqlite3_powersync_init");
- loadExtension.ExecuteNonQuery();
-#endif
- }
-}
-
diff --git a/PowerSync/PowerSync.Maui/SQLite/MAUISQLiteDBOpenFactory.cs b/PowerSync/PowerSync.Maui/SQLite/MAUISQLiteDBOpenFactory.cs
deleted file mode 100644
index c8d35088..00000000
--- a/PowerSync/PowerSync.Maui/SQLite/MAUISQLiteDBOpenFactory.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-using PowerSync.Common.DB;
-
-namespace PowerSync.Maui.SQLite;
-
-using PowerSync.Common.Client;
-using PowerSync.Common.MDSQLite;
-
-
-public class MAUISQLiteDBOpenFactory : ISQLOpenFactory
-{
- private readonly MDSQLiteOpenFactoryOptions options;
-
- public MAUISQLiteDBOpenFactory(MDSQLiteOpenFactoryOptions options)
- {
- this.options = options;
- }
-
- public IDBAdapter OpenDatabase()
- {
- return new MAUISQLiteAdapter(new MDSQLiteAdapterOptions
- {
- Name = options.DbFilename,
- SqliteOptions = options.SqliteOptions
- });
- }
-}
diff --git a/PowerSync/PowerSync.Maui/build/ApiDefinition.cs b/PowerSync/PowerSync.Maui/build/ApiDefinition.cs
deleted file mode 100644
index 5fe8ef43..00000000
--- a/PowerSync/PowerSync.Maui/build/ApiDefinition.cs
+++ /dev/null
@@ -1,4 +0,0 @@
-namespace PowerSync.Maui.build
-{
- // Empty API definition - allows xcframework to be included without managed bindings
-}
diff --git a/Tests/PowerSync/PowerSync.Common.Tests/MDSQLite/MDSQLiteAdapterTests.cs b/Tests/PowerSync/PowerSync.Common.Tests/MDSQLite/MDSQLiteAdapterTests.cs
index 41843828..507a173c 100644
--- a/Tests/PowerSync/PowerSync.Common.Tests/MDSQLite/MDSQLiteAdapterTests.cs
+++ b/Tests/PowerSync/PowerSync.Common.Tests/MDSQLite/MDSQLiteAdapterTests.cs
@@ -53,7 +53,7 @@ public async Task DisablingCoreExtensionPreventsPowerSyncFromLoading()
public async Task LoadsCustomPowerSyncExtensionFromOverriddenPath()
{
var dbName = $"MDSQLiteAdapter-{Guid.NewGuid():N}.db";
- var sourcePath = PowerSyncPathResolver.GetNativeLibraryPath(AppContext.BaseDirectory);
+ var sourcePath = PowerSyncDesktopPathResolver.GetNativeLibraryPath(AppContext.BaseDirectory);
var customPath = Path.Combine(
Path.GetTempPath(),
$"powersync-ext-copy-{Guid.NewGuid():N}{Path.GetExtension(sourcePath)}"
diff --git a/Tools/Setup/Setup.cs b/Tools/Setup/Setup.cs
index c91d2ecd..ddca0e46 100644
--- a/Tools/Setup/Setup.cs
+++ b/Tools/Setup/Setup.cs
@@ -1,245 +1,260 @@
-using System;
-using System.IO;
-using System.Net.Http;
-using System.Threading.Tasks;
-using System.Collections.Generic;
-using System.IO.Compression;
-
-///
-/// Execute with `dotnet run --project Tools/Setup`
-///
-public class PowerSyncSetup
-{
- private const string VERSION = "0.5.2";
-
- private const string GITHUB_BASE_URL = $"https://github.com/powersync-ja/powersync-sqlite-core/releases/download/v{VERSION}";
-
- private readonly HttpClient _httpClient;
- private readonly string _basePath;
-
- public PowerSyncSetup()
- {
- _httpClient = new HttpClient();
- _basePath = Path.Combine(AppContext.BaseDirectory, "../../../../..", "PowerSync");
- }
-
- public async Task RunSetup()
- {
- try
- {
- await SetupDesktop();
- await SetupMauiIos();
- await SetupMauiAndroid();
- await SetupMauiMacCatalyst();
- }
- finally
- {
- _httpClient?.Dispose();
- }
- }
-
- public async Task SetupDesktop()
- {
- Console.WriteLine("Setting up Desktop libraries...");
-
- var runtimeConfigs = GetDesktopRuntimeConfigs();
- var commonPath = Path.Combine(_basePath, "PowerSync.Common");
-
- foreach (var config in runtimeConfigs)
- {
- await ProcessDesktopRuntime(commonPath, config);
- }
- }
-
- private static Dictionary GetDesktopRuntimeConfigs()
- {
- return new Dictionary
- {
- { "osx-x64", new RuntimeConfig("libpowersync_x64.macos.dylib", "libpowersync.dylib") },
- { "osx-arm64", new RuntimeConfig("libpowersync_aarch64.macos.dylib", "libpowersync.dylib") },
- { "linux-x64", new RuntimeConfig("libpowersync_x64.linux.so", "libpowersync.so") },
- { "linux-arm64", new RuntimeConfig("libpowersync_aarch64.linux.so", "libpowersync.so") },
- { "win-x64", new RuntimeConfig("powersync_x64.dll", "powersync.dll") },
- { "win-arm64", new RuntimeConfig("powersync_aarch64.dll", "powersync.dll") }
- };
- }
-
- private async Task ProcessDesktopRuntime(string basePath, KeyValuePair runtimeConfig)
- {
- var (rid, config) = runtimeConfig;
- var nativeDir = Path.Combine(basePath, "runtimes", rid, "native");
-
- try
- {
- Directory.CreateDirectory(nativeDir);
-
- var downloadPath = Path.Combine(nativeDir, config.OriginalFileName);
- var finalPath = Path.Combine(nativeDir, config.FinalFileName);
-
- var downloadUrl = $"{GITHUB_BASE_URL}/{config.OriginalFileName}";
-
- await DownloadFile(downloadUrl, downloadPath);
- File.Move(downloadPath, finalPath, overwrite: true);
-
- Console.WriteLine($"✓ {rid}: {config.OriginalFileName} → {config.FinalFileName}");
- }
- catch (Exception ex)
- {
- Console.Error.WriteLine($"✗ Failed to process {rid}: {ex.Message}");
- }
- }
-
- public async Task SetupMauiIos()
- {
- Console.WriteLine("Setting up MAUI iOS libraries...");
-
- var nativeDir = Path.Combine(_basePath, "PowerSync.Maui", "Platforms", "iOS", "NativeLibs");
- var config = new ArchiveConfig(
- "powersync-sqlite-core.xcframework.zip",
- "powersync-sqlite-core.xcframework"
- );
-
- await ProcessArchiveDownload(nativeDir, config, GITHUB_BASE_URL);
- }
-
- public async Task SetupMauiAndroid()
- {
- Console.WriteLine("Setting up MAUI Android libraries...");
-
- var nativeDir = Path.Combine(_basePath, "PowerSync.Maui", "Platforms", "Android", "jniLibs");
-
- try
- {
- Directory.CreateDirectory(nativeDir);
-
- await Task.WhenAll(
- DownloadAndroidLibrary("libpowersync_aarch64.android.so ", nativeDir,"arm64-v8a"),
- DownloadAndroidLibrary("libpowersync_armv7.android.so ", nativeDir, "armeabi-v7a"),
- DownloadAndroidLibrary("libpowersync_x86.android.so ", nativeDir, "x86"),
- DownloadAndroidLibrary("libpowersync_x64.android.so ", nativeDir, "x86_64")
- );
-
- Console.WriteLine($"✓ Android: Downloaded native libraries");
- }
- catch (Exception ex)
- {
- Console.Error.WriteLine($"✗ Failed to setup Android: {ex.Message}");
- }
- }
-
- private async Task DownloadAndroidLibrary(string filename, string jniLibsDir, string arch)
- {
- var targetDir = Path.Combine(jniLibsDir, arch);
- Directory.CreateDirectory(targetDir);
- var targetFile = Path.Combine(targetDir, "libpowersync.so");
- await DownloadFile($"{GITHUB_BASE_URL}/{filename}", targetFile);
- }
-
- public async Task SetupMauiMacCatalyst()
- {
- Console.WriteLine("Setting up MAUI MacCatalyst libraries...");
-
- var nativeDir = Path.Combine(_basePath, "PowerSync.Maui", "Platforms", "MacCatalyst", "NativeLibs");
- var config = new ArchiveConfig(
- "powersync-sqlite-core.xcframework.zip",
- "powersync-sqlite-core.xcframework"
- );
-
- await ProcessArchiveDownload(nativeDir, config, GITHUB_BASE_URL);
- }
-
- private async Task ProcessArchiveDownload(string nativeDir, ArchiveConfig config, string baseUrl)
- {
- try
- {
- Directory.CreateDirectory(nativeDir);
-
- var downloadPath = Path.Combine(nativeDir, config.ArchiveFileName);
- var extractedPath = Path.Combine(nativeDir, config.ExtractedName);
- var downloadUrl = $"{baseUrl}/{config.ArchiveFileName}";
-
- await DownloadFile(downloadUrl, downloadPath);
-
- // Clean up existing extraction
- if (Directory.Exists(extractedPath))
- Directory.Delete(extractedPath, recursive: true);
-
- ExtractZipPreservingSymlinks(downloadPath, nativeDir);
- File.Delete(downloadPath);
-
- Console.WriteLine($"✓ Extracted {config.ArchiveFileName} → {config.ExtractedName}");
- }
- catch (Exception ex)
- {
- Console.Error.WriteLine($"✗ Failed to process archive: {ex.Message}");
- }
- }
-
- private static void ExtractZipPreservingSymlinks(string zipPath, string destDir)
- {
- // ZipFile.ExtractToDirectory does not preserve symlinks, which breaks
- // macOS/Catalyst .xcframework bundles. Use `unzip` on Unix instead.
- if (!OperatingSystem.IsWindows())
- {
- var proc = System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
- {
- FileName = "unzip",
- ArgumentList = { "-o", zipPath, "-d", destDir },
- RedirectStandardOutput = true,
- RedirectStandardError = true,
- })!;
- proc.WaitForExit();
- if (proc.ExitCode != 0)
- throw new Exception($"unzip exited with code {proc.ExitCode}: {proc.StandardError.ReadToEnd()}");
- }
- else
- {
- ZipFile.ExtractToDirectory(zipPath, destDir);
- }
- }
-
- private async Task DownloadFile(string url, string outputPath)
- {
- Console.WriteLine($"📥 Downloading: {Path.GetFileName(outputPath)}");
-
- using var response = await _httpClient.GetAsync(url);
-
- if (!response.IsSuccessStatusCode)
- {
- throw new HttpRequestException($"Download failed: {response.StatusCode} {response.ReasonPhrase}");
- }
-
- await using var fileStream = new FileStream(outputPath, FileMode.Create, FileAccess.Write, FileShare.None);
- await response.Content.CopyToAsync(fileStream);
- }
-
- private static void CleanupPaths(params string[] paths)
- {
- foreach (var path in paths)
- {
- try
- {
- if (File.Exists(path))
- File.Delete(path);
- else if (Directory.Exists(path))
- Directory.Delete(path, recursive: true);
- }
- catch (Exception ex)
- {
- Console.WriteLine($"⚠️ Cleanup warning for {path}: {ex.Message}");
- }
- }
- }
-
- private record RuntimeConfig(string OriginalFileName, string FinalFileName);
- private record ArchiveConfig(string ArchiveFileName, string ExtractedName);
-}
-
-public class Program
-{
- static async Task Main(string[] args)
- {
- var setup = new PowerSyncSetup();
- await setup.RunSetup();
- }
-}
+using System;
+using System.IO;
+using System.Linq;
+using System.Net.Http;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+using System.IO.Compression;
+using System.Xml;
+using System.Xml.Linq;
+
+///
+/// Downloads the powersync-sqlite-core native libraries into PowerSync.Common/runtimes,
+/// laid out using NuGet's RID conventions so a single package can serve every target:
+///
+/// runtimes/{rid}/native/... desktop and Android, resolved by RID
+/// runtimes/ios/native/*.xcframework.zip
+/// runtimes/maccatalyst/native/*.xcframework.zip
+///
+/// The Apple slices stay zipped on purpose. NuGet pack/extract and ordinary file copies
+/// drop symlinks, which corrupts the versioned MacCatalyst framework bundle and makes
+/// codesign fail with "bundle format is ambiguous". The Apple SDK unzips these itself
+/// and embeds the framework into App.app/Frameworks.
+///
+/// Execute with `dotnet run --project Tools/Setup`
+///
+public class PowerSyncSetup
+{
+ private const string VERSION = "0.5.2";
+
+ private const string GITHUB_BASE_URL = $"https://github.com/powersync-ja/powersync-sqlite-core/releases/download/v{VERSION}";
+
+ private const string XCFRAMEWORK_NAME = "powersync-sqlite-core.xcframework";
+
+ private readonly HttpClient _httpClient;
+ private readonly string _commonPath;
+
+ public PowerSyncSetup()
+ {
+ _httpClient = new HttpClient();
+ _commonPath = Path.Combine(AppContext.BaseDirectory, "../../../../..", "PowerSync", "PowerSync.Common");
+ }
+
+ public async Task RunSetup()
+ {
+ try
+ {
+ await SetupRuntimes();
+ await SetupApple();
+ }
+ finally
+ {
+ _httpClient?.Dispose();
+ }
+ }
+
+ ///
+ /// Every target whose native library is a single downloadable file: one entry per RID.
+ ///
+ public async Task SetupRuntimes()
+ {
+ Console.WriteLine("Setting up native libraries...");
+
+ foreach (var config in GetRuntimeConfigs())
+ {
+ await ProcessRuntime(config);
+ }
+ }
+
+ private static Dictionary GetRuntimeConfigs()
+ {
+ return new Dictionary
+ {
+ // Desktop
+ { "osx-x64", new RuntimeConfig("libpowersync_x64.macos.dylib", "libpowersync.dylib") },
+ { "osx-arm64", new RuntimeConfig("libpowersync_aarch64.macos.dylib", "libpowersync.dylib") },
+ { "linux-x64", new RuntimeConfig("libpowersync_x64.linux.so", "libpowersync.so") },
+ { "linux-arm64", new RuntimeConfig("libpowersync_aarch64.linux.so", "libpowersync.so") },
+ { "win-x64", new RuntimeConfig("powersync_x64.dll", "powersync.dll") },
+ { "win-arm64", new RuntimeConfig("powersync_aarch64.dll", "powersync.dll") },
+
+ // Android. These reach the APK's ABI directories via AndroidNativeLibrary items
+ // in PowerSync.Common.csproj (ProjectReference) and NuGet RID resolution (PackageReference).
+ { "android-arm64", new RuntimeConfig("libpowersync_aarch64.android.so", "libpowersync.so") },
+ { "android-x64", new RuntimeConfig("libpowersync_x64.android.so", "libpowersync.so") },
+ { "android-arm", new RuntimeConfig("libpowersync_armv7.android.so", "libpowersync.so") },
+ { "android-x86", new RuntimeConfig("libpowersync_x86.android.so", "libpowersync.so") },
+ };
+ }
+
+ private async Task ProcessRuntime(KeyValuePair runtimeConfig)
+ {
+ var (rid, config) = runtimeConfig;
+ var nativeDir = Path.Combine(_commonPath, "runtimes", rid, "native");
+
+ try
+ {
+ Directory.CreateDirectory(nativeDir);
+
+ var downloadPath = Path.Combine(nativeDir, config.OriginalFileName);
+ var finalPath = Path.Combine(nativeDir, config.FinalFileName);
+
+ var downloadUrl = $"{GITHUB_BASE_URL}/{config.OriginalFileName}";
+
+ await DownloadFile(downloadUrl, downloadPath);
+ File.Move(downloadPath, finalPath, overwrite: true);
+
+ Console.WriteLine($"✓ {rid}: {config.OriginalFileName} → {config.FinalFileName}");
+ }
+ catch (Exception ex)
+ {
+ Console.Error.WriteLine($"✗ Failed to process {rid}: {ex.Message}");
+ }
+ }
+
+ ///
+ /// Apple ships one xcframework covering every Apple platform, so it needs its own path:
+ /// download once, then write a per-platform copy carrying only the slices that platform
+ /// needs. The published archive is ~135MB — it includes tvOS, watchOS and macOS slices we
+ /// do not target, plus a dSYM per slice — which trims down to a few MB.
+ ///
+ public async Task SetupApple()
+ {
+ Console.WriteLine("Setting up Apple libraries...");
+
+ var tempZip = Path.Combine(Path.GetTempPath(), $"{XCFRAMEWORK_NAME}.zip");
+
+ try
+ {
+ await DownloadFile($"{GITHUB_BASE_URL}/{XCFRAMEWORK_NAME}.zip", tempZip);
+
+ WriteTrimmedXcframework(tempZip, "ios", ["ios-arm64", "ios-arm64_x86_64-simulator"]);
+ WriteTrimmedXcframework(tempZip, "maccatalyst", ["ios-arm64_x86_64-maccatalyst"]);
+ }
+ catch (Exception ex)
+ {
+ Console.Error.WriteLine($"✗ Failed to process Apple libraries: {ex.Message}");
+ }
+ finally
+ {
+ if (File.Exists(tempZip)) File.Delete(tempZip);
+ }
+ }
+
+ private void WriteTrimmedXcframework(string sourceZip, string rid, string[] keepSlices)
+ {
+ var nativeDir = Path.Combine(_commonPath, "runtimes", rid, "native");
+ Directory.CreateDirectory(nativeDir);
+
+ var targetZip = Path.Combine(nativeDir, $"{XCFRAMEWORK_NAME}.zip");
+ if (File.Exists(targetZip)) File.Delete(targetZip);
+
+ using var source = ZipFile.OpenRead(sourceZip);
+ using var targetStream = new FileStream(targetZip, FileMode.Create, FileAccess.Write, FileShare.None);
+ using var target = new ZipArchive(targetStream, ZipArchiveMode.Create);
+
+ var infoPlistPath = $"{XCFRAMEWORK_NAME}/Info.plist";
+
+ foreach (var entry in source.Entries)
+ {
+ // Debug symbols are the bulk of the download and are not needed to build against.
+ if (entry.FullName.Contains("/dSYMs/")) continue;
+
+ var slice = GetSliceName(entry.FullName);
+ if (slice != null && !keepSlices.Contains(slice)) continue;
+
+ // Copying the raw bytes plus ExternalAttributes is what preserves symlinks: they
+ // are stored as entries whose content is the link target and whose unix mode bits
+ // live in ExternalAttributes. Extracting and re-zipping would lose them.
+ var copy = target.CreateEntry(entry.FullName, CompressionLevel.Optimal);
+ copy.ExternalAttributes = entry.ExternalAttributes;
+ copy.LastWriteTime = entry.LastWriteTime;
+
+ using var output = copy.Open();
+
+ if (entry.FullName == infoPlistPath)
+ {
+ using var plistStream = entry.Open();
+ using var trimmed = TrimInfoPlist(plistStream, keepSlices);
+ trimmed.CopyTo(output);
+ }
+ else
+ {
+ using var input = entry.Open();
+ input.CopyTo(output);
+ }
+ }
+
+ Console.WriteLine($"✓ {rid}: {XCFRAMEWORK_NAME}.zip ({string.Join(", ", keepSlices)})");
+ }
+
+ ///
+ /// Returns the xcframework slice a zip entry belongs to, or null for entries that sit
+ /// outside a slice (the root Info.plist, LICENSE, README).
+ ///
+ private static string? GetSliceName(string entryPath)
+ {
+ var parts = entryPath.Split('/');
+ if (parts.Length < 3 || parts[0] != XCFRAMEWORK_NAME) return null;
+ return parts[1];
+ }
+
+ ///
+ /// Drops the removed slices from the xcframework's AvailableLibraries list, so the Apple
+ /// SDK does not go looking for slices that are no longer present in the archive.
+ ///
+ private static Stream TrimInfoPlist(Stream plistStream, string[] keepSlices)
+ {
+ // XmlResolver is null so the Apple DTD named by the DOCTYPE is never fetched, while
+ // DtdProcessing.Parse keeps the DOCTYPE itself in the output.
+ var settings = new XmlReaderSettings { DtdProcessing = DtdProcessing.Parse, XmlResolver = null };
+ using var reader = XmlReader.Create(plistStream, settings);
+ var doc = XDocument.Load(reader);
+
+ var librariesArray = doc.Root?.Element("dict")
+ ?.Elements("key")
+ .FirstOrDefault(k => k.Value == "AvailableLibraries")
+ ?.NextNode as XElement;
+
+ librariesArray?.Elements("dict")
+ .Where(d => !keepSlices.Contains(PlistString(d, "LibraryIdentifier")))
+ .Remove();
+
+ var output = new MemoryStream();
+ doc.Save(output);
+ output.Position = 0;
+ return output;
+ }
+
+ private static string? PlistString(XElement dict, string key)
+ {
+ var keyElement = dict.Elements("key").FirstOrDefault(k => k.Value == key);
+ return (keyElement?.NextNode as XElement)?.Value;
+ }
+
+ private async Task DownloadFile(string url, string outputPath)
+ {
+ Console.WriteLine($"📥 Downloading: {Path.GetFileName(outputPath)}");
+
+ using var response = await _httpClient.GetAsync(url);
+
+ if (!response.IsSuccessStatusCode)
+ {
+ throw new HttpRequestException($"Download failed: {response.StatusCode} {response.ReasonPhrase}");
+ }
+
+ await using var fileStream = new FileStream(outputPath, FileMode.Create, FileAccess.Write, FileShare.None);
+ await response.Content.CopyToAsync(fileStream);
+ }
+
+ private record RuntimeConfig(string OriginalFileName, string FinalFileName);
+}
+
+public class Program
+{
+ static async Task Main(string[] args)
+ {
+ var setup = new PowerSyncSetup();
+ await setup.RunSetup();
+ }
+}
diff --git a/demos/MAUITodo/Data/PowerSyncData.cs b/demos/MAUITodo/Data/PowerSyncData.cs
index 676781f8..818e20c6 100644
--- a/demos/MAUITodo/Data/PowerSyncData.cs
+++ b/demos/MAUITodo/Data/PowerSyncData.cs
@@ -8,7 +8,6 @@
using PowerSync.Common.Attachments;
using PowerSync.Common.Client;
using PowerSync.Common.MDSQLite;
-using PowerSync.Maui.SQLite;
namespace MAUITodo.Data;
@@ -29,7 +28,7 @@ public PowerSyncData()
var logger = loggerFactory.CreateLogger("PowerSyncLogger");
var dbPath = Path.Combine(FileSystem.AppDataDirectory, "example.db");
- var factory = new MAUISQLiteDBOpenFactory(new MDSQLiteOpenFactoryOptions()
+ var factory = new MDSQLiteDBOpenFactory(new MDSQLiteOpenFactoryOptions()
{
DbFilename = dbPath
});
diff --git a/demos/MAUITodo/MAUITodo.csproj b/demos/MAUITodo/MAUITodo.csproj
index 9f38150b..ee955424 100644
--- a/demos/MAUITodo/MAUITodo.csproj
+++ b/demos/MAUITodo/MAUITodo.csproj
@@ -1,5 +1,4 @@
-
com.companyname.todo
@@ -64,6 +63,5 @@
-