diff --git a/.github/workflows/dev-packages.yml b/.github/workflows/dev-packages.yml new file mode 100644 index 0000000..a082f82 --- /dev/null +++ b/.github/workflows/dev-packages.yml @@ -0,0 +1,40 @@ +# The version is pulled from the CHANGELOG.md file of the package. +# Add a `-dev.xxx` suffix to the version. +name: Create Dev Release + +on: workflow_dispatch + +jobs: + dev-release: + name: Publish Dev Packages + runs-on: windows-latest + + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Setup .NET SDK + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '8.0' + + - name: Download PowerSync extension + run: dotnet run --project Tools/Setup + + - name: Restore dependencies + run: dotnet restore + + - name: Extract Version from CHANGELOG.md + id: extract_version + shell: bash + run: | + VERSION=$(awk '/^## [0-9]+\.[0-9]+\.[0-9]+-dev(\.[0-9]+)?$/ {print $2; exit}' PowerSync/PowerSync.Common/CHANGELOG.md) + echo "Detected Version: $VERSION" + echo "VERSION=$VERSION" >> $GITHUB_ENV + + - name: Run Pack + run: dotnet pack -c Release -o ${{ github.workspace }}/output + + - name: Run Push + run: dotnet nuget push ${{ github.workspace }}\output\*.nupkg --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_API_KEY }} + \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..cfbff30 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,40 @@ +# The version is pulled from the CHANGELOG.md file of the package. +name: Release + +on: workflow_dispatch + +jobs: + release: + name: Release + runs-on: windows-latest + if: github.ref == 'refs/heads/main' + + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Setup .NET SDK + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '8.0' + + - name: Download PowerSync extension + run: dotnet run --project Tools/Setup + + - name: Restore dependencies + run: dotnet restore + + - name: Extract Version from CHANGELOG.md + id: extract_version + shell: bash + run: | + VERSION=$(awk '/^## [0-9]+\.[0-9]+\.[0-9]+/ {print $2; exit}' PowerSync/PowerSync.Common/CHANGELOG.md) + echo "Detected Version: $VERSION" + echo "VERSION=$VERSION" >> $GITHUB_ENV + + - name: Run Pack + run: dotnet pack -c Release -o ${{ github.workspace }}/output + + - name: Run Push + run: dotnet nuget push ${{ github.workspace }}\output\*.nupkg --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_API_KEY }} + \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..42f1e8f --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,27 @@ +name: Test Packages + +on: + push: + +jobs: + build: + name: Test Packages + runs-on: windows-latest + + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Setup .NET SDK + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '8.0' + + - name: Download PowerSync extension + run: dotnet run --project Tools/Setup + + - name: Restore dependencies + run: dotnet restore + + - name: Run tests + run: dotnet test -v n --framework net8.0 \ No newline at end of file diff --git a/PowerSync/PowerSync.Common/CHANGELOG.md b/PowerSync/PowerSync.Common/CHANGELOG.md new file mode 100644 index 0000000..58e0c7f --- /dev/null +++ b/PowerSync/PowerSync.Common/CHANGELOG.md @@ -0,0 +1,10 @@ +## 0.0.2-alpha.1 + +- Introduce package. Support for Desktop .NET use cases. + +### Platform Runtime Support Added +* linux-arm64 +* linux-x64 +* osx-arm64 +* osx-x64 +* wind-x64 \ No newline at end of file diff --git a/PowerSync/PowerSync.Common/Client/PowerSyncDatabase.cs b/PowerSync/PowerSync.Common/Client/PowerSyncDatabase.cs index 5d28379..d9c693a 100644 --- a/PowerSync/PowerSync.Common/Client/PowerSyncDatabase.cs +++ b/PowerSync/PowerSync.Common/Client/PowerSyncDatabase.cs @@ -581,7 +581,7 @@ public async Task WriteTransaction(Func> fn, DBLockO /// public Task Watch(string query, object[]? parameters, WatchHandler handler, SQLWatchOptions? options = null) { - var tcs = new TaskCompletionSource(); + var tcs = new TaskCompletionSource(); Task.Run(async () => { try @@ -611,7 +611,7 @@ public Task Watch(string query, object[]? parameters, WatchHandler handler Signal = options?.Signal, ThrottleMs = options?.ThrottleMs }); - tcs.SetResult(); + tcs.SetResult(true); } catch (Exception ex) { diff --git a/PowerSync/PowerSync.Common/PowerSync.Common.csproj b/PowerSync/PowerSync.Common/PowerSync.Common.csproj index 28610d8..4aab2a7 100644 --- a/PowerSync/PowerSync.Common/PowerSync.Common.csproj +++ b/PowerSync/PowerSync.Common/PowerSync.Common.csproj @@ -5,8 +5,20 @@ 12 enable enable - 0.0.1 - alpha + PowerSync.Common + PowerSync.Common + PowerSync.Common is a package that enables local-first and real-time reactive apps with embedded SQLite for .NET clients + PowerSync + powersync + Apache-2.0 + https://github.com/powersync-ja/powersync-dotnet + https://powersync.com + true + https://github.com/powersync-ja/powersync-dotnet/PowerSync/PowerSync.Common/CHANGELOG.md + powersync local-first local-storage state-management offline sql db persistence sqlite sync + icon.png + NU5100 + README.md @@ -18,10 +30,21 @@ + PreserveNewest + + + + + + + + + + diff --git a/PowerSync/PowerSync.Common/README.md b/PowerSync/PowerSync.Common/README.md index 818a081..cb8c852 100644 --- a/PowerSync/PowerSync.Common/README.md +++ b/PowerSync/PowerSync.Common/README.md @@ -2,6 +2,20 @@ This package contains a .NET implementation of a PowerSync database connector and streaming sync bucket implementation. +## ⚠️ Project Status & Release Note + +This package is currently in an alpha state, intended strictly for testing. Expect breaking changes and instability as development continues. + +Do not rely on this package for production use. + +## Installation + +This package is published on [NuGet](https://www.nuget.org/packages/PowerSync.Common). + +```bash +dotnet add package PowerSync.Common --prerelease +``` + ## Usage ### Simple Query diff --git a/PowerSync/PowerSync.Common/Utils/EventStream.cs b/PowerSync/PowerSync.Common/Utils/EventStream.cs index 38afd48..671abcd 100644 --- a/PowerSync/PowerSync.Common/Utils/EventStream.cs +++ b/PowerSync/PowerSync.Common/Utils/EventStream.cs @@ -53,9 +53,11 @@ public CancellationTokenSource RunListenerAsync( Func callback) { var cts = new CancellationTokenSource(); + var started = new TaskCompletionSource(); _ = Task.Run(async () => { + started.SetResult(true); await foreach (var value in ListenAsync(cts.Token)) { await callback(value); @@ -63,6 +65,8 @@ public CancellationTokenSource RunListenerAsync( }, cts.Token); + started.Task.GetAwaiter().GetResult(); + return cts; } @@ -76,15 +80,19 @@ public IAsyncEnumerable ListenAsync(CancellationToken cancellationToken) public CancellationTokenSource RunListener(Action callback) { var cts = new CancellationTokenSource(); + var started = new TaskCompletionSource(); _ = Task.Run(() => { + started.SetResult(true); foreach (var value in Listen(cts.Token)) { callback(value); } }, cts.Token); + started.Task.GetAwaiter().GetResult(); + return cts; } diff --git a/PowerSync/PowerSync.Common/Utils/PowerSyncPathResolver.cs b/PowerSync/PowerSync.Common/Utils/PowerSyncPathResolver.cs index 3e203d7..be382d8 100644 --- a/PowerSync/PowerSync.Common/Utils/PowerSyncPathResolver.cs +++ b/PowerSync/PowerSync.Common/Utils/PowerSyncPathResolver.cs @@ -6,6 +6,13 @@ public static class PowerSyncPathResolver { public static string GetNativeLibraryPath(string packagePath) { + + // .NET Framework 4.8 on Windows requires a different path (not supporting versions prior to this) + if (RuntimeInformation.FrameworkDescription.StartsWith(".NET Framework 4.8") && RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return Path.Combine(AppContext.BaseDirectory, "powersync.dll"); + } + string rid = GetRuntimeIdentifier(); string nativeDir = Path.Combine(packagePath, "runtimes", rid, "native"); diff --git a/README.md b/README.md index fc1bd55..62b8233 100644 --- a/README.md +++ b/README.md @@ -8,14 +8,10 @@ _[PowerSync](https://www.powersync.com) is a sync engine for building local-firs `powersync-dotnet` is the monorepo for PowerSync .NET SDKs. -## ⚠️ Project Status & Release Note - -This package is part of a monorepo that is not yet officially released or published. It is currently in a pre-alpha state, intended strictly for closed testing. Expect breaking changes and instability as development continues. - -Do not rely on this package for production use. - ## Monorepo Structure: Packages +Packages are published to [NuGet](https://www.nuget.org/profiles/PowerSync). + - [PowerSync/Common](./PowerSync/Common/README.md) - Core package: .NET implementation of a PowerSync database connector and streaming sync bucket implementation. Packages meant for specific platforms will extend functionality of `Common`. @@ -54,6 +50,18 @@ This PowerSync SDK currently targets the following .NET versions: ``` + + and create a `IsExternalInit.cs` file in your project with the following contents: + + ```cs + using System.ComponentModel; + + namespace System.Runtime.CompilerServices + { + [EditorBrowsable(EditorBrowsableState.Never)] + internal class IsExternalInit { } + } + ``` ------- @@ -87,29 +95,7 @@ Run a specific test dotnet test -v n --framework net8.0 --filter "test-file-pattern" ``` -## Using the PowerSync.Common package in your project (temporary) -A NuGet package will be available soon, until then you clone this repo and follow these steps: - -Add the dependency to your project's .csproj: -```.xml - - - -``` - -Which assumes the following directory structure: -``` -code/ - powersync-dotnet (X) - ├── PowerSync/PowerSync.Common - │ ├── PowerSync.Common.csproj - │ ├── Class1.cs - │ └── Utils.cs - └── root.sln - - your-project - ├── demo - │ ├── Program.csproj - │ └── Program.cs - ├── root.sln +## Using the PowerSync.Common package in your project +```bash +dotnet add package PowerSync.Common --prerelease ``` \ No newline at end of file diff --git a/Tests/PowerSync/PowerSync.Common.Tests/Client/PowerSyncDatabaseTransactionTests.cs b/Tests/PowerSync/PowerSync.Common.Tests/Client/PowerSyncDatabaseTransactionTests.cs index 40e84b1..d9a1614 100644 --- a/Tests/PowerSync/PowerSync.Common.Tests/Client/PowerSyncDatabaseTransactionTests.cs +++ b/Tests/PowerSync/PowerSync.Common.Tests/Client/PowerSyncDatabaseTransactionTests.cs @@ -189,7 +189,7 @@ await db.Execute( [Fact(Timeout = 2000)] public async Task ReadWhileWriteIsRunningTest() { - var tcs = new TaskCompletionSource(); + var tcs = new TaskCompletionSource(); // This wont resolve or free until another connection free's it var writeTask = db.WriteLock(async context => @@ -200,7 +200,7 @@ public async Task ReadWhileWriteIsRunningTest() var readTask = db.ReadLock(async context => { // Read logic could execute here while writeLock is still open - tcs.SetResult(); + tcs.SetResult(true); await Task.CompletedTask; return 42; }); @@ -238,13 +238,13 @@ await db.WriteLock(async context => public async Task CallUpdateHookOnChangesTest() { var cts = new CancellationTokenSource(); - var result = new TaskCompletionSource(); + var result = new TaskCompletionSource(); db.OnChange(new WatchOnChangeHandler { OnChange = (x) => { - result.SetResult(); + result.SetResult(true); cts.Cancel(); return Task.CompletedTask; } @@ -261,7 +261,7 @@ public async Task CallUpdateHookOnChangesTest() [Fact(Timeout = 2000)] public async Task ReflectWriteTransactionUpdatesOnReadConnectionsTest() { - var watched = new TaskCompletionSource(); + var watched = new TaskCompletionSource(); var cts = new CancellationTokenSource(); await db.Watch("SELECT COUNT(*) as count FROM assets", null, new WatchHandler @@ -270,7 +270,7 @@ public async Task ReflectWriteTransactionUpdatesOnReadConnectionsTest() { if (x.First().count == 1) { - watched.SetResult(); + watched.SetResult(true); cts.Cancel(); } } @@ -292,7 +292,7 @@ public async Task ReflectWriteLockUpdatesOnReadConnectionsTest() { var numberOfAssets = 10_000; - var watched = new TaskCompletionSource(); + var watched = new TaskCompletionSource(); var cts = new CancellationTokenSource(); await db.Watch("SELECT COUNT(*) as count FROM assets", null, new WatchHandler @@ -301,7 +301,7 @@ public async Task ReflectWriteLockUpdatesOnReadConnectionsTest() { if (x.First().count == numberOfAssets) { - watched.SetResult(); + watched.SetResult(true); cts.Cancel(); } } @@ -324,12 +324,12 @@ await db.WriteLock(async tx => } [Fact(Timeout = 5000)] - public async Task Insert10000Records_CompleteWithinTimeLimitTest() + public async Task Insert1000Records_CompleteWithinTimeLimitTest() { var random = new Random(); var stopwatch = Stopwatch.StartNew(); - for (int i = 0; i < 10000; ++i) + for (int i = 0; i < 1000; ++i) { int n = random.Next(0, 100000); await db.Execute( diff --git a/Tools/Setup/Setup.cs b/Tools/Setup/Setup.cs index 6638217..5494705 100644 --- a/Tools/Setup/Setup.cs +++ b/Tools/Setup/Setup.cs @@ -1,8 +1,8 @@ using System; using System.IO; using System.Net.Http; -using System.Runtime.InteropServices; using System.Threading.Tasks; +using System.Collections.Generic; public class Setup { @@ -11,103 +11,42 @@ static async Task Main(string[] args) const string baseUrl = "https://github.com/powersync-ja/powersync-sqlite-core/releases/download/v0.3.8"; string powersyncCorePath = Path.Combine(AppContext.BaseDirectory, "../../../../..", "PowerSync/PowerSync.Common/"); - string rid = GetRuntimeIdentifier(); - string nativeDir = Path.Combine(powersyncCorePath, "runtimes", rid, "native"); - - Directory.CreateDirectory(nativeDir); - - string sqliteCoreFilename = GetLibraryForPlatform(); - string sqliteCorePath = Path.Combine(nativeDir, sqliteCoreFilename); + var runtimeIdentifiers = new Dictionary + { + { "osx-x64", ("libpowersync_x64.dylib", "libpowersync.dylib") }, + { "osx-arm64", ("libpowersync_aarch64.dylib", "libpowersync.dylib") }, + { "linux-x64", ("libpowersync_x64.so", "libpowersync.so") }, + { "linux-arm64", ("libpowersync_aarch64.so", "libpowersync.so") }, + { "win-x64", ("powersync_x64.dll", "powersync.dll") } + }; - try + foreach (var (rid, (originalFile, newFile)) in runtimeIdentifiers) { - await DownloadFile($"{baseUrl}/{sqliteCoreFilename}", sqliteCorePath); + string nativeDir = Path.Combine(powersyncCorePath, "runtimes", rid, "native"); + Directory.CreateDirectory(nativeDir); - string newFileName = GetFileNameForPlatform(); - string newFilePath = Path.Combine(nativeDir, newFileName); + string sqliteCorePath = Path.Combine(nativeDir, originalFile); + string newFilePath = Path.Combine(nativeDir, newFile); - if (File.Exists(sqliteCorePath)) + try { - File.Move(sqliteCorePath, newFilePath, overwrite: true); - Console.WriteLine($"File renamed successfully from {sqliteCoreFilename} to {newFileName}"); + await DownloadFile($"{baseUrl}/{originalFile}", sqliteCorePath); + + if (File.Exists(sqliteCorePath)) + { + File.Move(sqliteCorePath, newFilePath, overwrite: true); + Console.WriteLine($"File renamed successfully from {originalFile} to {newFile} in {nativeDir}"); + } + else + { + throw new IOException($"File {originalFile} does not exist."); + } } - else + catch (Exception ex) { - throw new IOException($"File {sqliteCoreFilename} does not exist."); + Console.Error.WriteLine($"Error processing {rid}: {ex.Message}"); } } - catch (Exception ex) - { - Console.Error.WriteLine($"Error: {ex.Message}"); - Environment.Exit(1); - } - } - - static string GetRuntimeIdentifier() - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - { - if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64) - return "osx-arm64"; - else - return "osx-x64"; - } - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64) - return "linux-arm64"; - else - return "linux-x64"; - } - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - return "win-x64"; - } - throw new PlatformNotSupportedException("Unsupported platform."); - } - - static string GetFileNameForPlatform() - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - { - return "libpowersync.dylib"; - } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - return "libpowersync.so"; - } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - return "powersync.dll"; - } - else - { - throw new PlatformNotSupportedException("Unsupported platform."); - } - } - - static string GetLibraryForPlatform() - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - { - return RuntimeInformation.ProcessArchitecture == Architecture.Arm64 - ? "libpowersync_aarch64.dylib" - : "libpowersync_x64.dylib"; - } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - return RuntimeInformation.ProcessArchitecture == Architecture.Arm64 - ? "libpowersync_aarch64.so" - : "libpowersync_x64.so"; - } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - return "powersync_x64.dll"; - } - else - { - throw new PlatformNotSupportedException("Unsupported platform."); - } } static async Task DownloadFile(string url, string outputPath) diff --git a/icon.png b/icon.png new file mode 100644 index 0000000..1e6d62c Binary files /dev/null and b/icon.png differ