From 413e21a848df9f76ccd08d6f288fb02512f21f76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Tue, 28 May 2024 12:28:53 +0200 Subject: [PATCH 01/16] RDART-1039: Drop catalyst support. Flutter doesn't support it (#1696) * Drop catalyst support. Flutter doesn't support it * Update CHANGELOG --- .github/workflows/binary-combine-ios.yml | 8 +------- .github/workflows/ci.yml | 2 +- CHANGELOG.md | 1 + packages/realm_dart/CMakePresets.json | 9 --------- packages/realm_dart/dev/lib/src/build.dart | 2 +- packages/realm_dart/scripts/build-ios.sh | 6 +----- 6 files changed, 5 insertions(+), 23 deletions(-) diff --git a/.github/workflows/binary-combine-ios.yml b/.github/workflows/binary-combine-ios.yml index 3ebc2c9a7..1bb198739 100644 --- a/.github/workflows/binary-combine-ios.yml +++ b/.github/workflows/binary-combine-ios.yml @@ -15,23 +15,18 @@ jobs: with: name: librealm-ios-device path: binary/ios + - name: Fetch simulator build uses: actions/download-artifact@v4 with: name: librealm-ios-simulator path: binary/ios - - name: Fetch catalyst build - uses: actions/download-artifact@v4 - with: - name: librealm-ios-catalyst - path: binary/ios - name: Build .xcframework run: | xcodebuild -create-xcframework \ -framework ./binary/ios/Release-iphoneos/realm_dart.framework \ -framework ./binary/ios/Release-iphonesimulator/realm_dart.framework \ - -framework ./binary/ios/Release-maccatalyst/realm_dart.framework \ -output ./binary/ios/realm_dart.xcframework rm -rf ./binary/ios/Release-* @@ -48,5 +43,4 @@ jobs: name: | librealm-ios-device librealm-ios-simulator - librealm-ios-catalyst failOnError: false diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f0b577f51..dc27fc007 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,7 +53,7 @@ jobs: with: runner: macos-12 binary: ios - build: '["ios-device", "ios-simulator", "ios-catalyst"]' + build: '["ios-device", "ios-simulator"]' build-android-combined: name: Build combine Android diff --git a/CHANGELOG.md b/CHANGELOG.md index a8130ac56..f1a9004d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ ### Internal * Using Core 14.7.0. * Disabled codesigning of Apple binaries. (Issue [#1679](https://github.com/realm/realm-dart/issues/1679)) +* Drop building xcframework for catalyst. (Issue [#1695](https://github.com/realm/realm-dart/issues/1695)) ## 2.3.0 (2024-05-23) diff --git a/packages/realm_dart/CMakePresets.json b/packages/realm_dart/CMakePresets.json index b83696281..2c8948870 100644 --- a/packages/realm_dart/CMakePresets.json +++ b/packages/realm_dart/CMakePresets.json @@ -185,15 +185,6 @@ "xcode_destination": "generic/platform=iOS Simulator" }, "configuration": "Debug" - }, - { - "name": "ios-catalyst", - "displayName": "Catalyst", - "inherits": "ios", - "environment": { - "xcode_destination": "generic/platform=macOS,variant=Mac Catalyst" - }, - "configuration": "Debug" } ] } \ No newline at end of file diff --git a/packages/realm_dart/dev/lib/src/build.dart b/packages/realm_dart/dev/lib/src/build.dart index e6659975a..59b03a918 100644 --- a/packages/realm_dart/dev/lib/src/build.dart +++ b/packages/realm_dart/dev/lib/src/build.dart @@ -38,7 +38,7 @@ enum Architecture { enum iOSSdk { iPhoneOS('device'), iPhoneSimulator('simulator'), - ; // flutter doesn't support maccatalyst (yet?) + ; // flutter doesn't support catalyst (yet https://github.com/flutter/flutter/issues/33860?) final String? _cmakeName; String get cmakeName => _cmakeName ?? name; diff --git a/packages/realm_dart/scripts/build-ios.sh b/packages/realm_dart/scripts/build-ios.sh index 22fb3e303..b49b06e9d 100755 --- a/packages/realm_dart/scripts/build-ios.sh +++ b/packages/realm_dart/scripts/build-ios.sh @@ -13,7 +13,7 @@ function usage { echo "" echo "Arguments:" echo " -c : build configuration (Debug or Release)" - echo " : platforms to build for (catalyst, ios, or simulator)" + echo " : platforms to build for (ios, or simulator)" echo " " echo "Environment variables:" echo " REALM_USE_CCACHE=TRUE - enables ccache builds" @@ -66,10 +66,6 @@ for platform in "${PLATFORMS[@]}"; do cmake --build --preset ios-device --config $CONFIGURATION FRAMEWORKS+=(-framework ./binary/ios/$CONFIGURATION-iphoneos/realm_dart.framework) ;; - catalyst) - cmake --build --preset ios-catalyst --config $CONFIGURATION - FRAMEWORKS+=(-framework ./binary/ios/$CONFIGURATION-maccatalyst/realm_dart.framework) - ;; simulator) cmake --build --preset ios-simulator --config $CONFIGURATION FRAMEWORKS+=(-framework ./binary/ios/$CONFIGURATION-iphonesimulator/realm_dart.framework) From e2d918709d5d811c31e31af551cbc8380065332a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Wed, 29 May 2024 15:56:56 +0200 Subject: [PATCH 02/16] RDART-962: Use xcode 15.4 (#1548) * Use xcode 15.4 Update CHANGELOG Use Release instead of MinSIzeRel * bump ulimit on ios test * Check arch instead of inferring implicitly * Update flutter-desktop-tests.yml to match dart-desktop-tests.yml * Don't force XCode version on iOS tests (only on builds) * Run iOS test on macos-12 * Update CHANGELOG * PR feedback Co-authored-by: Nikola Irinchev * PR feedback 2 --------- Co-authored-by: Nikola Irinchev --- .github/workflows/binary-combine-android.yml | 2 + .github/workflows/build-native.yml | 8 +- .github/workflows/ci.yml | 101 ++++++++++++------- .github/workflows/dart-desktop-tests.yml | 46 +++++---- .github/workflows/flutter-desktop-tests.yml | 48 +++++---- CHANGELOG.md | 1 + packages/realm_dart/CMakePresets.json | 2 +- packages/realm_dart/scripts/build-macos.sh | 2 +- 8 files changed, 127 insertions(+), 83 deletions(-) diff --git a/.github/workflows/binary-combine-android.yml b/.github/workflows/binary-combine-android.yml index 6187c54f9..559fe51c9 100644 --- a/.github/workflows/binary-combine-android.yml +++ b/.github/workflows/binary-combine-android.yml @@ -14,11 +14,13 @@ jobs: with: name: librealm-android-x86_64 path: packages/realm_dart/binary/android + - name: Fetch armeabi-v7a build uses: actions/download-artifact@v4 with: name: librealm-android-armeabi-v7a path: packages/realm_dart/binary/android + - name: Fetch arm64-v8a build uses: actions/download-artifact@v4 with: diff --git a/.github/workflows/build-native.yml b/.github/workflows/build-native.yml index 67352f8ef..33daa68a1 100644 --- a/.github/workflows/build-native.yml +++ b/.github/workflows/build-native.yml @@ -51,9 +51,11 @@ jobs: if: startsWith(matrix.build, 'android') run: echo "ANDROID_NDK=$ANDROID_NDK_LATEST_HOME" >> $GITHUB_ENV - - name: Downgrade XCode for MacOS - if: matrix.build == 'macos' - run: sudo xcodes select 14.3.1 + - name: Select XCode for MacOS + if: runner.os == 'macOS' + run: | + xcodes installed + sudo xcodes select 15.4 - name: Build if: steps.check-cache.outputs.cache-hit != 'true' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dc27fc007..63d68845b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,9 +19,9 @@ jobs: name: Build Windows uses: ./.github/workflows/build-native.yml with: - runner: windows-latest - binary: windows - build: '["windows"]' + runner: windows-latest + binary: windows + build: '["windows"]' build-macos: name: Build MacOS @@ -35,23 +35,23 @@ jobs: name: Build Linux uses: ./.github/workflows/build-native.yml with: - runner: ubuntu-20.04 # Building on the lowest possible Linux (Ubuntu) version for compatibility - binary: linux - build: '["linux"]' + runner: ubuntu-20.04 # Building on the lowest possible Linux (Ubuntu) version for compatibility + binary: linux + build: '["linux"]' build-android: name: Build Android uses: ./.github/workflows/build-native.yml with: - runner: ubuntu-20.04 - binary: android - build: '["android-x86_64", "android-armeabi-v7a", "android-arm64-v8a"]' + runner: ubuntu-20.04 + binary: android + build: '["android-x86_64", "android-armeabi-v7a", "android-arm64-v8a"]' build-ios: name: Build IOS uses: ./.github/workflows/build-native.yml with: - runner: macos-12 + runner: macos-14 binary: ios build: '["ios-device", "ios-simulator"]' @@ -65,7 +65,7 @@ jobs: needs: build-ios uses: ./.github/workflows/binary-combine-ios.yml -# Dart jobs + # Dart jobs deploy-cluster-dart-windows: name: Deploy Cluster for Dart Windows @@ -82,7 +82,6 @@ jobs: - deploy-cluster-dart-windows secrets: inherit with: - os: windows runner: windows-latest differentiator: dw${{ github.run_id }}${{ github.run_attempt }} @@ -111,8 +110,7 @@ jobs: - deploy-cluster-dart-macos secrets: inherit with: - os: macos - runner: macos-latest + runner: macos-13 differentiator: dm${{ github.run_id }}${{ github.run_attempt }} cleanup-cluster-dart-macos: @@ -140,9 +138,8 @@ jobs: - deploy-cluster-dart-macos-arm secrets: inherit with: - os: macos runner: macos-14 - architecture: arm + arch: arm64 differentiator: dma${{ github.run_id }}${{ github.run_attempt }} cleanup-cluster-dart-macos-arm: @@ -170,7 +167,6 @@ jobs: - deploy-cluster-dart-linux secrets: inherit with: - os: linux runner: ubuntu-latest differentiator: dl${{ github.run_id }}${{ github.run_attempt }} @@ -184,7 +180,7 @@ jobs: with: differentiator: dl${{ github.run_id }}${{ github.run_attempt }} -# Flutter jobs + # Flutter jobs deploy-cluster-flutter-windows: name: Deploy Cluster for Flutter Windows uses: ./.github/workflows/deploy-baas.yml @@ -200,11 +196,9 @@ jobs: - deploy-cluster-flutter-windows secrets: inherit with: - os: windows runner: windows-latest differentiator: fw${{ github.run_id }}${{ github.run_attempt }} - cleanup-cluster-flutter-windows: name: Cleanup Cluster for Flutter Windows uses: ./.github/workflows/terminate-baas.yml @@ -230,8 +224,7 @@ jobs: - deploy-cluster-flutter-macos secrets: inherit with: - os: macos - runner: macos-latest + runner: macos-13 differentiator: fm${{ github.run_id }}${{ github.run_attempt }} cleanup-cluster-flutter-macos: @@ -244,6 +237,35 @@ jobs: with: differentiator: fm${{ github.run_id }}${{ github.run_attempt }} + deploy-cluster-flutter-macos-arm: + name: Deploy Cluster for Flutter MacOS Arm + uses: ./.github/workflows/deploy-baas.yml + secrets: inherit + with: + differentiator: fma${{ github.run_id }}${{ github.run_attempt }} + + flutter-tests-macos-arm: + name: Flutter Tests MacOS Arm + uses: ./.github/workflows/flutter-desktop-tests.yml + needs: + - build-macos + - deploy-cluster-flutter-macos-arm + secrets: inherit + with: + runner: macos-14 + arch: arm64 + differentiator: fma${{ github.run_id }}${{ github.run_attempt }} + + cleanup-cluster-flutter-macos-arm: + name: Cleanup Cluster for Flutter macOS Arm + uses: ./.github/workflows/terminate-baas.yml + if: always() + needs: + - flutter-tests-macos-arm + secrets: inherit + with: + differentiator: fma${{ github.run_id }}${{ github.run_attempt }} + deploy-cluster-flutter-linux: name: Deploy Cluster for Flutter Linux uses: ./.github/workflows/deploy-baas.yml @@ -259,7 +281,6 @@ jobs: - deploy-cluster-flutter-linux secrets: inherit with: - os: linux runner: ubuntu-latest differentiator: fl${{ github.run_id }}${{ github.run_attempt }} @@ -298,7 +319,12 @@ jobs: - name: Checkout uses: actions/checkout@v4 with: - submodules: 'recursive' + submodules: "recursive" + + - name: Bump ulimit + run: | + ulimit -n + ulimit -n 10240 - name: Enable ccache run: echo "PATH=/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" >> $GITHUB_ENV @@ -312,7 +338,7 @@ jobs: - name: Setup Flutter uses: subosito/flutter-action@v2 with: - channel: 'stable' + channel: "stable" - name: Setup Melos run: | @@ -325,9 +351,9 @@ jobs: - name: Launch Simulator uses: futureware-tech/simulator-action@v3 with: - model: 'iPhone SE (3rd generation)' - os: 'iOS' - os_version: '>= 14.0' + model: "iPhone SE (3rd generation)" + os: "iOS" + os_version: ">= 14.0" - name: Run tests on iOS Simulator run: | @@ -382,7 +408,7 @@ jobs: - name: Checkout uses: actions/checkout@v4 with: - submodules: 'recursive' + submodules: "recursive" - name: Enable KVM run: | @@ -396,7 +422,7 @@ jobs: - name: Setup Java uses: actions/setup-java@v4 with: - distribution: 'temurin' + distribution: "temurin" java-version: 11 - name: Fetch artifacts @@ -408,7 +434,7 @@ jobs: - name: Setup Flutter uses: subosito/flutter-action@v2 with: - channel: 'stable' + channel: "stable" - name: Setup Melos run: | @@ -419,7 +445,7 @@ jobs: run: dart pub get # Hack to free up space on the runner to ensure we have enough diskspace to run the emulator - - name: Remove unnecessary files (dotnet, etc.) + - name: Remove unnecessary files (dotnet, etc.) run: | sudo rm -rf /usr/share/dotnet @@ -468,7 +494,6 @@ jobs: only-summary: true working-directory: packages/realm/tests - cleanup-cluster-flutter-android: name: Cleanup Cluster for Flutter Android uses: ./.github/workflows/terminate-baas.yml @@ -479,7 +504,7 @@ jobs: with: differentiator: fa${{ github.run_id }}${{ github.run_attempt }} -# Generator jobs + # Generator jobs generator: strategy: @@ -499,12 +524,12 @@ jobs: - name: Checkout uses: actions/checkout@v4 with: - submodules: 'recursive' + submodules: "recursive" - name: Setup Flutter uses: subosito/flutter-action@v2 with: - channel: 'stable' + channel: "stable" - name: Setup Melos run: | @@ -580,7 +605,7 @@ jobs: runs-on: ubuntu-latest if: always() && github.ref == 'refs/heads/main' steps: - # Run this action to set env.WORKFLOW_CONCLUSION + # Run this action to set env.WORKFLOW_CONCLUSION - uses: technote-space/workflow-conclusion-action@45ce8e0eb155657ab8ccf346ade734257fd196a5 - uses: act10ns/slack@ed1309ab9862e57e9e583e51c7889486b9a00b0f @@ -589,7 +614,7 @@ jobs: with: status: ${{ env.WORKFLOW_CONCLUSION }} webhook-url: ${{ secrets.SLACK_DART_WEBHOOK }} - channel: '#realm-github-dart' + channel: "#realm-github-dart" message: | ** <{{refUrl}}|`{{ref}}` - {{description}}> diff --git a/.github/workflows/dart-desktop-tests.yml b/.github/workflows/dart-desktop-tests.yml index 0f4d80087..1d65b3cca 100644 --- a/.github/workflows/dart-desktop-tests.yml +++ b/.github/workflows/dart-desktop-tests.yml @@ -3,22 +3,18 @@ name: Dart desktop tests on: workflow_call: inputs: - os: - description: OS to execute on. - required: true - type: string runner: description: GitHub runner image to execute on. required: true type: string - architecture: - description: Architecture to execute on. - required: false - type: string differentiator: description: Differentiator for the BaaS container. required: true type: string + arch: + description: Architecture to execute on. + default: x64 + type: string env: REALM_CI: true @@ -28,7 +24,7 @@ env: jobs: dart-tests: runs-on: ${{ inputs.runner }} - name: Dart tests on ${{inputs.os }} ${{ inputs.architecture }} + name: Dart tests on ${{ inputs.runner }}-${{ inputs.arch }} timeout-minutes: 45 steps: @@ -37,17 +33,27 @@ jobs: with: submodules: false + - name: Check runner.arch + if: ${{ inputs.arch != runner.arch }} + run: false # fail if arch is not as expected + shell: bash + + - id: runner_os_lowercase + # there is no such thing as ${{ tolower(runner.os) }}, hence this abomination ¯\_(ツ)_/¯ + # use with steps.runner_os_lowercase.outputs.os + run: echo ${{ runner.os }} | awk '{print "os=" tolower($0)}' >> $GITHUB_OUTPUT + shell: bash + - name: Fetch artifacts uses: actions/download-artifact@v4 with: - name: librealm-${{ inputs.os }} - path: packages/realm_dart/binary/${{ inputs.os }} + name: librealm-${{ steps.runner_os_lowercase.outputs.os }} + path: packages/realm_dart/binary/${{ steps.runner_os_lowercase.outputs.os }} - - name : Setup Dart SDK + - name: Setup Dart SDK uses: dart-lang/setup-dart@main with: sdk: stable - architecture: ${{ inputs.architecture == 'arm' && 'arm64' || 'x64'}} - name: Setup Melos run: | @@ -56,18 +62,20 @@ jobs: melos setup - name: Bump ulimit on macos - run: ulimit -n 10240 - if: ${{ contains(inputs.os, 'macos') }} + run: | + ulimit -n + ulimit -n 10240 + if: ${{ contains(runner.os, 'macos') }} - - name: Run tests - run: ${{ inputs.architecture == 'arm' && 'arch -arm64 ' || '' }}melos test:unit + - name: Run tests ${{ runner }} ${{ runner.arch }} + run: melos test:unit # TODO: Publish all reports - name: Publish Test Report uses: dorny/test-reporter@v1.8.0 if: success() || failure() with: - name: Test Results Dart ${{ inputs.os }} ${{ inputs.architecture }} + name: Test Results Dart ${{ runner }} ${{ runner.arch }} path: test-results.json reporter: dart-json only-summary: true @@ -89,4 +97,4 @@ jobs: github-token: ${{ secrets.GITHUB_TOKEN }} flag-name: realm_dart path-to-lcov: ./coverage/lcov.info - parallel: true \ No newline at end of file + parallel: true diff --git a/.github/workflows/flutter-desktop-tests.yml b/.github/workflows/flutter-desktop-tests.yml index 59df0e196..a5234499b 100644 --- a/.github/workflows/flutter-desktop-tests.yml +++ b/.github/workflows/flutter-desktop-tests.yml @@ -3,22 +3,18 @@ name: Flutter desktop tests on: workflow_call: inputs: - os: - description: OS to execute on. - required: true - type: string runner: description: GitHub runner image to execute on. required: true type: string - architecture: - description: Architecture to execute on. - required: false - type: string differentiator: description: Differentiator for the BaaS container. required: true type: string + arch: + description: Architecture to execute on. + default: x64 + type: string env: REALM_CI: true @@ -26,7 +22,7 @@ env: jobs: flutter-tests: runs-on: ${{ inputs.runner }} - name: Flutter tests on ${{inputs.os }}-${{ inputs.architecture }} + name: Flutter tests on ${{ inputs.runner }}-${{ inputs.arch }} timeout-minutes: 45 env: BAAS_BAASAAS_API_KEY: ${{ secrets.BAASAAS_API_KEY}} @@ -42,8 +38,19 @@ jobs: with: submodules: false + - name: Check runner.arch + if: ${{ inputs.arch != runner.arch }} + run: false # fail if arch is not as expected + shell: bash + + - id: runner_os_lowercase + # there is no such thing as ${{ tolower(runner.os) }}, hence this abomination ¯\_(ツ)_/¯ + # use with steps.runner_os_lowercase.outputs.os + run: echo ${{ runner.os }} | awk '{print "os=" tolower($0)}' >> $GITHUB_OUTPUT + shell: bash + - name: Setup GTK & Ninja on Linux - if: ${{ inputs.os == 'linux' }} + if: ${{ runner.os == 'linux' }} run: | sudo apt-get update -y sudo apt-get install -y libgtk-3-dev xvfb ninja-build @@ -51,17 +58,14 @@ jobs: - name: Fetch artifacts uses: actions/download-artifact@v4 with: - name: librealm-${{ inputs.os }} - path: packages/realm_dart/binary/${{ inputs.os }} + name: librealm-${{ steps.runner_os_lowercase.outputs.os }} + path: packages/realm_dart/binary/${{ steps.runner_os_lowercase.outputs.os }} - name: Setup Flutter uses: subosito/flutter-action@v2 with: channel: 'stable' - architecture: ${{ inputs.architecture == 'arm' && 'arm64' || 'x64'}} - - - name: Enable Flutter Desktop support - run: flutter config --enable-${{ inputs.os }}-desktop + architecture: ${{ inputs.arch }} - name: Setup Melos run: | @@ -72,16 +76,18 @@ jobs: run: dart pub get - name: Bump ulimit - run: ulimit -n 10240 - if: ${{ contains(inputs.os, 'macos') }} + run: | + ulimit -n + ulimit -n 10240 + if: ${{ contains(runner.os, 'macos') }} - name: Run tests run: | - ${{ inputs.os == 'linux' && 'xvfb-run' || '' }} \ + ${{ runner.os == 'linux' && 'xvfb-run' || '' }} \ flutter test integration_test/all_tests.dart \ --dart-define=BAAS_BAASAAS_API_KEY=$BAAS_BAASAAS_API_KEY \ --dart-define=BAAS_DIFFERENTIATOR=$BAAS_DIFFERENTIATOR \ - --device-id=${{ inputs.os }} \ + --device-id=${{ steps.runner_os_lowercase.outputs.os }} \ --file-reporter=json:test-results.json \ --suppress-analytics shell: bash @@ -91,7 +97,7 @@ jobs: uses: dorny/test-reporter@v1.8.0 if: success() || failure() with: - name: Test Results Flutter ${{ inputs.os }} ${{ inputs.architecture }} + name: Test Results Flutter ${{ runner.os }} ${{ runner.arch }} path: test-results.json reporter: dart-json only-summary: true diff --git a/CHANGELOG.md b/CHANGELOG.md index f1a9004d7..233abb1a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ * Using Core 14.7.0. * Disabled codesigning of Apple binaries. (Issue [#1679](https://github.com/realm/realm-dart/issues/1679)) * Drop building xcframework for catalyst. (Issue [#1695](https://github.com/realm/realm-dart/issues/1695)) +* Using xcode 15.4 for native build. (Issue [#1547](https://github.com/realm/realm-dart/issues/1547)) ## 2.3.0 (2024-05-23) diff --git a/packages/realm_dart/CMakePresets.json b/packages/realm_dart/CMakePresets.json index 2c8948870..b802ce4be 100644 --- a/packages/realm_dart/CMakePresets.json +++ b/packages/realm_dart/CMakePresets.json @@ -154,7 +154,7 @@ "configurePreset": "macos", "nativeToolOptions": [ "-destination", - "platform=macOS" + "generic/platform=macOS" ], "configuration": "Debug" }, diff --git a/packages/realm_dart/scripts/build-macos.sh b/packages/realm_dart/scripts/build-macos.sh index dd1b98585..5c78eee8f 100755 --- a/packages/realm_dart/scripts/build-macos.sh +++ b/packages/realm_dart/scripts/build-macos.sh @@ -7,4 +7,4 @@ set -o pipefail cd "$(dirname "$0")/.." cmake --preset macos -cmake --build --preset macos --config MinSizeRel -- -destination "generic/platform=macOS" +cmake --build --preset macos --config Release -- -destination "generic/platform=macOS" From c57d96f0786ff53fa2141862406442962e44dc51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Thu, 30 May 2024 09:46:06 +0200 Subject: [PATCH 03/16] RDART-1045: Expose setrlimit ios (#1700) * Expose setrlimit on iOS * Use setrlimit when running test on iOS * Revert "Run iOS test on macos-12" This reverts commit 1e6a379b80efecfd38bdfeef13a6899fa8627888. * Throw and handle exceptions on error for good measure --- .github/workflows/ci.yml | 2 +- .../lib/src/native/realm_bindings.dart | 32 ++++++++++++++++ .../realm_dart/lib/src/native/realm_core.dart | 8 ++++ packages/realm_dart/src/ios/platform.mm | 38 +++++++++++++++++-- packages/realm_dart/src/realm_dart.h | 12 ++++++ packages/realm_dart/test/test.dart | 5 +++ 6 files changed, 93 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 63d68845b..4cf172d84 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -302,7 +302,7 @@ jobs: differentiator: fi${{ github.run_id }}${{ github.run_attempt }} flutter-tests-ios: - runs-on: macos-12 + runs-on: macos-14 name: Flutter Tests iOS timeout-minutes: 45 needs: diff --git a/packages/realm_dart/lib/src/native/realm_bindings.dart b/packages/realm_dart/lib/src/native/realm_bindings.dart index a7fdd06fe..721dbca85 100644 --- a/packages/realm_dart/lib/src/native/realm_bindings.dart +++ b/packages/realm_dart/lib/src/native/realm_bindings.dart @@ -3971,6 +3971,33 @@ class RealmLibrary { late final _realm_dart_scheduler_invoke = _realm_dart_scheduler_invokePtr .asFunction)>(); + /// implemented for iOS only (for now - valid for all posix) + /// /** + /// * Set the soft limit on number of open files + /// * @param limit The requested limit. If less than zero no attempt is made. + /// * @param[out] out_limit The actual limit set. + /// * + /// * @return true if no error occurred. + /// * + /// * @throws RLM_ERR_FILE_PERMISSION_DENIED if the operation was not permitted. + /// */ + bool realm_dart_set_and_get_rlimit( + int limit, + ffi.Pointer out_limit, + ) { + return _realm_dart_set_and_get_rlimit( + limit, + out_limit, + ); + } + + late final _realm_dart_set_and_get_rlimitPtr = _lookup< + ffi + .NativeFunction)>>( + 'realm_dart_set_and_get_rlimit'); + late final _realm_dart_set_and_get_rlimit = _realm_dart_set_and_get_rlimitPtr + .asFunction)>(); + bool realm_dart_sync_after_reset_handler_callback( ffi.Pointer userdata, ffi.Pointer before_realm, @@ -11833,6 +11860,11 @@ class _SymbolAddresses { .NativeFunction)>> get realm_dart_scheduler_invoke => _library._realm_dart_scheduler_invokePtr; + ffi.Pointer< + ffi + .NativeFunction)>> + get realm_dart_set_and_get_rlimit => + _library._realm_dart_set_and_get_rlimitPtr; ffi.Pointer< ffi.NativeFunction< ffi.Bool Function( diff --git a/packages/realm_dart/lib/src/native/realm_core.dart b/packages/realm_dart/lib/src/native/realm_core.dart index a05b4d1ab..2b2a4830e 100644 --- a/packages/realm_dart/lib/src/native/realm_core.dart +++ b/packages/realm_dart/lib/src/native/realm_core.dart @@ -188,4 +188,12 @@ class RealmCore { String _getFilesPath() { return realmLib.realm_dart_get_files_path().cast().toRealmDartString()!; } + + int setAndGetRLimit(int limit) { + return using((arena) { + final outLimit = arena(); + realmLib.realm_dart_set_and_get_rlimit(limit, outLimit).raiseLastErrorIfFalse(); + return outLimit.value; + }); + } } diff --git a/packages/realm_dart/src/ios/platform.mm b/packages/realm_dart/src/ios/platform.mm index 80a330ee6..bfe62f2e8 100644 --- a/packages/realm_dart/src/ios/platform.mm +++ b/packages/realm_dart/src/ios/platform.mm @@ -19,9 +19,12 @@ #import #import -#include #include "../realm_dart.h" -#import +#include +#include +#include +#include +#include static std::string filesDir; static std::string deviceModel; @@ -48,7 +51,6 @@ } } - RLM_API const char* realm_dart_get_files_path() { if (filesDir == "") { filesDir = default_realm_file_directory(); @@ -74,3 +76,33 @@ return deviceVersion.c_str(); } + +namespace { + using namespace realm::c_api; + + rlimit get_rlimit() { + rlimit rlim; + int status = getrlimit(RLIMIT_NOFILE, &rlim); + if (status < 0) + throw std::system_error(errno, std::system_category(), "getrlimit() failed"); + return rlim; + } + + long set_and_get_rlimit(long limit) { + if (limit > 0) { + auto rlim = get_rlimit(); + rlim.rlim_cur = limit; + int status = setrlimit(RLIMIT_NOFILE, &rlim); + if (status < 0) + throw std::system_error(errno, std::system_category(), "setrlimit() failed"); + } + return get_rlimit().rlim_cur; + } +} + +RLM_API bool realm_dart_set_and_get_rlimit(long limit, long* out_limit) { + return wrap_err([&]() { + *out_limit = set_and_get_rlimit(limit); + return true; + }); +} diff --git a/packages/realm_dart/src/realm_dart.h b/packages/realm_dart/src/realm_dart.h index 7108b677a..5726809a5 100644 --- a/packages/realm_dart/src/realm_dart.h +++ b/packages/realm_dart/src/realm_dart.h @@ -36,6 +36,18 @@ RLM_API const char* realm_dart_get_device_version(); // implemented for Android only RLM_API const char* realm_dart_get_bundle_id(); +// implemented for iOS only (for now - valid for all posix) +/** + * Set the soft limit on number of open files + * @param limit The requested limit. If less than zero no attempt is made. + * @param[out] out_limit The actual limit set. + * + * @return true if no error occurred. + * + * @throws RLM_ERR_FILE_PERMISSION_DENIED if the operation was not permitted. + */ +RLM_API bool realm_dart_set_and_get_rlimit(long limit, long* out_limit); + RLM_API const char* realm_get_library_cpu_arch(); typedef struct realm_dart_userdata_async* realm_dart_userdata_async_t; diff --git a/packages/realm_dart/test/test.dart b/packages/realm_dart/test/test.dart index 7dc8eae1c..f88e5d0ec 100644 --- a/packages/realm_dart/test/test.dart +++ b/packages/realm_dart/test/test.dart @@ -430,6 +430,11 @@ void setupTests() { printOnFailure('${record.category} ${record.level.name}: ${record.message}'); }); + if (Platform.isIOS) { + final maxFiles = realmCore.setAndGetRLimit(1024); + print('Max files: $maxFiles'); + } + // Enable this to print platform info, including current PID _printPlatformInfo(); }); From 2b476a74853d4479c964d06aac86b02f99cc831c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Thu, 30 May 2024 11:41:45 +0200 Subject: [PATCH 04/16] RDART-999: Fix flutter test dlopen (#1623) * Use lib in package directly during flutter test * Fix paths * Update CHANGELOG --- CHANGELOG.md | 1 + .../lib/src/cli/common/target_os_type.dart | 15 +-- .../lib/src/cli/install/install_command.dart | 72 +++++------ .../lib/src/cli/install/options.dart | 10 +- .../lib/src/cli/install/options.g.dart | 57 +++++--- packages/realm_dart/lib/src/init.dart | 122 ++++++++++++------ packages/realm_dart/lib/src/realm_dart.dart | 3 - .../realm_dart/lib/src/realm_flutter.dart | 3 - 8 files changed, 166 insertions(+), 117 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 233abb1a2..8550b9c42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Fixed * Fixed an issue that would cause macOS apps to be rejected with `Invalid Code Signing Entitlements` error. (Issue [#1679](https://github.com/realm/realm-dart/issues/1679)) +* Fixed a regression that makes it inconvenient to run unit tests using realm. (Issue [#1619](https://github.com/realm/realm-dart/issues/1619)) ### Compatibility * Realm Studio: 15.0.0 or later. diff --git a/packages/realm_dart/lib/src/cli/common/target_os_type.dart b/packages/realm_dart/lib/src/cli/common/target_os_type.dart index f87d958b6..6868ebfce 100644 --- a/packages/realm_dart/lib/src/cli/common/target_os_type.dart +++ b/packages/realm_dart/lib/src/cli/common/target_os_type.dart @@ -8,24 +8,19 @@ enum TargetOsType { ios, linux, macos, - windows, + windows; + + bool get isDesktop => [TargetOsType.linux, TargetOsType.macos, TargetOsType.windows].contains(this); } // Cannot use Dart 2.17 enhanced enums, due to an issue with build_cli :-/ enum Flavor { flutter, - dart, + dart; } extension FlavorEx on Flavor { - String get packageName { - switch (this) { - case Flavor.dart: - return 'realm_dart'; - case Flavor.flutter: - return 'realm'; - } - } + String get packageName => switch (this) { Flavor.dart => 'realm_dart', Flavor.flutter => 'realm' }; } extension StringEx on String { diff --git a/packages/realm_dart/lib/src/cli/install/install_command.dart b/packages/realm_dart/lib/src/cli/install/install_command.dart index 5d44ba5db..5f783f128 100644 --- a/packages/realm_dart/lib/src/cli/install/install_command.dart +++ b/packages/realm_dart/lib/src/cli/install/install_command.dart @@ -25,26 +25,34 @@ class InstallCommand extends Command { late Options options; - bool get debug => options.debug; + late Pubspec pubspec; + late final debug = options.debug; + late final flavor = options.flavor!; + late final force = options.force; // not used!?! + late final targetOsType = options.targetOsType!; InstallCommand() { - populateOptionsParser(argParser); + pubspec = parsePubspec(File('pubspec.yaml')); + final defaultFlavor = pubspec.dependencies['flutter'] == null ? Flavor.dart : Flavor.flutter; // default depends on project type + populateOptionsParser( + argParser, + targetOsTypeDefaultOverride: Platform.operatingSystem.asTargetOsType, + flavorDefaultOverride: defaultFlavor, + ); } - Directory getBinaryPath(Directory realmPackagePath, {required bool isFlutter}) { - if (isFlutter) { - final root = realmPackagePath.path; - return Directory(switch (options.targetOsType) { - TargetOsType.android => path.join(root, 'android', 'src', 'main', 'cpp', 'lib'), - TargetOsType.ios => path.join(root, 'ios'), - TargetOsType.macos => path.join(root, 'macos'), - TargetOsType.linux => path.join(root, 'linux', 'binary', 'linux'), - TargetOsType.windows => path.join(root, 'windows', 'binary', 'windows'), - _ => throw Exception('Unsupported target OS type for Flutter: ${options.targetOsType}') - }); - } - // TODO: Should binaries not go into package also for Dart? - return Directory(path.join(Directory.current.absolute.path, 'binary', options.targetOsType!.name)); + Directory getBinaryPath(Directory realmPackagePath, Flavor flavor) { + final root = realmPackagePath.path; + return switch (flavor) { + Flavor.dart => Directory(path.join(root, 'binary', targetOsType.name)), + Flavor.flutter => Directory(switch (targetOsType) { + TargetOsType.android => path.join(root, 'android', 'src', 'main', 'cpp', 'lib'), + TargetOsType.ios => path.join(root, 'ios'), + TargetOsType.macos => path.join(root, 'macos'), + TargetOsType.linux => path.join(root, 'linux', 'binary', 'linux'), + TargetOsType.windows => path.join(root, 'windows', 'binary', 'windows'), + }), + }; } Future shouldSkipDownload(String binariesPath, String expectedVersion) async { @@ -105,31 +113,29 @@ class InstallCommand extends Command { if (packageConfig == null) { abort('Run `dart pub get`'); } - final package = packageConfig.packages.where((p) => p.name == name).singleOrNull; + final package = packageConfig[name]; if (package == null) { abort('$name package not found in dependencies. Add $name package to your pubspec.yaml'); } return Directory.fromUri(package.root); } - Future parsePubspec(File file) async { + Pubspec parsePubspec(File file) { try { - return Pubspec.parse(await file.readAsString(), sourceUrl: file.uri); - } on Exception catch (e) { - throw Exception('Error parsing package pubspec at ${file.parent}. Error $e'); + return Pubspec.parse(file.readAsStringSync(), sourceUrl: file.uri); + } catch (e) { + abort('Error parsing package pubspec at ${file.parent}. Error $e'); } } @override FutureOr run() async { - final pubspec = await parsePubspec(File('pubspec.yaml')); - final flavor = pubspec.dependencies['flutter'] == null ? Flavor.dart : Flavor.flutter; - options = parseOptionsResult(argResults!); - validateOptions(flavor); final flavorName = flavor.packageName; final realmDependency = pubspec.dependencyOverrides[flavorName] ?? pubspec.dependencies[flavorName]; + print(pubspec.dependencyOverrides.values.join('\n')); + print(realmDependency); if (realmDependency is PathDependency) { print('Path dependency for $flavorName found. Skipping install of native lib (assuming local development)'); return; @@ -140,26 +146,16 @@ class InstallCommand extends Command { } final realmPackagePath = await getPackagePath(flavorName); - final realmPubspec = await parsePubspec(File(path.join(realmPackagePath.path, "pubspec.yaml"))); + final realmPubspec = parsePubspec(File(path.join(realmPackagePath.path, "pubspec.yaml"))); - final binaryPath = getBinaryPath(realmPackagePath, isFlutter: flavor == Flavor.flutter); + final binaryPath = getBinaryPath(realmPackagePath, flavor); print(binaryPath); - final archiveName = '${options.targetOsType!.name}.tar.gz'; + final archiveName = '${targetOsType.name}.tar.gz'; await downloadAndExtractBinaries(binaryPath, realmPubspec.version!, archiveName); print('Realm install command finished.'); } - void validateOptions(Flavor flavor) { - final targetOs = flavor == Flavor.dart ? getTargetOS() : options.targetOsType; - if (targetOs == null) { - abort('Target OS not specified'); - } - options.targetOsType = targetOs; - } - - TargetOsType getTargetOS() => Platform.operatingSystem.asTargetOsType ?? (throw UnsupportedError('Unsupported platform ${Platform.operatingSystem}')); - Never abort(String error) { print(error); print(usage); diff --git a/packages/realm_dart/lib/src/cli/install/options.dart b/packages/realm_dart/lib/src/cli/install/options.dart index f9c1440b6..59b35e96f 100644 --- a/packages/realm_dart/lib/src/cli/install/options.dart +++ b/packages/realm_dart/lib/src/cli/install/options.dart @@ -8,7 +8,10 @@ part 'options.g.dart'; @CliOptions() class Options { - @CliOption(help: 'The target OS to install binaries for.', abbr: 't') + @CliOption(help: 'The flavor to install binaries for.', abbr: 'f', provideDefaultToOverride: true) + Flavor? flavor; + + @CliOption(help: 'The target OS to install binaries for.', abbr: 't', provideDefaultToOverride: true) TargetOsType? targetOsType; // use to debug install command @@ -23,6 +26,9 @@ class Options { String get usage => _$parserForOptions.usage; -ArgParser populateOptionsParser(ArgParser p) => _$populateOptionsParser(p); +ArgParser populateOptionsParser(ArgParser parser, { + TargetOsType? targetOsTypeDefaultOverride, + Flavor? flavorDefaultOverride, +}) => _$populateOptionsParser(parser, targetOsTypeDefaultOverride: targetOsTypeDefaultOverride, flavorDefaultOverride: flavorDefaultOverride); Options parseOptionsResult(ArgResults results) => _$parseOptionsResult(results); diff --git a/packages/realm_dart/lib/src/cli/install/options.g.dart b/packages/realm_dart/lib/src/cli/install/options.g.dart index f0a4e6e05..d6a8b6f7f 100644 --- a/packages/realm_dart/lib/src/cli/install/options.g.dart +++ b/packages/realm_dart/lib/src/cli/install/options.g.dart @@ -30,7 +30,15 @@ Options _$parseOptionsResult(ArgResults result) => Options( ), force: result['force'] as bool, debug: result['debug'] as bool, - ); + )..flavor = _$nullableEnumValueHelperNullable( + _$FlavorEnumMapBuildCli, + result['flavor'] as String?, + ); + +const _$FlavorEnumMapBuildCli = { + Flavor.flutter: 'flutter', + Flavor.dart: 'dart' +}; const _$TargetOsTypeEnumMapBuildCli = { TargetOsType.android: 'android', @@ -40,23 +48,36 @@ const _$TargetOsTypeEnumMapBuildCli = { TargetOsType.windows: 'windows' }; -ArgParser _$populateOptionsParser(ArgParser parser) => parser - ..addOption( - 'target-os-type', - abbr: 't', - help: 'The target OS to install binaries for.', - allowed: ['android', 'ios', 'linux', 'macos', 'windows'], - ) - ..addFlag( - 'debug', - help: 'Download binary from http://localhost:8000/.', - hide: true, - ) - ..addFlag( - 'force', - help: 'Force install, even if we would normally skip it.', - hide: true, - ); +ArgParser _$populateOptionsParser( + ArgParser parser, { + Flavor? flavorDefaultOverride, + TargetOsType? targetOsTypeDefaultOverride, +}) => + parser + ..addOption( + 'flavor', + abbr: 'f', + help: 'The flavor to install binaries for.', + defaultsTo: _$FlavorEnumMapBuildCli[flavorDefaultOverride], + allowed: ['flutter', 'dart'], + ) + ..addOption( + 'target-os-type', + abbr: 't', + help: 'The target OS to install binaries for.', + defaultsTo: _$TargetOsTypeEnumMapBuildCli[targetOsTypeDefaultOverride], + allowed: ['android', 'ios', 'linux', 'macos', 'windows'], + ) + ..addFlag( + 'debug', + help: 'Download binary from http://localhost:8000/.', + hide: true, + ) + ..addFlag( + 'force', + help: 'Force install, even if we would normally skip it.', + hide: true, + ); final _$parserForOptions = _$populateOptionsParser(ArgParser()); diff --git a/packages/realm_dart/lib/src/init.dart b/packages/realm_dart/lib/src/init.dart index 21de3e93a..075ca4764 100644 --- a/packages/realm_dart/lib/src/init.dart +++ b/packages/realm_dart/lib/src/init.dart @@ -4,45 +4,61 @@ import 'dart:ffi'; import 'dart:io'; +import 'package:package_config/package_config.dart'; import 'package:path/path.dart' as p; import '../realm.dart' as realm show isFlutterPlatform; -import '../realm.dart' show realmBinaryName; import 'cli/common/target_os_type.dart'; import 'cli/metrics/metrics_command.dart'; import 'cli/metrics/options.dart'; import 'realm_class.dart'; +const realmBinaryName = 'realm_dart'; +final targetOsType = Platform.operatingSystem.asTargetOsType ?? _platformNotSupported(); +final nativeLibraryName = _getLibName(realmBinaryName); + DynamicLibrary? _library; -String _getPluginPath(String libName) { - if (Platform.isAndroid) { - return libName; - } - if (Platform.isLinux) { - return '$_exeDirName/lib/$libName'; - } - if (Platform.isMacOS) { - return '$_exeDirName/../Frameworks/$libName'; - } - if (Platform.isIOS) { - return '$_exeDirName/Frameworks/realm_dart.framework/$libName'; - } - if (Platform.isWindows) { - return libName; +String _getLibPathFlutter() { + final root = _exeDirName; + return switch (targetOsType) { + TargetOsType.android => nativeLibraryName, + TargetOsType.ios => p.join(root, 'Frameworks', 'realm_dart.framework', nativeLibraryName), + TargetOsType.linux => p.join(root, 'lib', nativeLibraryName), + TargetOsType.macos => p.join(p.dirname(root), 'Frameworks', nativeLibraryName), + TargetOsType.windows => nativeLibraryName, + }; +} + +String _getLibPathFlutterTest(Package realmPackage) { + assert(realmPackage.name == 'realm'); + final root = p.join(realmPackage.root.toFilePath(), targetOsType.name); + return switch (targetOsType) { + TargetOsType.linux => p.join(root, 'binary', 'linux', nativeLibraryName), + TargetOsType.macos => p.join(root, nativeLibraryName), + TargetOsType.windows => p.join(root, 'binary', 'windows', nativeLibraryName), + _ => _platformNotSupported(), + }; +} + +String _getLibPathDart(Package realmDartPackage) { + assert(realmDartPackage.name == 'realm_dart'); + final root = p.join(realmDartPackage.root.toFilePath(), 'binary', targetOsType.name); + if (targetOsType.isDesktop) { + return p.join(root, nativeLibraryName); } _platformNotSupported(); } bool get isFlutterPlatform => realm.isFlutterPlatform; -String _getLibName(String stem) { - if (Platform.isMacOS) return 'lib$stem.dylib'; - if (Platform.isIOS) return stem; - if (Platform.isWindows) return '$stem.dll'; - if (Platform.isAndroid || Platform.isLinux) return 'lib$stem.so'; - _platformNotSupported(); // we don't support Fuchsia yet -} +String _getLibName(String stem) => switch (targetOsType) { + TargetOsType.android => 'lib$stem.so', + TargetOsType.ios => stem, // xcframeworks are a directory + TargetOsType.linux => 'lib$stem.so', + TargetOsType.macos => 'lib$stem.dylib', + TargetOsType.windows => '$stem.dll', + }; String? _getNearestProjectRoot(String dir) { while (dir != p.dirname(dir)) { @@ -52,17 +68,24 @@ String? _getNearestProjectRoot(String dir) { return null; } +File _getPackageConfigJson(Directory d) { + final root = _getNearestProjectRoot(d.path); + if (root != null) { + final file = File(p.join(root, '.dart_tool', 'package_config.json')); + if (file.existsSync()) return file; + } + throw StateError('Could not find package_config.json'); +} + Never _platformNotSupported() => throw UnsupportedError('Platform ${Platform.operatingSystem} is not supported'); String get _exeDirName => p.dirname(Platform.resolvedExecutable); DynamicLibrary _openRealmLib() { - final libName = _getLibName(realmBinaryName); - DynamicLibrary? tryOpen(String path) { try { return DynamicLibrary.open(path); - } on Error catch (_) { + } catch (_) { return null; } } @@ -70,7 +93,7 @@ DynamicLibrary _openRealmLib() { Never throwError(Iterable candidatePaths) { throw RealmError( [ - 'Could not open $libName. Tried:', + 'Could not open $nativeLibraryName. Tried:', candidatePaths.map((p) => '- "$p"').join('\n'), isFlutterPlatform // ? 'Hint: Did you forget to add a dependency on the realm package?' @@ -79,23 +102,36 @@ DynamicLibrary _openRealmLib() { ); } - if (isFlutterPlatform) { - final path = _getPluginPath(libName); - return tryOpen(path) ?? throwError([path]); - } else { - final root = _getNearestProjectRoot(Platform.script.path) ?? _getNearestProjectRoot(p.current); - final candidatePaths = [ - libName, // just ask OS.. - p.join(_exeDirName, libName), // try finding it next to the executable - if (root != null) p.join(root, 'binary', Platform.operatingSystem, libName), // try finding it relative to project - ]; - DynamicLibrary? lib; - for (final path in candidatePaths) { - lib = tryOpen(path); - if (lib != null) return lib; - } - throwError(candidatePaths); + DynamicLibrary open(String path) => tryOpen(path) ?? throwError([path]); + + final isFlutterTest = Platform.environment.containsKey('FLUTTER_TEST'); + if (isFlutterPlatform && !isFlutterTest) { + return open(_getLibPathFlutter()); + } + + // NOTE: This needs to be sync, so we cannot use findPackageConfig + final packageConfigFile = _getPackageConfigJson(Directory.current); + final packageConfig = PackageConfig.parseBytes(packageConfigFile.readAsBytesSync(), packageConfigFile.uri); + + if (isFlutterTest) { + final realmPackage = packageConfig['realm']!; + return open(_getLibPathFlutterTest(realmPackage)); + } + + final realmDartPackage = packageConfig['realm_dart']!; + + // else plain dart + final candidatePaths = [ + nativeLibraryName, // just ask OS.. + p.join(_exeDirName, nativeLibraryName), // try finding it next to the executable + _getLibPathDart(realmDartPackage), // try finding it in the package + ]; + DynamicLibrary? lib; + for (final path in candidatePaths) { + lib = tryOpen(path); + if (lib != null) return lib; } + throwError(candidatePaths); } /// @nodoc diff --git a/packages/realm_dart/lib/src/realm_dart.dart b/packages/realm_dart/lib/src/realm_dart.dart index 9de3c2278..5c3c9c7c4 100644 --- a/packages/realm_dart/lib/src/realm_dart.dart +++ b/packages/realm_dart/lib/src/realm_dart.dart @@ -6,6 +6,3 @@ export 'realm_class.dart' hide RealmInternal; /// @nodoc // is Realm loaded in Flutter or Dart const bool isFlutterPlatform = false; - -/// @nodoc -const String realmBinaryName = 'realm_dart'; diff --git a/packages/realm_dart/lib/src/realm_flutter.dart b/packages/realm_dart/lib/src/realm_flutter.dart index cb7b7e9d6..63202c442 100644 --- a/packages/realm_dart/lib/src/realm_flutter.dart +++ b/packages/realm_dart/lib/src/realm_flutter.dart @@ -6,6 +6,3 @@ export 'realm_class.dart' hide RealmInternal; /// @nodoc // is Realm loaded in Flutter or Dart const bool isFlutterPlatform = true; - -/// @nodoc -const String realmBinaryName = 'realm_dart'; From 641956d46d512c92234991c45e8288d433e0a6bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Thu, 30 May 2024 17:08:34 +0200 Subject: [PATCH 05/16] RDART-1020: Fix writeAsync behaviour (#1666) * Add test that illustrates problem with async callback to writeAsync * Fix async callback issue * Update CHANGELOG * Add assert that callback to write is not async * Handle Never, which is a subtype of any type, but not a future * Disallow async callbacks even on writeAsync * Drop new async callback tests again --- CHANGELOG.md | 1 + packages/realm_dart/lib/src/realm_class.dart | 6 +++++- packages/realm_dart/test/realm_test.dart | 12 +++++++++++- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8550b9c42..29e462d70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * None ### Fixed +* `Realm.writeAsync` did not handle async callbacks (`Future Function()`) correctly. (Issue [#1667](https://github.com/realm/realm-dart/issues/1667)) * Fixed an issue that would cause macOS apps to be rejected with `Invalid Code Signing Entitlements` error. (Issue [#1679](https://github.com/realm/realm-dart/issues/1679)) * Fixed a regression that makes it inconvenient to run unit tests using realm. (Issue [#1619](https://github.com/realm/realm-dart/issues/1619)) diff --git a/packages/realm_dart/lib/src/realm_class.dart b/packages/realm_dart/lib/src/realm_class.dart index ad4c26862..5cc18c3fc 100644 --- a/packages/realm_dart/lib/src/realm_class.dart +++ b/packages/realm_dart/lib/src/realm_class.dart @@ -355,11 +355,15 @@ class Realm { /// Checks whether the `Realm` is in write transaction. bool get isInTransaction => handle.isWritable; + bool _isSubtype() => [] is List; + bool _isFuture() => T != Never && _isSubtype(); + /// Synchronously calls the provided callback inside a write transaction. /// /// If no exception is thrown from within the callback, the transaction will be committed. /// It is more efficient to update several properties or even create multiple objects in a single write transaction. T write(T Function() writeCallback) { + assert(!_isFuture(), 'writeCallback must be synchronous'); final transaction = beginWrite(); try { @@ -388,8 +392,8 @@ class Realm { /// Executes the provided [writeCallback] in a temporary write transaction. Both acquiring the write /// lock and committing the transaction will be done asynchronously. Future writeAsync(T Function() writeCallback, [CancellationToken? cancellationToken]) async { + assert(!_isFuture(), 'writeCallback must be synchronous'); final transaction = await beginWriteAsync(cancellationToken); - try { T result = writeCallback(); await transaction.commitAsync(cancellationToken); diff --git a/packages/realm_dart/test/realm_test.dart b/packages/realm_dart/test/realm_test.dart index d8c946499..cb7c094de 100644 --- a/packages/realm_dart/test/realm_test.dart +++ b/packages/realm_dart/test/realm_test.dart @@ -1071,7 +1071,7 @@ void main() { expect(realm2.all().length, 0); }); - test("Realm.writeAsync with multiple transactions doesnt't deadlock", () async { + test("Realm.writeAsync with multiple transactions doesn't deadlock", () async { final realm = getRealm(Configuration.local([Person.schema])); final t1 = await realm.beginWriteAsync(); realm.add(Person('Marco')); @@ -1200,6 +1200,16 @@ void main() { expect(realm.isInTransaction, false); }); + test('Realm.writeAsync with async callback fails with assert', () async { + final realm = getRealm(Configuration.local([Person.schema])); + await expectLater(realm.writeAsync(() async {}), throwsA(isA())); + }); + + test('Realm.write with async callback', () { + final realm = getRealm(Configuration.local([Person.schema])); + expect(() => realm.write(() async {}), throwsA(isA())); + }); + test('Transaction.commitAsync with a canceled token throws', () async { final realm = getRealm(Configuration.local([Person.schema])); From 3e8ad85c91dfa0bf6cac7173137a9f42c1c462e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Thu, 6 Jun 2024 18:03:49 +0200 Subject: [PATCH 06/16] RDART-1052: Update realm-core to v14.9.0 (#1704) * Update realm-core to v14.9.0 * Fix test case * Update CHANGELOG --- CHANGELOG.md | 8 ++++++-- packages/realm_dart/src/realm-core | 2 +- packages/realm_dart/test/results_test.dart | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29e462d70..ef36fffd0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,18 +1,22 @@ ## vNext (TBD) ### Enhancements -* None +* Report the originating error that caused a client reset to occur. (Core 14.9.0) ### Fixed * `Realm.writeAsync` did not handle async callbacks (`Future Function()`) correctly. (Issue [#1667](https://github.com/realm/realm-dart/issues/1667)) * Fixed an issue that would cause macOS apps to be rejected with `Invalid Code Signing Entitlements` error. (Issue [#1679](https://github.com/realm/realm-dart/issues/1679)) * Fixed a regression that makes it inconvenient to run unit tests using realm. (Issue [#1619](https://github.com/realm/realm-dart/issues/1619)) +* After compacting, a file upgrade would be triggered. This could cause loss of data if schema mode is SoftResetFile (Core 14.9.0) +* A non-streaming progress notifier would not immediately call its callback after registration. Instead you would have to wait for a download message to be received to get your first update - if you were already caught up when you registered the notifier you could end up waiting a long time for the server to deliver a download that would call/expire your notifier (Core 14.8.0). +* Comparing a numeric property with an argument list containing a string would throw. (Core 14.8.0) ### Compatibility * Realm Studio: 15.0.0 or later. +* Fileformat: Generates files with format v24. Reads and automatically upgrade from fileformat v10. ### Internal -* Using Core 14.7.0. +* Using Core 14.9.0. * Disabled codesigning of Apple binaries. (Issue [#1679](https://github.com/realm/realm-dart/issues/1679)) * Drop building xcframework for catalyst. (Issue [#1695](https://github.com/realm/realm-dart/issues/1695)) * Using xcode 15.4 for native build. (Issue [#1547](https://github.com/realm/realm-dart/issues/1547)) diff --git a/packages/realm_dart/src/realm-core b/packages/realm_dart/src/realm-core index 1f2a4c741..f3d7ae5f9 160000 --- a/packages/realm_dart/src/realm-core +++ b/packages/realm_dart/src/realm-core @@ -1 +1 @@ -Subproject commit 1f2a4c74112c1988b00a862d348e3f91200c5c4b +Subproject commit f3d7ae5f9f31d90b327a64536bb7801cc69fd85b diff --git a/packages/realm_dart/test/results_test.dart b/packages/realm_dart/test/results_test.dart index a22f883e1..8dbea271f 100644 --- a/packages/realm_dart/test/results_test.dart +++ b/packages/realm_dart/test/results_test.dart @@ -242,7 +242,7 @@ void main() { var config = Configuration.local([Dog.schema, Person.schema]); var realm = getRealm(config); realm.write(() => realm.add(Dog("Foxi"))); - expect(() => realm.all().query(r'age == $0', [true]), throws("Unsupported comparison between type")); + expect(() => realm.all().query(r'age == $0', [true]), throws("Cannot compare argument")); }); test('Results sort', () { From abb8ed1451601df2b4b7c8b97fd70ad603ac84be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Thu, 6 Jun 2024 22:58:19 +0200 Subject: [PATCH 07/16] RDART-866: Minimal web support (#1699) * Take 1 * Take 2 * Avoid SyncErrorCode dep on realm_binding.dart * Avoid dep on realm_library.dart * Avoid dep on from_native.dart * Split convert.dart to avoid dart:ffi dep * BREAKING CHANGE! (WIP) Avoid dep on dart:io - AppConfiguration.baseFilePath is now a String instead of a Directory. - AppConfiguration.httpClient is now a Client instead of a HttpClient. This is to avoid a dependency on dart:io in order to be compatible with package web. This is still work in progress. Missing is: - Support for timeout on web requests - Custom certificate for windows. * Refactor test to avoid direct deps on dart:io and dart:ffi * Avoid dart:ffi dep on Scheduler * Split decimal128.dart for web and native * Split realm_core.dart for web and native * Move int literals with more than 53 bit into platform_util.dart * Add compile for web CI job * Split defaultClient * Update CHANGELOG * Drop some default icons for web tests * PR feedback --- .github/workflows/ci.yml | 27 ++++++ CHANGELOG.md | 9 ++ packages/realm/tests/pubspec.yaml | 5 +- packages/realm/tests/web/index.html | 38 +++++++++ packages/realm/tests/web/manifest.json | 35 ++++++++ packages/realm_dart/lib/realm.dart | 2 +- packages/realm_dart/lib/src/app.dart | 76 +++-------------- packages/realm_dart/lib/src/collections.dart | 2 +- .../realm_dart/lib/src/configuration.dart | 48 +++++++---- packages/realm_dart/lib/src/convert.dart | 8 ++ packages/realm_dart/lib/src/credentials.dart | 4 +- .../lib/src/handles/app_handle.dart | 39 +++++++++ .../src/handles/async_open_task_handle.dart | 20 +++++ .../handles/collection_changes_handle.dart | 9 ++ .../lib/src/handles/credentials_handle.dart | 29 +++++++ .../lib/src/handles/decimal128.dart | 79 ++++++++++++++++++ .../lib/src/handles/default_client.dart | 8 ++ .../lib/src/handles/handle_base.dart | 9 ++ .../lib/src/handles/list_handle.dart | 32 ++++++++ .../lib/src/handles/map_changes_handle.dart | 9 ++ .../lib/src/handles/map_handle.dart | 31 +++++++ .../mutable_subscription_set_handle.dart | 18 ++++ .../src/{ => handles}/native/app_handle.dart | 50 ++++++++--- .../native/async_open_task_handle.dart | 24 ++++-- .../native/collection_changes_handle.dart | 7 +- .../native/collection_handle_base.dart | 11 ++- .../{ => handles}/native/config_handle.dart | 28 +++---- .../lib/src/{ => handles}/native/convert.dart | 0 .../{ => handles}/native/convert_native.dart | 0 .../native/credentials_handle.dart | 6 +- .../src/{ => handles}/native/decimal128.dart | 36 +++++--- .../src/handles/native/default_client.dart | 60 ++++++++++++++ .../{ => handles}/native/error_handling.dart | 2 +- .../lib/src/{ => handles}/native/ffi.dart | 0 .../src/{ => handles}/native/from_native.dart | 82 +++++++++++-------- .../src/{ => handles}/native/handle_base.dart | 42 ++++++---- .../native/http_transport_handle.dart | 74 +++++++---------- .../lib/src/{ => handles/native}/init.dart | 13 +-- .../src/{ => handles}/native/list_handle.dart | 26 ++++-- .../native/map_changes_handle.dart | 10 ++- .../src/{ => handles}/native/map_handle.dart | 25 +++++- .../mutable_subscription_set_handle.dart | 25 ++++-- .../native/notification_token_handle.dart | 6 +- .../handles/native/object_changes_handle.dart | 32 ++++++++ .../{ => handles}/native/object_handle.dart | 58 +++++++------ .../{ => handles}/native/query_handle.dart | 0 .../{ => handles}/native/realm_bindings.dart | 0 .../src/{ => handles}/native/realm_core.dart | 37 +++++++-- .../{ => handles}/native/realm_handle.dart | 58 +++++++++++-- .../{ => handles}/native/realm_library.dart | 7 +- .../{ => handles}/native/results_handle.dart | 18 +++- .../{ => handles}/native/rooted_handle.dart | 0 .../src/handles/native/scheduler_handle.dart | 30 +++++++ .../{ => handles}/native/schema_handle.dart | 6 +- .../{ => handles}/native/session_handle.dart | 33 +++++--- .../src/{ => handles}/native/set_handle.dart | 20 ++++- .../native/subscription_handle.dart | 14 +++- .../native/subscription_set_handle.dart | 24 ++++-- .../src/{ => handles}/native/to_native.dart | 6 +- .../src/{ => handles}/native/user_handle.dart | 58 ++++++++----- .../handles/notification_token_handle.dart | 6 ++ .../src/handles/object_changes_handle.dart | 9 ++ .../lib/src/handles/object_handle.dart | 55 +++++++++++++ .../lib/src/handles/realm_core.dart | 33 ++++++++ .../lib/src/handles/realm_handle.dart | 69 ++++++++++++++++ .../lib/src/handles/results_handle.dart | 28 +++++++ .../lib/src/handles/scheduler_handle.dart | 14 ++++ .../lib/src/handles/schema_handle.dart | 11 +++ .../lib/src/handles/session_handle.dart | 35 ++++++++ .../lib/src/handles/set_handle.dart | 34 ++++++++ .../lib/src/handles/subscription_handle.dart | 16 ++++ .../src/handles/subscription_set_handle.dart | 31 +++++++ .../lib/src/handles/user_handle.dart | 33 ++++++++ .../lib/src/handles/web/app_handle.dart | 11 +++ .../handles/web/async_open_task_handle.dart | 19 +++++ .../web/collection_changes_handle.dart | 7 ++ .../src/handles/web/credentials_handle.dart | 25 ++++++ .../lib/src/handles/web/decimal128.dart | 39 +++++++++ .../lib/src/handles/web/default_client.dart | 7 ++ .../lib/src/handles/web/handle_base.dart | 12 +++ .../lib/src/handles/web/list_handle.dart | 7 ++ .../src/handles/web/map_changes_handle.dart | 7 ++ .../lib/src/handles/web/map_handle.dart | 7 ++ .../web/mutable_subscription_set_handle.dart | 7 ++ .../web/notification_token_handle.dart | 7 ++ .../handles/web/object_changes_handle.dart | 7 ++ .../lib/src/handles/web/object_handle.dart | 7 ++ .../lib/src/handles/web/realm_core.dart | 14 ++++ .../lib/src/handles/web/realm_handle.dart | 11 +++ .../lib/src/handles/web/results_handle.dart | 7 ++ .../lib/src/handles/web/scheduler_handle.dart | 11 +++ .../lib/src/handles/web/schema_handle.dart | 11 +++ .../lib/src/handles/web/session_handle.dart | 7 ++ .../lib/src/handles/web/set_handle.dart | 7 ++ .../src/handles/web/subscription_handle.dart | 7 ++ .../handles/web/subscription_set_handle.dart | 7 ++ .../lib/src/handles/web/user_handle.dart | 7 ++ .../src/handles/web/web_not_supported.dart | 4 + packages/realm_dart/lib/src/list.dart | 12 +-- packages/realm_dart/lib/src/logging.dart | 2 +- packages/realm_dart/lib/src/map.dart | 13 ++- packages/realm_dart/lib/src/migration.dart | 2 +- .../lib/src/native/scheduler_handle.dart | 22 ----- packages/realm_dart/lib/src/realm_class.dart | 48 ++++------- packages/realm_dart/lib/src/realm_object.dart | 19 +++-- packages/realm_dart/lib/src/realm_web.dart | 9 ++ packages/realm_dart/lib/src/results.dart | 10 +-- packages/realm_dart/lib/src/scheduler.dart | 7 +- packages/realm_dart/lib/src/session.dart | 40 ++++----- packages/realm_dart/lib/src/set.dart | 16 ++-- packages/realm_dart/lib/src/subscription.dart | 14 ++-- packages/realm_dart/lib/src/user.dart | 2 +- packages/realm_dart/pubspec.yaml | 1 + packages/realm_dart/test/app_test.dart | 25 +++--- packages/realm_dart/test/baas_helper.dart | 17 ++-- .../realm_dart/test/client_reset_test.dart | 6 +- .../realm_dart/test/configuration_test.dart | 26 +++--- packages/realm_dart/test/decimal128_test.dart | 4 +- packages/realm_dart/test/embedded_test.dart | 8 +- .../realm_dart/test/realm_logger_test.dart | 3 +- packages/realm_dart/test/realm_map_test.dart | 10 +-- packages/realm_dart/test/realm_test.dart | 50 ++++++----- .../realm_dart/test/subscription_test.dart | 13 ++- packages/realm_dart/test/test.dart | 52 ++++-------- packages/realm_dart/test/user_test.dart | 3 +- .../test/utils/native/platform_util.dart | 56 +++++++++++++ .../realm_dart/test/utils/platform_util.dart | 29 +++++++ .../test/utils/web/platform_util.dart | 11 +++ 128 files changed, 2013 insertions(+), 618 deletions(-) create mode 100644 packages/realm/tests/web/index.html create mode 100644 packages/realm/tests/web/manifest.json create mode 100644 packages/realm_dart/lib/src/convert.dart create mode 100644 packages/realm_dart/lib/src/handles/app_handle.dart create mode 100644 packages/realm_dart/lib/src/handles/async_open_task_handle.dart create mode 100644 packages/realm_dart/lib/src/handles/collection_changes_handle.dart create mode 100644 packages/realm_dart/lib/src/handles/credentials_handle.dart create mode 100644 packages/realm_dart/lib/src/handles/decimal128.dart create mode 100644 packages/realm_dart/lib/src/handles/default_client.dart create mode 100644 packages/realm_dart/lib/src/handles/handle_base.dart create mode 100644 packages/realm_dart/lib/src/handles/list_handle.dart create mode 100644 packages/realm_dart/lib/src/handles/map_changes_handle.dart create mode 100644 packages/realm_dart/lib/src/handles/map_handle.dart create mode 100644 packages/realm_dart/lib/src/handles/mutable_subscription_set_handle.dart rename packages/realm_dart/lib/src/{ => handles}/native/app_handle.dart (93%) rename packages/realm_dart/lib/src/{ => handles}/native/async_open_task_handle.dart (84%) rename packages/realm_dart/lib/src/{ => handles}/native/collection_changes_handle.dart (94%) rename packages/realm_dart/lib/src/{ => handles}/native/collection_handle_base.dart (89%) rename packages/realm_dart/lib/src/{ => handles}/native/config_handle.dart (95%) rename packages/realm_dart/lib/src/{ => handles}/native/convert.dart (100%) rename packages/realm_dart/lib/src/{ => handles}/native/convert_native.dart (100%) rename packages/realm_dart/lib/src/{ => handles}/native/credentials_handle.dart (95%) rename packages/realm_dart/lib/src/{ => handles}/native/decimal128.dart (84%) create mode 100644 packages/realm_dart/lib/src/handles/native/default_client.dart rename packages/realm_dart/lib/src/{ => handles}/native/error_handling.dart (97%) rename packages/realm_dart/lib/src/{ => handles}/native/ffi.dart (100%) rename packages/realm_dart/lib/src/{ => handles}/native/from_native.dart (77%) rename packages/realm_dart/lib/src/{ => handles}/native/handle_base.dart (64%) rename packages/realm_dart/lib/src/{ => handles}/native/http_transport_handle.dart (71%) rename packages/realm_dart/lib/src/{ => handles/native}/init.dart (94%) rename packages/realm_dart/lib/src/{ => handles}/native/list_handle.dart (91%) rename packages/realm_dart/lib/src/{ => handles}/native/map_changes_handle.dart (92%) rename packages/realm_dart/lib/src/{ => handles}/native/map_handle.dart (93%) rename packages/realm_dart/lib/src/{ => handles}/native/mutable_subscription_set_handle.dart (74%) rename packages/realm_dart/lib/src/{ => handles}/native/notification_token_handle.dart (88%) create mode 100644 packages/realm_dart/lib/src/handles/native/object_changes_handle.dart rename packages/realm_dart/lib/src/{ => handles}/native/object_handle.dart (87%) rename packages/realm_dart/lib/src/{ => handles}/native/query_handle.dart (100%) rename packages/realm_dart/lib/src/{ => handles}/native/realm_bindings.dart (100%) rename packages/realm_dart/lib/src/{ => handles}/native/realm_core.dart (90%) rename packages/realm_dart/lib/src/{ => handles}/native/realm_handle.dart (93%) rename packages/realm_dart/lib/src/{ => handles}/native/realm_library.dart (90%) rename packages/realm_dart/lib/src/{ => handles}/native/results_handle.dart (90%) rename packages/realm_dart/lib/src/{ => handles}/native/rooted_handle.dart (100%) create mode 100644 packages/realm_dart/lib/src/handles/native/scheduler_handle.dart rename packages/realm_dart/lib/src/{ => handles}/native/schema_handle.dart (95%) rename packages/realm_dart/lib/src/{ => handles}/native/session_handle.dart (90%) rename packages/realm_dart/lib/src/{ => handles}/native/set_handle.dart (91%) rename packages/realm_dart/lib/src/{ => handles}/native/subscription_handle.dart (73%) rename packages/realm_dart/lib/src/{ => handles}/native/subscription_set_handle.dart (87%) rename packages/realm_dart/lib/src/{ => handles}/native/to_native.dart (98%) rename packages/realm_dart/lib/src/{ => handles}/native/user_handle.dart (89%) create mode 100644 packages/realm_dart/lib/src/handles/notification_token_handle.dart create mode 100644 packages/realm_dart/lib/src/handles/object_changes_handle.dart create mode 100644 packages/realm_dart/lib/src/handles/object_handle.dart create mode 100644 packages/realm_dart/lib/src/handles/realm_core.dart create mode 100644 packages/realm_dart/lib/src/handles/realm_handle.dart create mode 100644 packages/realm_dart/lib/src/handles/results_handle.dart create mode 100644 packages/realm_dart/lib/src/handles/scheduler_handle.dart create mode 100644 packages/realm_dart/lib/src/handles/schema_handle.dart create mode 100644 packages/realm_dart/lib/src/handles/session_handle.dart create mode 100644 packages/realm_dart/lib/src/handles/set_handle.dart create mode 100644 packages/realm_dart/lib/src/handles/subscription_handle.dart create mode 100644 packages/realm_dart/lib/src/handles/subscription_set_handle.dart create mode 100644 packages/realm_dart/lib/src/handles/user_handle.dart create mode 100644 packages/realm_dart/lib/src/handles/web/app_handle.dart create mode 100644 packages/realm_dart/lib/src/handles/web/async_open_task_handle.dart create mode 100644 packages/realm_dart/lib/src/handles/web/collection_changes_handle.dart create mode 100644 packages/realm_dart/lib/src/handles/web/credentials_handle.dart create mode 100644 packages/realm_dart/lib/src/handles/web/decimal128.dart create mode 100644 packages/realm_dart/lib/src/handles/web/default_client.dart create mode 100644 packages/realm_dart/lib/src/handles/web/handle_base.dart create mode 100644 packages/realm_dart/lib/src/handles/web/list_handle.dart create mode 100644 packages/realm_dart/lib/src/handles/web/map_changes_handle.dart create mode 100644 packages/realm_dart/lib/src/handles/web/map_handle.dart create mode 100644 packages/realm_dart/lib/src/handles/web/mutable_subscription_set_handle.dart create mode 100644 packages/realm_dart/lib/src/handles/web/notification_token_handle.dart create mode 100644 packages/realm_dart/lib/src/handles/web/object_changes_handle.dart create mode 100644 packages/realm_dart/lib/src/handles/web/object_handle.dart create mode 100644 packages/realm_dart/lib/src/handles/web/realm_core.dart create mode 100644 packages/realm_dart/lib/src/handles/web/realm_handle.dart create mode 100644 packages/realm_dart/lib/src/handles/web/results_handle.dart create mode 100644 packages/realm_dart/lib/src/handles/web/scheduler_handle.dart create mode 100644 packages/realm_dart/lib/src/handles/web/schema_handle.dart create mode 100644 packages/realm_dart/lib/src/handles/web/session_handle.dart create mode 100644 packages/realm_dart/lib/src/handles/web/set_handle.dart create mode 100644 packages/realm_dart/lib/src/handles/web/subscription_handle.dart create mode 100644 packages/realm_dart/lib/src/handles/web/subscription_set_handle.dart create mode 100644 packages/realm_dart/lib/src/handles/web/user_handle.dart create mode 100644 packages/realm_dart/lib/src/handles/web/web_not_supported.dart delete mode 100644 packages/realm_dart/lib/src/native/scheduler_handle.dart create mode 100644 packages/realm_dart/lib/src/realm_web.dart create mode 100644 packages/realm_dart/test/utils/native/platform_util.dart create mode 100644 packages/realm_dart/test/utils/platform_util.dart create mode 100644 packages/realm_dart/test/utils/web/platform_util.dart diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4cf172d84..4058963b5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -619,3 +619,30 @@ jobs: ** <{{refUrl}}|`{{ref}}` - {{description}}> {{#if description}}<{{diffUrl}}|branch: `{{diffRef}}`>{{/if}} + + web-compile: + name: Compile for web + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: "recursive" + + - name: Setup Flutter + uses: subosito/flutter-action@v2 + with: + channel: "stable" + + - name: Setup Melos + run: | + dart pub global activate melos + dart pub global run melos bootstrap + + - name: Compile to wasm + run: flutter build web --wasm -t integration_test/all_tests.dart + working-directory: packages/realm/tests/ + + - name: Compile to js + run: flutter build web -t integration_test/all_tests.dart + working-directory: packages/realm/tests/ diff --git a/CHANGELOG.md b/CHANGELOG.md index ef36fffd0..009d15b6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,16 @@ ## vNext (TBD) +### Breaking Changes +* To avoid dependency on `dart:io` + - `AppConfiguration.httpClient` is now of type [`Client`](https://pub.dev/documentation/http/latest/http/Client-class.html) and + - `AppConfiguration.baseFilePath` is now of type `String` + + (Issue [#1374](https://github.com/realm/realm-dart/issues/1374)) + ### Enhancements * Report the originating error that caused a client reset to occur. (Core 14.9.0) +* Allow the realm package, and code generated by realm_generator to be included when building + for web without breaking compilation. (Issue [#1374](https://github.com/realm/realm-dart/issues/1374)) ### Fixed * `Realm.writeAsync` did not handle async callbacks (`Future Function()`) correctly. (Issue [#1667](https://github.com/realm/realm-dart/issues/1667)) diff --git a/packages/realm/tests/pubspec.yaml b/packages/realm/tests/pubspec.yaml index fdbe289c0..ed5cb4b25 100644 --- a/packages/realm/tests/pubspec.yaml +++ b/packages/realm/tests/pubspec.yaml @@ -24,10 +24,11 @@ dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^3.0.1 - test: any - timezone: ^0.9.0 integration_test: sdk: flutter + test: any + timezone: ^0.9.0 + universal_platform: ^1.1.0 flutter: assets: diff --git a/packages/realm/tests/web/index.html b/packages/realm/tests/web/index.html new file mode 100644 index 000000000..4317b0bf5 --- /dev/null +++ b/packages/realm/tests/web/index.html @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + tests + + + + + + diff --git a/packages/realm/tests/web/manifest.json b/packages/realm/tests/web/manifest.json new file mode 100644 index 000000000..b314fa17c --- /dev/null +++ b/packages/realm/tests/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "tests", + "short_name": "tests", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/packages/realm_dart/lib/realm.dart b/packages/realm_dart/lib/realm.dart index 70bd5dc37..b6935627e 100644 --- a/packages/realm_dart/lib/realm.dart +++ b/packages/realm_dart/lib/realm.dart @@ -1,6 +1,6 @@ // Copyright 2021 MongoDB, Inc. // SPDX-License-Identifier: Apache-2.0 -//dart.library.cli is available only on dart desktop +// dart.library.cli is available only on dart desktop export 'src/realm_flutter.dart' if (dart.library.cli) 'src/realm_dart.dart'; export 'package:ejson/ejson.dart'; diff --git a/packages/realm_dart/lib/src/app.dart b/packages/realm_dart/lib/src/app.dart index 500a71e09..91dc6c734 100644 --- a/packages/realm_dart/lib/src/app.dart +++ b/packages/realm_dart/lib/src/app.dart @@ -1,73 +1,20 @@ // Copyright 2022 MongoDB, Inc. // SPDX-License-Identifier: Apache-2.0 -import 'dart:convert'; -import 'dart:io'; import 'dart:isolate'; +import 'package:http/http.dart'; import 'package:meta/meta.dart'; import 'package:path/path.dart' as path; import '../realm.dart'; import 'credentials.dart'; +import 'handles/app_handle.dart'; +import 'handles/default_client.dart'; +import 'handles/realm_core.dart'; import 'logging.dart'; -import 'native/app_handle.dart'; -import 'native/realm_core.dart'; import 'user.dart'; -final _defaultClient = () { - const isrgRootX1CertPEM = // The root certificate used by lets encrypt and hence MongoDB - ''' -subject=CN=ISRG Root X1,O=Internet Security Research Group,C=US -issuer=CN=DST Root CA X3,O=Digital Signature Trust Co. ------BEGIN CERTIFICATE----- -MIIFYDCCBEigAwIBAgIQQAF3ITfU6UK47naqPGQKtzANBgkqhkiG9w0BAQsFADA/ -MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT -DkRTVCBSb290IENBIFgzMB4XDTIxMDEyMDE5MTQwM1oXDTI0MDkzMDE4MTQwM1ow -TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh -cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwggIiMA0GCSqGSIb3DQEB -AQUAA4ICDwAwggIKAoICAQCt6CRz9BQ385ueK1coHIe+3LffOJCMbjzmV6B493XC -ov71am72AE8o295ohmxEk7axY/0UEmu/H9LqMZshftEzPLpI9d1537O4/xLxIZpL -wYqGcWlKZmZsj348cL+tKSIG8+TA5oCu4kuPt5l+lAOf00eXfJlII1PoOK5PCm+D -LtFJV4yAdLbaL9A4jXsDcCEbdfIwPPqPrt3aY6vrFk/CjhFLfs8L6P+1dy70sntK -4EwSJQxwjQMpoOFTJOwT2e4ZvxCzSow/iaNhUd6shweU9GNx7C7ib1uYgeGJXDR5 -bHbvO5BieebbpJovJsXQEOEO3tkQjhb7t/eo98flAgeYjzYIlefiN5YNNnWe+w5y -sR2bvAP5SQXYgd0FtCrWQemsAXaVCg/Y39W9Eh81LygXbNKYwagJZHduRze6zqxZ -Xmidf3LWicUGQSk+WT7dJvUkyRGnWqNMQB9GoZm1pzpRboY7nn1ypxIFeFntPlF4 -FQsDj43QLwWyPntKHEtzBRL8xurgUBN8Q5N0s8p0544fAQjQMNRbcTa0B7rBMDBc -SLeCO5imfWCKoqMpgsy6vYMEG6KDA0Gh1gXxG8K28Kh8hjtGqEgqiNx2mna/H2ql -PRmP6zjzZN7IKw0KKP/32+IVQtQi0Cdd4Xn+GOdwiK1O5tmLOsbdJ1Fu/7xk9TND -TwIDAQABo4IBRjCCAUIwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw -SwYIKwYBBQUHAQEEPzA9MDsGCCsGAQUFBzAChi9odHRwOi8vYXBwcy5pZGVudHJ1 -c3QuY29tL3Jvb3RzL2RzdHJvb3RjYXgzLnA3YzAfBgNVHSMEGDAWgBTEp7Gkeyxx -+tvhS5B1/8QVYIWJEDBUBgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEEAYLfEwEB -ATAwMC4GCCsGAQUFBwIBFiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2VuY3J5cHQu -b3JnMDwGA1UdHwQ1MDMwMaAvoC2GK2h0dHA6Ly9jcmwuaWRlbnRydXN0LmNvbS9E -U1RST09UQ0FYM0NSTC5jcmwwHQYDVR0OBBYEFHm0WeZ7tuXkAXOACIjIGlj26Ztu -MA0GCSqGSIb3DQEBCwUAA4IBAQAKcwBslm7/DlLQrt2M51oGrS+o44+/yQoDFVDC -5WxCu2+b9LRPwkSICHXM6webFGJueN7sJ7o5XPWioW5WlHAQU7G75K/QosMrAdSW -9MUgNTP52GE24HGNtLi1qoJFlcDyqSMo59ahy2cI2qBDLKobkx/J3vWraV0T9VuG -WCLKTVXkcGdtwlfFRjlBz4pYg1htmf5X6DYO8A4jqv2Il9DjXA6USbW1FzXSLr9O -he8Y4IWS6wY7bCkjCWDcRQJMEhg76fsO3txE+FiYruq9RUWhiF1myv4Q6W+CyBFC -Dfvp7OOGAN6dEOM4+qR9sdjoSYKEBpsr6GtPAQw4dy753ec5 ------END CERTIFICATE-----'''; - - if (Platform.isWindows) { - try { - final context = SecurityContext(withTrustedRoots: true); - context.setTrustedCertificatesBytes(const AsciiEncoder().convert(isrgRootX1CertPEM)); - return HttpClient(context: context); - } on TlsException catch (e) { - // certificate is already trusted. Nothing to do here - if (e.osError?.message.contains("CERT_ALREADY_IN_HASH_TABLE") != true) { - rethrow; - } - } - } - - return HttpClient(); -}(); - /// A class exposing configuration options for an [App] /// {@category Application} @immutable @@ -79,7 +26,7 @@ class AppConfiguration { /// /// This data includes metadata for users and synchronized Realms. If set, you must ensure that the [baseFilePath] /// directory exists. - final Directory baseFilePath; + final String baseFilePath; /// The [baseUrl] is the [Uri] used to reach the MongoDB Atlas. /// @@ -106,27 +53,27 @@ class AppConfiguration { /// Setting this will not change the encryption key for individual Realms, which is set in the [Configuration]. final List? metadataEncryptionKey; - /// The [HttpClient] that will be used for HTTP requests during authentication. + /// The [Client] that will be used for HTTP requests during authentication. /// /// You can use this to override the default http client handler and configure settings like proxies, /// client certificates, and cookies. While these are not required to connect to MongoDB Atlas under /// normal circumstances, they can be useful if client devices are behind corporate firewall or use /// a more complex networking setup. - final HttpClient httpClient; + final Client httpClient; /// Instantiates a new [AppConfiguration] with the specified appId. AppConfiguration( this.appId, { Uri? baseUrl, - Directory? baseFilePath, + String? baseFilePath, this.defaultRequestTimeout = const Duration(seconds: 60), this.metadataEncryptionKey, this.metadataPersistenceMode = MetadataPersistenceMode.plaintext, this.maxConnectionTimeout = const Duration(minutes: 2), - HttpClient? httpClient, + Client? httpClient, }) : baseUrl = baseUrl ?? Uri.parse(realmCore.getDefaultBaseUrl()), - baseFilePath = baseFilePath ?? Directory(path.dirname(Configuration.defaultRealmPath)), - httpClient = httpClient ?? _defaultClient { + baseFilePath = baseFilePath ?? path.dirname(Configuration.defaultRealmPath), + httpClient = httpClient ?? defaultClient { if (appId == '') { throw RealmException('Supplied appId must be a non-empty value'); } @@ -170,7 +117,6 @@ class App { App._(this._handle); static AppHandle _createApp(AppConfiguration configuration) { - configuration.baseFilePath.createSync(recursive: true); return AppHandle.from(configuration); } diff --git a/packages/realm_dart/lib/src/collections.dart b/packages/realm_dart/lib/src/collections.dart index 9609d1b06..51c7deffe 100644 --- a/packages/realm_dart/lib/src/collections.dart +++ b/packages/realm_dart/lib/src/collections.dart @@ -1,7 +1,7 @@ // Copyright 2022 MongoDB, Inc. // SPDX-License-Identifier: Apache-2.0 -import 'native/collection_changes_handle.dart'; +import 'handles/collection_changes_handle.dart'; /// Contains index information about objects that moved within the same collection. class Move { diff --git a/packages/realm_dart/lib/src/configuration.dart b/packages/realm_dart/lib/src/configuration.dart index ab182afec..ffd299b70 100644 --- a/packages/realm_dart/lib/src/configuration.dart +++ b/packages/realm_dart/lib/src/configuration.dart @@ -2,17 +2,14 @@ // SPDX-License-Identifier: Apache-2.0 import 'dart:async'; -import 'dart:io'; // ignore: no_leading_underscores_for_library_prefixes import 'package:path/path.dart' as _path; import 'app.dart'; -import 'init.dart'; +import 'handles/realm_core.dart'; import 'logging.dart'; -import 'native/from_native.dart'; -import 'native/realm_core.dart'; -import 'realm_class.dart'; +import 'realm_dart.dart'; import 'user.dart'; const encryptionKeySize = 64; @@ -85,11 +82,7 @@ abstract class Configuration { /// On Flutter Linux this is the `/home/username/.local/share/app_name` directory. /// On Dart standalone Windows, macOS and Linux this is the current directory. static String get defaultStoragePath { - if (isFlutterPlatform) { - return realmCore.getAppDirectory(); - } - - return Directory.current.path; + return realmCore.getAppDirectory(); } /// The platform dependent path to the default realm file. @@ -714,29 +707,52 @@ final class CompensatingWriteError extends SyncError { } } +/// @nodoc +class SyncErrorDetails { + final String message; + final SyncErrorCode code; + final String? path; + final bool isFatal; + final bool isClientResetRequested; + final String? originalFilePath; + final String? backupFilePath; + final List? compensatingWrites; + final Object? userError; + + SyncErrorDetails( + this.message, + this.code, + this.userError, { + this.path, + this.isFatal = false, + this.isClientResetRequested = false, + this.originalFilePath, + this.backupFilePath, + this.compensatingWrites, + }); +} + /// @nodoc extension SyncErrorInternal on SyncError { static SyncError createSyncError(SyncErrorDetails error, {App? app}) { //Client reset can be requested with isClientResetRequested disregarding the ErrorCode - SyncErrorCode errorCode = SyncErrorCode.fromInt(error.code); - - return switch (errorCode) { + return switch (error.code) { SyncErrorCode.autoClientResetFailed => ClientResetError._( error.message, - errorCode, + error.code, app, error.userError, originalFilePath: error.originalFilePath, backupFilePath: error.backupFilePath, ), SyncErrorCode.clientReset => - ClientResetError._(error.message, errorCode, app, error.userError, originalFilePath: error.originalFilePath, backupFilePath: error.backupFilePath), + ClientResetError._(error.message, error.code, app, error.userError, originalFilePath: error.originalFilePath, backupFilePath: error.backupFilePath), SyncErrorCode.compensatingWrite => CompensatingWriteError._( error.message, error.userError, compensatingWrites: error.compensatingWrites, ), - _ => SyncError._(error.message, errorCode, error.userError), + _ => SyncError._(error.message, error.code, error.userError), }; } } diff --git a/packages/realm_dart/lib/src/convert.dart b/packages/realm_dart/lib/src/convert.dart new file mode 100644 index 000000000..861a2719e --- /dev/null +++ b/packages/realm_dart/lib/src/convert.dart @@ -0,0 +1,8 @@ + +extension NullableObjectEx on T? { + U? convert(U Function(T) convertor) { + final self = this; + if (self == null) return null; + return convertor(self); + } +} diff --git a/packages/realm_dart/lib/src/credentials.dart b/packages/realm_dart/lib/src/credentials.dart index 2d0ffb046..85ba0e52d 100644 --- a/packages/realm_dart/lib/src/credentials.dart +++ b/packages/realm_dart/lib/src/credentials.dart @@ -4,8 +4,8 @@ import 'dart:convert'; import 'app.dart'; -import 'native/convert.dart'; -import 'native/credentials_handle.dart'; +import 'convert.dart'; +import 'handles/credentials_handle.dart'; import 'user.dart'; /// An enum containing all authentication providers. These have to be enabled manually for the application before they can be used. diff --git a/packages/realm_dart/lib/src/handles/app_handle.dart b/packages/realm_dart/lib/src/handles/app_handle.dart new file mode 100644 index 000000000..58f171adf --- /dev/null +++ b/packages/realm_dart/lib/src/handles/app_handle.dart @@ -0,0 +1,39 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import '../../realm.dart'; + +import 'credentials_handle.dart'; +import 'user_handle.dart'; + +import 'native/app_handle.dart' if (dart.library.js_interop) 'web/app_handle.dart' as impl; + +abstract interface class AppHandle { + factory AppHandle.from(AppConfiguration configuration) = impl.AppHandle.from; + static AppHandle? get(String id, String? baseUrl) => impl.AppHandle.get(id, baseUrl); + + String get id; + + UserHandle? get currentUser; + List get users; + Future logIn(CredentialsHandle credentials); + Future removeUser(UserHandle user); + void switchUser(UserHandle user); + Future refreshCustomData(UserHandle user); + + void reconnect(); + String get baseUrl; + Future updateBaseUrl(Uri? baseUrl); + + Future registerUser(String email, String password); + Future confirmUser(String token, String tokenId); + Future resendConfirmation(String email); + + Future completeResetPassword(String password, String token, String tokenId); + Future requestResetPassword(String email); + Future callResetPasswordFunction(String email, String password, String? argsAsJSON); + Future retryCustomConfirmationFunction(String email); + Future deleteUser(UserHandle user); + bool resetRealm(String realmPath); + Future callAppFunction(UserHandle user, String functionName, String? argsAsJSON); +} diff --git a/packages/realm_dart/lib/src/handles/async_open_task_handle.dart b/packages/realm_dart/lib/src/handles/async_open_task_handle.dart new file mode 100644 index 000000000..47d988585 --- /dev/null +++ b/packages/realm_dart/lib/src/handles/async_open_task_handle.dart @@ -0,0 +1,20 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import '../realm_class.dart'; +import 'handle_base.dart'; +import 'native/async_open_task_handle.dart' if (dart.library.js_interop) 'web/async_open_task_handle.dart' as impl; +import 'realm_handle.dart'; + +abstract interface class AsyncOpenTaskHandle extends HandleBase { + factory AsyncOpenTaskHandle.from(FlexibleSyncConfiguration config) = impl.AsyncOpenTaskHandle.from; + + Future openAsync(CancellationToken? cancellationToken); + void cancel(); + + AsyncOpenTaskProgressNotificationTokenHandle registerProgressNotifier( + RealmAsyncOpenProgressNotificationsController controller, + ); +} + +abstract class AsyncOpenTaskProgressNotificationTokenHandle extends HandleBase {} \ No newline at end of file diff --git a/packages/realm_dart/lib/src/handles/collection_changes_handle.dart b/packages/realm_dart/lib/src/handles/collection_changes_handle.dart new file mode 100644 index 000000000..e8295ba1d --- /dev/null +++ b/packages/realm_dart/lib/src/handles/collection_changes_handle.dart @@ -0,0 +1,9 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import '../collections.dart'; +import 'handle_base.dart'; + +abstract interface class CollectionChangesHandle extends HandleBase { + CollectionChanges get changes; +} diff --git a/packages/realm_dart/lib/src/handles/credentials_handle.dart b/packages/realm_dart/lib/src/handles/credentials_handle.dart new file mode 100644 index 000000000..d4259374b --- /dev/null +++ b/packages/realm_dart/lib/src/handles/credentials_handle.dart @@ -0,0 +1,29 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import '../credentials.dart'; +import 'handle_base.dart'; + +import 'native/credentials_handle.dart' if (dart.library.js_interop) 'web/credentials_handle.dart' as impl; + +abstract interface class CredentialsHandle extends HandleBase { + factory CredentialsHandle.anonymous(bool reuseCredentials) = impl.CredentialsHandle.anonymous; + + factory CredentialsHandle.emailPassword(String email, String password) = impl.CredentialsHandle.emailPassword; + + factory CredentialsHandle.jwt(String token) = impl.CredentialsHandle.jwt; + + factory CredentialsHandle.apple(String idToken) = impl.CredentialsHandle.apple; + + factory CredentialsHandle.facebook(String accessToken) = impl.CredentialsHandle.facebook; + + factory CredentialsHandle.googleIdToken(String idToken) = impl.CredentialsHandle.googleIdToken; + + factory CredentialsHandle.googleAuthCode(String authCode) = impl.CredentialsHandle.googleAuthCode; + + factory CredentialsHandle.function(String payload) = impl.CredentialsHandle.function; + + factory CredentialsHandle.apiKey(String key) = impl.CredentialsHandle.apiKey; + + AuthProviderType get providerType; +} diff --git a/packages/realm_dart/lib/src/handles/decimal128.dart b/packages/realm_dart/lib/src/handles/decimal128.dart new file mode 100644 index 000000000..095096313 --- /dev/null +++ b/packages/realm_dart/lib/src/handles/decimal128.dart @@ -0,0 +1,79 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import 'package:realm_common/realm_common.dart' as common; + +import 'native/decimal128.dart' + if (dart.library.js_interop) 'web/decimal128.dart' as impl; + +abstract interface class Decimal128 implements Comparable, common.Decimal128 { + /// The value 0. + static Decimal128 get zero => impl.Decimal128.zero; + + /// The value 1. + static Decimal128 get one => impl.Decimal128.one; + + /// The value 10. + static Decimal128 get ten => impl.Decimal128.ten; + + /// The value NaN. + static Decimal128 get nan => impl.Decimal128.nan; + + /// The value +Inf. + static Decimal128 get infinity => impl.Decimal128.infinity; + + /// The value -Inf. + static Decimal128 get negativeInfinity => impl.Decimal128.negativeInfinity; + + /// Parses a string into a [Decimal128]. Returns `null` if the string is not a valid [Decimal128]. + static Decimal128? tryParse(String source) => impl.Decimal128.tryParse(source); + + /// Parses a string into a [Decimal128]. Throws a [FormatException] if the string is not a valid [Decimal128]. + factory Decimal128.parse(String source) = impl.Decimal128.parse; + + /// Converts a `int` into a [Decimal128]. + factory Decimal128.fromInt(int value) = impl.Decimal128.fromInt; + + /// Converts a `double` into a [Decimal128]. + factory Decimal128.fromDouble(double value) = impl.Decimal128.fromDouble; + + /// Returns `true` if `this` is NaN. + bool get isNaN; + + /// Adds `this` with `other` and returns a new [Decimal128]. + Decimal128 operator +(Decimal128 other); + + /// Subtracts `other` from `this` and returns a new [Decimal128]. + Decimal128 operator -(Decimal128 other); + + /// Multiplies `this` with `other` and returns a new [Decimal128]. + Decimal128 operator *(Decimal128 other); + + /// Divides `this` by `other` and returns a new [Decimal128]. + Decimal128 operator /(Decimal128 other); + + /// Negates `this` and returns a new [Decimal128]. + Decimal128 operator -(); + + /// Returns the absolute value of `this`. + Decimal128 abs(); + + /// Returns `true` if `this` is less than `other`. + bool operator <(Decimal128 other); + + /// Returns `true` if `this` is less than or equal to `other`. + bool operator <=(Decimal128 other); + + /// Returns `true` if `this` is greater than `other`. + bool operator >(Decimal128 other); + + /// Returns `true` if `this` is greater than or equal to `other`. + bool operator >=(Decimal128 other); + + /// Converts `this` to an `int`. Possibly loosing precision. + int toInt(); + + /// Compares `this` to `other`. + @override + int compareTo(Decimal128 other); +} diff --git a/packages/realm_dart/lib/src/handles/default_client.dart b/packages/realm_dart/lib/src/handles/default_client.dart new file mode 100644 index 000000000..0c93fee29 --- /dev/null +++ b/packages/realm_dart/lib/src/handles/default_client.dart @@ -0,0 +1,8 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import 'package:http/http.dart'; + +import 'native/default_client.dart' if (dart.library.js_interop) 'web/default_client.dart' as impl; + +final Client defaultClient = impl.defaultClient(); \ No newline at end of file diff --git a/packages/realm_dart/lib/src/handles/handle_base.dart b/packages/realm_dart/lib/src/handles/handle_base.dart new file mode 100644 index 000000000..bf255de0e --- /dev/null +++ b/packages/realm_dart/lib/src/handles/handle_base.dart @@ -0,0 +1,9 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +abstract class HandleBase { + bool get released; + bool get isUnowned; + void releaseCore(); + void release(); +} diff --git a/packages/realm_dart/lib/src/handles/list_handle.dart b/packages/realm_dart/lib/src/handles/list_handle.dart new file mode 100644 index 000000000..4606fe831 --- /dev/null +++ b/packages/realm_dart/lib/src/handles/list_handle.dart @@ -0,0 +1,32 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import '../realm_dart.dart'; +import 'handle_base.dart'; +import 'notification_token_handle.dart'; +import 'object_handle.dart'; +import 'realm_handle.dart'; +import 'results_handle.dart'; + +abstract interface class ListHandle extends HandleBase { + bool get isValid; + int get size; + + // TODO: Consider splitting into two methods + void addOrUpdateAt(int index, Object? value, bool insert); + // TODO: avoid taking the [realm] parameter + void addOrUpdateCollectionAt(Realm realm, int index, RealmValue value, bool insert); + ResultsHandle asResults(); + void clear(); + void deleteAll(); + // TODO: avoid taking the [realm] parameter + Object? elementAt(Realm realm, int index); + int indexOf(Object? value); + ObjectHandle insertEmbeddedAt(int index); + void move(int from, int to); + ResultsHandle query(String query, List args); + void removeAt(int index); + ListHandle? resolveIn(RealmHandle frozenRealm); + ObjectHandle setEmbeddedAt(int index); + NotificationTokenHandle subscribeForNotifications(NotificationsController controller); +} diff --git a/packages/realm_dart/lib/src/handles/map_changes_handle.dart b/packages/realm_dart/lib/src/handles/map_changes_handle.dart new file mode 100644 index 000000000..a90197e6b --- /dev/null +++ b/packages/realm_dart/lib/src/handles/map_changes_handle.dart @@ -0,0 +1,9 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import '../collections.dart'; +import 'handle_base.dart'; + +abstract interface class MapChangesHandle extends HandleBase { + MapChanges get changes; +} \ No newline at end of file diff --git a/packages/realm_dart/lib/src/handles/map_handle.dart b/packages/realm_dart/lib/src/handles/map_handle.dart new file mode 100644 index 000000000..2ffa43557 --- /dev/null +++ b/packages/realm_dart/lib/src/handles/map_handle.dart @@ -0,0 +1,31 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import '../realm_class.dart'; +import 'handle_base.dart'; +import 'notification_token_handle.dart'; +import 'object_handle.dart'; +import 'realm_handle.dart'; +import 'results_handle.dart'; + +abstract interface class MapHandle extends HandleBase { + bool get isValid; + ResultsHandle get keys; + int get size; + ResultsHandle get values; + + void clear(); + bool containsKey(String key); + bool containsValue(Object? value); + // TODO: avoid taking a [Realm] as parameter (wrong layer) + Object? find(Realm realm, String key); + int indexOf(Object? value); + void insert(String key, Object? value); + // TODO: avoid taking a [Realm] as parameter (wrong layer) + void insertCollection(Realm realm, String key, RealmValue value); + ObjectHandle insertEmbedded(String key); + ResultsHandle query(String query, List args); + bool remove(String key); + MapHandle? resolveIn(RealmHandle frozenRealm); + NotificationTokenHandle subscribeForNotifications(NotificationsController controller); +} diff --git a/packages/realm_dart/lib/src/handles/mutable_subscription_set_handle.dart b/packages/realm_dart/lib/src/handles/mutable_subscription_set_handle.dart new file mode 100644 index 000000000..06cb2a811 --- /dev/null +++ b/packages/realm_dart/lib/src/handles/mutable_subscription_set_handle.dart @@ -0,0 +1,18 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import 'results_handle.dart'; +import 'subscription_handle.dart'; +import 'subscription_set_handle.dart'; + +abstract interface class MutableSubscriptionSetHandle extends SubscriptionSetHandle { + SubscriptionSetHandle commit(); + + SubscriptionHandle insertOrAssignSubscription(ResultsHandle results, String? name, bool update); + + bool erase(SubscriptionHandle subscription); + bool eraseByName(String name); + bool eraseByResults(ResultsHandle results); + + void clear(); +} diff --git a/packages/realm_dart/lib/src/native/app_handle.dart b/packages/realm_dart/lib/src/handles/native/app_handle.dart similarity index 93% rename from packages/realm_dart/lib/src/native/app_handle.dart rename to packages/realm_dart/lib/src/handles/native/app_handle.dart index c766c0453..010022eeb 100644 --- a/packages/realm_dart/lib/src/native/app_handle.dart +++ b/packages/realm_dart/lib/src/handles/native/app_handle.dart @@ -6,9 +6,8 @@ import 'dart:ffi'; import 'dart:io'; import 'dart:isolate'; -import '../init.dart'; -import '../realm_class.dart'; -import '../scheduler.dart'; +import 'package:realm_dart/realm.dart'; + import 'convert.dart'; import 'convert_native.dart'; import 'credentials_handle.dart'; @@ -19,9 +18,12 @@ import 'http_transport_handle.dart'; import 'realm_bindings.dart'; import 'realm_core.dart'; import 'realm_library.dart'; +import 'scheduler_handle.dart'; import 'user_handle.dart'; -class AppHandle extends HandleBase { +import '../app_handle.dart' as intf; + +class AppHandle extends HandleBase implements intf.AppHandle { AppHandle(Pointer pointer) : super(pointer, 16); static bool _firstTime = true; @@ -32,6 +34,8 @@ class AppHandle extends HandleBase { _firstTime = false; realmLib.realm_clear_cached_apps(); } + Directory(configuration.baseFilePath).createSync(recursive: true); + final httpTransportHandle = HttpTransportHandle.from(configuration.httpClient); final appConfigHandle = _createAppConfig(configuration, httpTransportHandle); return AppHandle(realmLib.realm_app_create_cached(appConfigHandle.pointer)); @@ -51,10 +55,12 @@ class AppHandle extends HandleBase { }); } + @override UserHandle? get currentUser { return realmLib.realm_app_get_current_user(pointer).convert(UserHandle.new); } + @override List get users => using((arena) => _getUsers(arena)); List _getUsers(Arena arena, {int expectedSize = 2}) { @@ -76,7 +82,8 @@ class AppHandle extends HandleBase { return result; } - Future removeUser(UserHandle user) { + @override + Future removeUser(covariant UserHandle user) { final completer = Completer(); realmLib .realm_app_remove_user( @@ -90,7 +97,8 @@ class AppHandle extends HandleBase { return completer.future; } - void switchUser(UserHandle user) { + @override + void switchUser(covariant UserHandle user) { using((arena) { realmLib .realm_app_switch_user( @@ -101,13 +109,16 @@ class AppHandle extends HandleBase { }); } + @override void reconnect() => realmLib.realm_app_sync_client_reconnect(pointer); + @override String get baseUrl { final customDataPtr = realmLib.realm_app_get_base_url(pointer); return customDataPtr.cast().toRealmDartString(freeRealmMemory: true)!; } + @override Future updateBaseUrl(Uri? baseUrl) { final completer = Completer(); using((arena) { @@ -124,7 +135,8 @@ class AppHandle extends HandleBase { return completer.future; } - Future refreshCustomData(UserHandle user) { + @override + Future refreshCustomData(covariant UserHandle user) { final completer = Completer(); realmLib .realm_app_refresh_custom_data( @@ -138,11 +150,13 @@ class AppHandle extends HandleBase { return completer.future; } + @override String get id { return realmLib.realm_app_get_app_id(pointer).cast().toRealmDartString()!; } - Future logIn(CredentialsHandle credentials) { + @override + Future logIn(covariant CredentialsHandle credentials) { final completer = Completer(); realmLib .realm_app_log_in_with_credentials( @@ -156,6 +170,7 @@ class AppHandle extends HandleBase { return completer.future; } + @override Future registerUser(String email, String password) { final completer = Completer(); using((arena) { @@ -173,6 +188,7 @@ class AppHandle extends HandleBase { return completer.future; } + @override Future confirmUser(String token, String tokenId) { final completer = Completer(); using((arena) { @@ -190,6 +206,7 @@ class AppHandle extends HandleBase { return completer.future; } + @override Future resendConfirmation(String email) { final completer = Completer(); using((arena) { @@ -206,6 +223,7 @@ class AppHandle extends HandleBase { return completer.future; } + @override Future completeResetPassword(String password, String token, String tokenId) { final completer = Completer(); using((arena) { @@ -224,6 +242,7 @@ class AppHandle extends HandleBase { return completer.future; } + @override Future requestResetPassword(String email) { final completer = Completer(); using((arena) { @@ -240,6 +259,7 @@ class AppHandle extends HandleBase { return completer.future; } + @override Future callResetPasswordFunction(String email, String password, String? argsAsJSON) { final completer = Completer(); using((arena) { @@ -258,6 +278,7 @@ class AppHandle extends HandleBase { return completer.future; } + @override Future retryCustomConfirmationFunction(String email) { final completer = Completer(); using((arena) { @@ -274,7 +295,8 @@ class AppHandle extends HandleBase { return completer.future; } - Future deleteUser(UserHandle user) { + @override + Future deleteUser(covariant UserHandle user) { final completer = Completer(); realmLib .realm_app_delete_user( @@ -288,6 +310,7 @@ class AppHandle extends HandleBase { return completer.future; } + @override bool resetRealm(String realmPath) { return using((arena) { final didRun = arena(); @@ -302,7 +325,8 @@ class AppHandle extends HandleBase { }); } - Future callAppFunction(UserHandle user, String functionName, String? argsAsJSON) { + @override + Future callAppFunction(covariant UserHandle user, String functionName, String? argsAsJSON) { return using((arena) { final completer = Completer(); realmLib @@ -333,7 +357,7 @@ Pointer createAsyncFunctionCallbackUserdata(Completer completer) { final userdata = realmLib.realm_dart_userdata_async_new( completer, callback.cast(), - scheduler.handle.pointer, + schedulerHandle.pointer, ); return userdata.cast(); @@ -361,7 +385,7 @@ Pointer createAsyncCallbackUserdata(Completer completer) { final userdata = realmLib.realm_dart_userdata_async_new( completer, callback.cast(), - scheduler.handle.pointer, + schedulerHandle.pointer, ); return userdata.cast(); @@ -407,7 +431,7 @@ _AppConfigHandle _createAppConfig(AppConfiguration configuration, HttpTransportH realmLib.realm_app_config_set_bundle_id(handle.pointer, realmCore.getBundleId().toCharPtr(arena)); - realmLib.realm_app_config_set_base_file_path(handle.pointer, configuration.baseFilePath.path.toCharPtr(arena)); + realmLib.realm_app_config_set_base_file_path(handle.pointer, configuration.baseFilePath.toCharPtr(arena)); realmLib.realm_app_config_set_metadata_mode(handle.pointer, configuration.metadataPersistenceMode.index); if (configuration.metadataEncryptionKey != null && configuration.metadataPersistenceMode == MetadataPersistenceMode.encrypted) { diff --git a/packages/realm_dart/lib/src/native/async_open_task_handle.dart b/packages/realm_dart/lib/src/handles/native/async_open_task_handle.dart similarity index 84% rename from packages/realm_dart/lib/src/native/async_open_task_handle.dart rename to packages/realm_dart/lib/src/handles/native/async_open_task_handle.dart index ddd184276..2ab5450d4 100644 --- a/packages/realm_dart/lib/src/native/async_open_task_handle.dart +++ b/packages/realm_dart/lib/src/handles/native/async_open_task_handle.dart @@ -2,18 +2,20 @@ import 'dart:ffi'; import 'package:cancellation_token/cancellation_token.dart'; import 'ffi.dart'; -import 'package:realm_dart/src/native/error_handling.dart'; -import 'package:realm_dart/src/native/realm_bindings.dart'; +import 'error_handling.dart'; +import 'realm_bindings.dart'; -import '../realm_dart.dart'; -import '../scheduler.dart'; +import '../../realm_dart.dart'; import 'config_handle.dart'; import 'handle_base.dart'; import 'realm_handle.dart'; import 'realm_library.dart'; +import 'scheduler_handle.dart'; import 'session_handle.dart'; -class AsyncOpenTaskHandle extends HandleBase { +import '../async_open_task_handle.dart' as intf; + +class AsyncOpenTaskHandle extends HandleBase implements intf.AsyncOpenTaskHandle { AsyncOpenTaskHandle(Pointer pointer) : super(pointer, 32); factory AsyncOpenTaskHandle.from(FlexibleSyncConfiguration config) { @@ -22,12 +24,13 @@ class AsyncOpenTaskHandle extends HandleBase { return AsyncOpenTaskHandle(asyncOpenTaskPtr); } + @override Future openAsync(CancellationToken? cancellationToken) { final completer = CancellableCompleter(cancellationToken); if (!completer.isCancelled) { final callback = Pointer.fromFunction realm, Pointer error)>(_openRealmAsyncCallback); - final userData = realmLib.realm_dart_userdata_async_new(completer, callback.cast(), scheduler.handle.pointer); + final userData = realmLib.realm_dart_userdata_async_new(completer, callback.cast(), schedulerHandle.pointer); realmLib.realm_async_open_task_start( pointer, realmLib.addresses.realm_dart_async_open_task_callback, @@ -38,15 +41,17 @@ class AsyncOpenTaskHandle extends HandleBase { return completer.future; } + @override void cancel() { realmLib.realm_async_open_task_cancel(pointer); } + @override AsyncOpenTaskProgressNotificationTokenHandle registerProgressNotifier( RealmAsyncOpenProgressNotificationsController controller, ) { final callback = Pointer.fromFunction(syncProgressCallback); - final userdata = realmLib.realm_dart_userdata_async_new(controller, callback.cast(), scheduler.handle.pointer); + final userdata = realmLib.realm_dart_userdata_async_new(controller, callback.cast(), schedulerHandle.pointer); return AsyncOpenTaskProgressNotificationTokenHandle( realmLib.realm_async_open_task_register_download_progress_notifier( pointer, @@ -58,7 +63,8 @@ class AsyncOpenTaskHandle extends HandleBase { } } -class AsyncOpenTaskProgressNotificationTokenHandle extends HandleBase { +class AsyncOpenTaskProgressNotificationTokenHandle extends HandleBase + implements intf.AsyncOpenTaskProgressNotificationTokenHandle { AsyncOpenTaskProgressNotificationTokenHandle(Pointer pointer) : super(pointer, 40); } @@ -75,6 +81,6 @@ void _openRealmAsyncCallback(Object userData, Pointer { +import '../collection_changes_handle.dart' as intf; + +class CollectionChangesHandle extends HandleBase implements intf.CollectionChangesHandle { CollectionChangesHandle(Pointer pointer) : super(pointer, 256); + @override CollectionChanges get changes { return using((arena) { final outNumDeletions = arena(); diff --git a/packages/realm_dart/lib/src/native/collection_handle_base.dart b/packages/realm_dart/lib/src/handles/native/collection_handle_base.dart similarity index 89% rename from packages/realm_dart/lib/src/native/collection_handle_base.dart rename to packages/realm_dart/lib/src/handles/native/collection_handle_base.dart index 6ac5011c0..7342ced4f 100644 --- a/packages/realm_dart/lib/src/native/collection_handle_base.dart +++ b/packages/realm_dart/lib/src/handles/native/collection_handle_base.dart @@ -3,23 +3,22 @@ import 'dart:ffi'; -import 'package:realm_dart/src/native/rooted_handle.dart'; - -import '../realm_class.dart'; +import '../../realm_class.dart'; import 'list_handle.dart'; import 'map_handle.dart'; import 'realm_bindings.dart'; +import 'realm_handle.dart'; +import 'rooted_handle.dart'; abstract class CollectionHandleBase extends RootedHandleBase { CollectionHandleBase(super.root, super.pointer, super.size); } - void createCollection(Realm realm, RealmValue value, Pointer Function() createList, Pointer Function() createMap) { CollectionHandleBase? collectionHandle; try { switch (value.collectionType) { case RealmCollectionType.list: - final listHandle = ListHandle(createList(), realm.handle); + final listHandle = ListHandle(createList(), realm.handle as RealmHandle); collectionHandle = listHandle; final list = realm.createList(listHandle, null); @@ -31,7 +30,7 @@ void createCollection(Realm realm, RealmValue value, Pointer Functio list.add(item); } case RealmCollectionType.map: - final mapHandle = MapHandle(createMap(), realm.handle); + final mapHandle = MapHandle(createMap(), realm.handle as RealmHandle); collectionHandle = mapHandle; final map = realm.createMap(mapHandle, null); diff --git a/packages/realm_dart/lib/src/native/config_handle.dart b/packages/realm_dart/lib/src/handles/native/config_handle.dart similarity index 95% rename from packages/realm_dart/lib/src/native/config_handle.dart rename to packages/realm_dart/lib/src/handles/native/config_handle.dart index bb4010a70..6c1134264 100644 --- a/packages/realm_dart/lib/src/native/config_handle.dart +++ b/packages/realm_dart/lib/src/handles/native/config_handle.dart @@ -4,20 +4,20 @@ import 'dart:async'; import 'dart:ffi'; -import 'ffi.dart'; -import 'package:realm_dart/src/native/error_handling.dart'; - -import '../configuration.dart'; -import '../migration.dart'; -import '../realm_class.dart'; -import '../scheduler.dart'; -import '../user.dart'; +import '../../configuration.dart'; +import '../../migration.dart'; +import '../../realm_class.dart'; +import '../../user.dart'; import 'convert_native.dart'; +import 'error_handling.dart'; +import 'ffi.dart'; import 'handle_base.dart'; import 'realm_bindings.dart'; import 'realm_handle.dart'; import 'realm_library.dart'; +import 'scheduler_handle.dart'; import 'schema_handle.dart'; +import 'user_handle.dart'; class ConfigHandle extends HandleBase { ConfigHandle(Pointer pointer) : super(pointer, 512); @@ -32,7 +32,7 @@ class ConfigHandle extends HandleBase { } realmLib.realm_config_set_path(configHandle.pointer, config.path.toCharPtr(arena)); - realmLib.realm_config_set_scheduler(configHandle.pointer, scheduler.handle.pointer); + realmLib.realm_config_set_scheduler(configHandle.pointer, schedulerHandle.pointer); if (config.fifoFilesFallbackPath != null) { realmLib.realm_config_set_fifo_path(configHandle.pointer, config.fifoFilesFallbackPath!.toCharPtr(arena)); @@ -87,19 +87,19 @@ class ConfigHandle extends HandleBase { realmLib.realm_config_set_in_memory(configHandle.pointer, true); } else if (config is FlexibleSyncConfiguration) { realmLib.realm_config_set_schema_mode(configHandle.pointer, realm_schema_mode.RLM_SCHEMA_MODE_ADDITIVE_DISCOVERED); - final syncConfigPtr = realmLib.realm_flx_sync_config_new(config.user.handle.pointer).raiseLastErrorIfNull(); + final syncConfigPtr = realmLib.realm_flx_sync_config_new((config.user.handle as UserHandle).pointer).raiseLastErrorIfNull(); try { realmLib.realm_sync_config_set_session_stop_policy(syncConfigPtr, config.sessionStopPolicy.index); realmLib.realm_sync_config_set_resync_mode(syncConfigPtr, config.clientResetHandler.clientResyncMode.index); final errorHandlerCallback = Pointer.fromFunction, realm_sync_error_t)>(_syncErrorHandlerCallback); - final errorHandlerUserdata = realmLib.realm_dart_userdata_async_new(config, errorHandlerCallback.cast(), scheduler.handle.pointer); + final errorHandlerUserdata = realmLib.realm_dart_userdata_async_new(config, errorHandlerCallback.cast(), schedulerHandle.pointer); realmLib.realm_sync_config_set_error_handler(syncConfigPtr, realmLib.addresses.realm_dart_sync_error_handler_callback, errorHandlerUserdata.cast(), realmLib.addresses.realm_dart_userdata_async_free); if (config.clientResetHandler.onBeforeReset != null) { final syncBeforeResetCallback = Pointer.fromFunction, Pointer)>(_syncBeforeResetCallback); - final beforeResetUserdata = realmLib.realm_dart_userdata_async_new(config, syncBeforeResetCallback.cast(), scheduler.handle.pointer); + final beforeResetUserdata = realmLib.realm_dart_userdata_async_new(config, syncBeforeResetCallback.cast(), schedulerHandle.pointer); realmLib.realm_sync_config_set_before_client_reset_handler(syncConfigPtr, realmLib.addresses.realm_dart_sync_before_reset_handler_callback, beforeResetUserdata.cast(), realmLib.addresses.realm_dart_userdata_async_free); @@ -109,7 +109,7 @@ class ConfigHandle extends HandleBase { final syncAfterResetCallback = Pointer.fromFunction, Pointer, Bool, Pointer)>( _syncAfterResetCallback); - final afterResetUserdata = realmLib.realm_dart_userdata_async_new(config, syncAfterResetCallback.cast(), scheduler.handle.pointer); + final afterResetUserdata = realmLib.realm_dart_userdata_async_new(config, syncAfterResetCallback.cast(), schedulerHandle.pointer); realmLib.realm_sync_config_set_after_client_reset_handler(syncConfigPtr, realmLib.addresses.realm_dart_sync_after_reset_handler_callback, afterResetUserdata.cast(), realmLib.addresses.realm_dart_userdata_async_free); @@ -163,7 +163,7 @@ void _syncAfterResetCallback(Object userdata, Pointer beforeHandle syncConfig, RealmHandle.unowned(realmLib.realm_from_thread_safe_reference( afterReference, - scheduler.handle.pointer, + schedulerHandle.pointer, ))); try { diff --git a/packages/realm_dart/lib/src/native/convert.dart b/packages/realm_dart/lib/src/handles/native/convert.dart similarity index 100% rename from packages/realm_dart/lib/src/native/convert.dart rename to packages/realm_dart/lib/src/handles/native/convert.dart diff --git a/packages/realm_dart/lib/src/native/convert_native.dart b/packages/realm_dart/lib/src/handles/native/convert_native.dart similarity index 100% rename from packages/realm_dart/lib/src/native/convert_native.dart rename to packages/realm_dart/lib/src/handles/native/convert_native.dart diff --git a/packages/realm_dart/lib/src/native/credentials_handle.dart b/packages/realm_dart/lib/src/handles/native/credentials_handle.dart similarity index 95% rename from packages/realm_dart/lib/src/native/credentials_handle.dart rename to packages/realm_dart/lib/src/handles/native/credentials_handle.dart index d742d9ac9..c7ecffe0f 100644 --- a/packages/realm_dart/lib/src/native/credentials_handle.dart +++ b/packages/realm_dart/lib/src/handles/native/credentials_handle.dart @@ -5,13 +5,15 @@ import 'dart:ffi'; import 'ffi.dart'; -import '../credentials.dart'; +import '../../credentials.dart'; import 'handle_base.dart'; import 'realm_bindings.dart'; import 'realm_library.dart'; import 'to_native.dart'; -class CredentialsHandle extends HandleBase { +import '../credentials_handle.dart' as intf; + +class CredentialsHandle extends HandleBase implements intf.CredentialsHandle { CredentialsHandle(Pointer pointer) : super(pointer, 16); factory CredentialsHandle.anonymous(bool reuseCredentials) { diff --git a/packages/realm_dart/lib/src/native/decimal128.dart b/packages/realm_dart/lib/src/handles/native/decimal128.dart similarity index 84% rename from packages/realm_dart/lib/src/native/decimal128.dart rename to packages/realm_dart/lib/src/handles/native/decimal128.dart index ad4df4acd..8c9a15aee 100644 --- a/packages/realm_dart/lib/src/native/decimal128.dart +++ b/packages/realm_dart/lib/src/handles/native/decimal128.dart @@ -5,14 +5,14 @@ import 'dart:convert'; import 'dart:ffi'; import 'ffi.dart'; -import 'package:realm_common/realm_common.dart' as common; - import 'realm_bindings.dart'; import 'realm_library.dart'; import 'to_native.dart'; +import '../decimal128.dart' as intf; + /// A 128-bit decimal floating point number. -class Decimal128 implements Comparable, common.Decimal128 { +class Decimal128 implements intf.Decimal128 { /// The value 0. static final zero = Decimal128.fromInt(0); @@ -62,32 +62,39 @@ class Decimal128 implements Comparable, common.Decimal128 { } /// Returns `true` if `this` is NaN. + @override bool get isNaN => realmLib.realm_dart_decimal128_is_nan(_value); /// Adds `this` with `other` and returns a new [Decimal128]. - Decimal128 operator +(Decimal128 other) { + @override + Decimal128 operator +(covariant Decimal128 other) { return Decimal128._(realmLib.realm_dart_decimal128_add(_value, other._value)); } /// Subtracts `other` from `this` and returns a new [Decimal128]. - Decimal128 operator -(Decimal128 other) { + @override + Decimal128 operator -(covariant Decimal128 other) { return Decimal128._(realmLib.realm_dart_decimal128_subtract(_value, other._value)); } /// Multiplies `this` with `other` and returns a new [Decimal128]. - Decimal128 operator *(Decimal128 other) { + @override + Decimal128 operator *(covariant Decimal128 other) { return Decimal128._(realmLib.realm_dart_decimal128_multiply(_value, other._value)); } /// Divides `this` by `other` and returns a new [Decimal128]. - Decimal128 operator /(Decimal128 other) { + @override + Decimal128 operator /(covariant Decimal128 other) { return Decimal128._(realmLib.realm_dart_decimal128_divide(_value, other._value)); } /// Negates `this` and returns a new [Decimal128]. + @override Decimal128 operator -() => Decimal128._(realmLib.realm_dart_decimal128_negate(_value)); /// Returns the absolute value of `this`. + @override Decimal128 abs() => this < zero ? -this : this; /// Returns `true` if `this` and `other` are equal. @@ -103,22 +110,27 @@ class Decimal128 implements Comparable, common.Decimal128 { } /// Returns `true` if `this` is less than `other`. - bool operator <(Decimal128 other) { + @override + bool operator <(covariant Decimal128 other) { return realmLib.realm_dart_decimal128_less_than(_value, other._value); } /// Returns `true` if `this` is less than or equal to `other`. - bool operator <=(Decimal128 other) => compareTo(other) <= 0; + @override + bool operator <=(covariant Decimal128 other) => compareTo(other) <= 0; /// Returns `true` if `this` is greater than `other`. - bool operator >(Decimal128 other) { + @override + bool operator >(covariant Decimal128 other) { return realmLib.realm_dart_decimal128_greater_than(_value, other._value); } /// Returns `true` if `this` is greater than or equal to `other`. - bool operator >=(Decimal128 other) => compareTo(other) >= 0; + @override + bool operator >=(covariant Decimal128 other) => compareTo(other) >= 0; /// Converts `this` to an `int`. Possibly loosing precision. + @override int toInt() => realmLib.realm_dart_decimal128_to_int64(_value); /// String representation of `this`. @@ -132,7 +144,7 @@ class Decimal128 implements Comparable, common.Decimal128 { /// Compares `this` to `other`. @override - int compareTo(Decimal128 other) => realmLib.realm_dart_decimal128_compare_to(_value, other._value); + int compareTo(covariant Decimal128 other) => realmLib.realm_dart_decimal128_compare_to(_value, other._value); } extension Decimal128Internal on Decimal128 { diff --git a/packages/realm_dart/lib/src/handles/native/default_client.dart b/packages/realm_dart/lib/src/handles/native/default_client.dart new file mode 100644 index 000000000..11da173b0 --- /dev/null +++ b/packages/realm_dart/lib/src/handles/native/default_client.dart @@ -0,0 +1,60 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import 'dart:convert'; +import 'dart:io'; + +import 'package:http/http.dart'; +import 'package:http/io_client.dart'; + +Client defaultClient() { + const isrgRootX1CertPEM = // The root certificate used by lets encrypt and hence MongoDB + ''' +subject=CN=ISRG Root X1,O=Internet Security Research Group,C=US +issuer=CN=DST Root CA X3,O=Digital Signature Trust Co. +-----BEGIN CERTIFICATE----- +MIIFYDCCBEigAwIBAgIQQAF3ITfU6UK47naqPGQKtzANBgkqhkiG9w0BAQsFADA/ +MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT +DkRTVCBSb290IENBIFgzMB4XDTIxMDEyMDE5MTQwM1oXDTI0MDkzMDE4MTQwM1ow +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQCt6CRz9BQ385ueK1coHIe+3LffOJCMbjzmV6B493XC +ov71am72AE8o295ohmxEk7axY/0UEmu/H9LqMZshftEzPLpI9d1537O4/xLxIZpL +wYqGcWlKZmZsj348cL+tKSIG8+TA5oCu4kuPt5l+lAOf00eXfJlII1PoOK5PCm+D +LtFJV4yAdLbaL9A4jXsDcCEbdfIwPPqPrt3aY6vrFk/CjhFLfs8L6P+1dy70sntK +4EwSJQxwjQMpoOFTJOwT2e4ZvxCzSow/iaNhUd6shweU9GNx7C7ib1uYgeGJXDR5 +bHbvO5BieebbpJovJsXQEOEO3tkQjhb7t/eo98flAgeYjzYIlefiN5YNNnWe+w5y +sR2bvAP5SQXYgd0FtCrWQemsAXaVCg/Y39W9Eh81LygXbNKYwagJZHduRze6zqxZ +Xmidf3LWicUGQSk+WT7dJvUkyRGnWqNMQB9GoZm1pzpRboY7nn1ypxIFeFntPlF4 +FQsDj43QLwWyPntKHEtzBRL8xurgUBN8Q5N0s8p0544fAQjQMNRbcTa0B7rBMDBc +SLeCO5imfWCKoqMpgsy6vYMEG6KDA0Gh1gXxG8K28Kh8hjtGqEgqiNx2mna/H2ql +PRmP6zjzZN7IKw0KKP/32+IVQtQi0Cdd4Xn+GOdwiK1O5tmLOsbdJ1Fu/7xk9TND +TwIDAQABo4IBRjCCAUIwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw +SwYIKwYBBQUHAQEEPzA9MDsGCCsGAQUFBzAChi9odHRwOi8vYXBwcy5pZGVudHJ1 +c3QuY29tL3Jvb3RzL2RzdHJvb3RjYXgzLnA3YzAfBgNVHSMEGDAWgBTEp7Gkeyxx ++tvhS5B1/8QVYIWJEDBUBgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEEAYLfEwEB +ATAwMC4GCCsGAQUFBwIBFiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2VuY3J5cHQu +b3JnMDwGA1UdHwQ1MDMwMaAvoC2GK2h0dHA6Ly9jcmwuaWRlbnRydXN0LmNvbS9E +U1RST09UQ0FYM0NSTC5jcmwwHQYDVR0OBBYEFHm0WeZ7tuXkAXOACIjIGlj26Ztu +MA0GCSqGSIb3DQEBCwUAA4IBAQAKcwBslm7/DlLQrt2M51oGrS+o44+/yQoDFVDC +5WxCu2+b9LRPwkSICHXM6webFGJueN7sJ7o5XPWioW5WlHAQU7G75K/QosMrAdSW +9MUgNTP52GE24HGNtLi1qoJFlcDyqSMo59ahy2cI2qBDLKobkx/J3vWraV0T9VuG +WCLKTVXkcGdtwlfFRjlBz4pYg1htmf5X6DYO8A4jqv2Il9DjXA6USbW1FzXSLr9O +he8Y4IWS6wY7bCkjCWDcRQJMEhg76fsO3txE+FiYruq9RUWhiF1myv4Q6W+CyBFC +Dfvp7OOGAN6dEOM4+qR9sdjoSYKEBpsr6GtPAQw4dy753ec5 +-----END CERTIFICATE-----'''; + + if (Platform.isWindows) { + try { + final context = SecurityContext(withTrustedRoots: true); + context.setTrustedCertificatesBytes(const AsciiEncoder().convert(isrgRootX1CertPEM)); + return IOClient(HttpClient(context: context)); + } on TlsException catch (e) { + // certificate is already trusted. Nothing to do here + if (e.osError?.message.contains("CERT_ALREADY_IN_HASH_TABLE") != true) { + rethrow; + } + } + } + return Client(); +} diff --git a/packages/realm_dart/lib/src/native/error_handling.dart b/packages/realm_dart/lib/src/handles/native/error_handling.dart similarity index 97% rename from packages/realm_dart/lib/src/native/error_handling.dart rename to packages/realm_dart/lib/src/handles/native/error_handling.dart index 68f47fea8..ea18eeef7 100644 --- a/packages/realm_dart/lib/src/native/error_handling.dart +++ b/packages/realm_dart/lib/src/handles/native/error_handling.dart @@ -5,7 +5,7 @@ import 'dart:ffi'; import 'ffi.dart'; -import '../realm_object.dart'; +import '../../realm_object.dart'; import 'from_native.dart'; import 'realm_bindings.dart'; import 'realm_library.dart'; diff --git a/packages/realm_dart/lib/src/native/ffi.dart b/packages/realm_dart/lib/src/handles/native/ffi.dart similarity index 100% rename from packages/realm_dart/lib/src/native/ffi.dart rename to packages/realm_dart/lib/src/handles/native/ffi.dart diff --git a/packages/realm_dart/lib/src/native/from_native.dart b/packages/realm_dart/lib/src/handles/native/from_native.dart similarity index 77% rename from packages/realm_dart/lib/src/native/from_native.dart rename to packages/realm_dart/lib/src/handles/native/from_native.dart index 316331ce2..609782e36 100644 --- a/packages/realm_dart/lib/src/native/from_native.dart +++ b/packages/realm_dart/lib/src/handles/native/from_native.dart @@ -5,14 +5,15 @@ import 'dart:typed_data'; import 'ffi.dart'; -import '../app.dart'; -import '../configuration.dart'; -import '../realm_class.dart'; -import '../user.dart'; +import '../../app.dart'; +import '../../configuration.dart'; +import '../../realm_class.dart'; +import '../../user.dart'; import 'decimal128.dart'; import 'list_handle.dart'; import 'map_handle.dart'; import 'realm_bindings.dart'; +import 'realm_handle.dart'; import 'realm_library.dart'; // TODO: Duplicated in to_native.dart @@ -64,14 +65,14 @@ extension RealmValueEx on realm_value_t { throw RealmException('toDartValue called with a list argument but without a list getter'); } - final listHandle = ListHandle(getList(), realm.handle); + final listHandle = ListHandle(getList(), realm.handle as RealmHandle); return realm.createList(listHandle, null); case realm_value_type.RLM_TYPE_DICTIONARY: if (getMap == null || realm == null) { throw RealmException('toDartValue called with a list argument but without a list getter'); } - final mapHandle = MapHandle(getMap(), realm.handle); + final mapHandle = MapHandle(getMap(), realm.handle as RealmHandle); return realm.createMap(mapHandle, null); default: throw RealmException("realm_value_type $type not supported"); @@ -150,7 +151,7 @@ extension RealmSyncErrorEx on realm_sync_error { return SyncErrorDetails( message, - status.error, + status.error.toSyncErrorCode(), user_code_error.toUserCodeError(), isFatal: is_fatal, isClientResetRequested: is_client_reset_requested, @@ -197,11 +198,50 @@ extension PointerRealmSyncErrorCompensatingWriteInfoEx on Pointer { SyncError toDart() { final message = ref.message.cast().toDartString(); - final details = SyncErrorDetails(message, ref.error, ref.user_code_error.toUserCodeError()); + final details = SyncErrorDetails(message, ref.error.toSyncErrorCode(), ref.user_code_error.toUserCodeError()); return SyncErrorInternal.createSyncError(details); } } +extension IntEx on int { + SyncErrorCode toSyncErrorCode() => switch (this) { + realm_errno.RLM_ERR_RUNTIME => SyncErrorCode.runtimeError, + realm_errno.RLM_ERR_BAD_CHANGESET => SyncErrorCode.badChangeset, + realm_errno.RLM_ERR_BAD_SYNC_PARTITION_VALUE => SyncErrorCode.badPartitionValue, + realm_errno.RLM_ERR_SYNC_PROTOCOL_INVARIANT_FAILED => SyncErrorCode.protocolInvariantFailed, + realm_errno.RLM_ERR_INVALID_SUBSCRIPTION_QUERY => SyncErrorCode.invalidSubscriptionQuery, + realm_errno.RLM_ERR_SYNC_CLIENT_RESET_REQUIRED => SyncErrorCode.clientReset, + realm_errno.RLM_ERR_SYNC_INVALID_SCHEMA_CHANGE => SyncErrorCode.invalidSchemaChange, + realm_errno.RLM_ERR_SYNC_PERMISSION_DENIED => SyncErrorCode.permissionDenied, + realm_errno.RLM_ERR_SYNC_SERVER_PERMISSIONS_CHANGED => SyncErrorCode.serverPermissionsChanged, + realm_errno.RLM_ERR_SYNC_USER_MISMATCH => SyncErrorCode.userMismatch, + realm_errno.RLM_ERR_SYNC_WRITE_NOT_ALLOWED => SyncErrorCode.writeNotAllowed, + realm_errno.RLM_ERR_AUTO_CLIENT_RESET_FAILED => SyncErrorCode.autoClientResetFailed, + realm_errno.RLM_ERR_WRONG_SYNC_TYPE => SyncErrorCode.wrongSyncType, + realm_errno.RLM_ERR_SYNC_COMPENSATING_WRITE => SyncErrorCode.compensatingWrite, + _ => throw RealmError("Unknown sync error code $this"), + }; +} + +extension SyncErrorCodeEx on SyncErrorCode { + int get code => switch (this) { + SyncErrorCode.runtimeError => realm_errno.RLM_ERR_RUNTIME, + SyncErrorCode.badChangeset => realm_errno.RLM_ERR_BAD_CHANGESET, + SyncErrorCode.badPartitionValue => realm_errno.RLM_ERR_BAD_SYNC_PARTITION_VALUE, + SyncErrorCode.protocolInvariantFailed => realm_errno.RLM_ERR_SYNC_PROTOCOL_INVARIANT_FAILED, + SyncErrorCode.invalidSubscriptionQuery => realm_errno.RLM_ERR_INVALID_SUBSCRIPTION_QUERY, + SyncErrorCode.clientReset => realm_errno.RLM_ERR_SYNC_CLIENT_RESET_REQUIRED, + SyncErrorCode.invalidSchemaChange => realm_errno.RLM_ERR_SYNC_INVALID_SCHEMA_CHANGE, + SyncErrorCode.permissionDenied => realm_errno.RLM_ERR_SYNC_PERMISSION_DENIED, + SyncErrorCode.serverPermissionsChanged => realm_errno.RLM_ERR_SYNC_SERVER_PERMISSIONS_CHANGED, + SyncErrorCode.userMismatch => realm_errno.RLM_ERR_SYNC_USER_MISMATCH, + SyncErrorCode.writeNotAllowed => realm_errno.RLM_ERR_SYNC_WRITE_NOT_ALLOWED, + SyncErrorCode.autoClientResetFailed => realm_errno.RLM_ERR_AUTO_CLIENT_RESET_FAILED, + SyncErrorCode.wrongSyncType => realm_errno.RLM_ERR_WRONG_SYNC_TYPE, + SyncErrorCode.compensatingWrite => realm_errno.RLM_ERR_SYNC_COMPENSATING_WRITE, + }; +} + extension ObjectEx on Object { Pointer toPersistentHandle() { return realmLib.realm_dart_object_to_persistent_handle(this); @@ -247,6 +287,7 @@ extension CompleterEx on Completer { enum CustomErrorCode { noError(0), + socketException(997), unknownHttp(998), unknown(999), timeout(1000); @@ -313,31 +354,6 @@ extension PlatformEx on Platform { } } -/// @nodoc -class SyncErrorDetails { - final String message; - final int code; - final String? path; - final bool isFatal; - final bool isClientResetRequested; - final String? originalFilePath; - final String? backupFilePath; - final List? compensatingWrites; - final Object? userError; - - SyncErrorDetails( - this.message, - this.code, - this.userError, { - this.path, - this.isFatal = false, - this.isClientResetRequested = false, - this.originalFilePath, - this.backupFilePath, - this.compensatingWrites, - }); -} - extension PointerRealmValueEx on Pointer { Object? toDartValue(Realm realm, Pointer Function()? getList, Pointer Function()? getMap) { if (this == nullptr) { diff --git a/packages/realm_dart/lib/src/native/handle_base.dart b/packages/realm_dart/lib/src/handles/native/handle_base.dart similarity index 64% rename from packages/realm_dart/lib/src/native/handle_base.dart rename to packages/realm_dart/lib/src/handles/native/handle_base.dart index e0a94112e..0835503e8 100644 --- a/packages/realm_dart/lib/src/native/handle_base.dart +++ b/packages/realm_dart/lib/src/handles/native/handle_base.dart @@ -3,10 +3,13 @@ import 'dart:ffi'; -import 'package:realm_dart/src/native/error_handling.dart'; +import 'package:realm_dart/realm.dart'; +import 'error_handling.dart'; import 'realm_library.dart'; +import '../handle_base.dart' as intf; + // Flag to enable trace on finalization. // // Be aware that the trace is likely late, and it might in rare case be missing, @@ -33,33 +36,42 @@ void _tearDownFinalizationTrace(Object value, Object finalizationToken) { _traceFinalization(finalizationToken); } -abstract class HandleBase implements Finalizable { +abstract class HandleBase implements Finalizable, intf.HandleBase { late Pointer _finalizableHandle; - Pointer pointer; - bool get released => pointer == nullptr; + Pointer _pointer; + Pointer get pointer { + if (released) throw RealmError('Trying to access a released handle'); + return _pointer; + } + + @override + bool get released => _pointer == nullptr; + @override final bool isUnowned; - HandleBase(this.pointer, int size) : isUnowned = false { - pointer.raiseLastErrorIfNull(); + HandleBase(this._pointer, int size) : isUnowned = false { + _pointer.raiseLastErrorIfNull(); _finalizableHandle = realmLib.realm_attach_finalizer(this, pointer.cast(), size); if (_enableFinalizerTrace) { - _setupFinalizationTrace(this, pointer); + _setupFinalizationTrace(this, _pointer); } } - HandleBase.unowned(this.pointer) : isUnowned = true { - pointer.raiseLastErrorIfNull(); + HandleBase.unowned(this._pointer) : isUnowned = true { + _pointer.raiseLastErrorIfNull(); } @override - String toString() => "${pointer.toString()} value=${pointer.cast().value}${isUnowned ? ' (unowned)' : ''}"; + String toString() => "${_pointer.toString()} value=${_pointer.cast().value}${isUnowned ? ' (unowned)' : ''}"; /// @nodoc /// A method that will be invoked just before the handle is released. Allows to cleanup /// any custom data that inheritors are storing. + @override void releaseCore() {} + @override void release() { if (released) { return; @@ -70,21 +82,21 @@ abstract class HandleBase implements Finalizable { if (!isUnowned) { realmLib.realm_detach_finalizer(_finalizableHandle, this); - realmLib.realm_release(pointer.cast()); + realmLib.realm_release(_pointer.cast()); } - pointer = nullptr; + _pointer = nullptr; if (_enableFinalizerTrace) { - _tearDownFinalizationTrace(this, pointer); + _tearDownFinalizationTrace(this, _pointer); } } @override // ignore: hash_and_equals bool operator ==(Object other) => other is HandleBase - ? pointer == other.pointer + ? _pointer == other._pointer ? true - : realmLib.realm_equals(pointer.cast(), other.pointer.cast()) + : realmLib.realm_equals(_pointer.cast(), other._pointer.cast()) : false; } diff --git a/packages/realm_dart/lib/src/native/http_transport_handle.dart b/packages/realm_dart/lib/src/handles/native/http_transport_handle.dart similarity index 71% rename from packages/realm_dart/lib/src/native/http_transport_handle.dart rename to packages/realm_dart/lib/src/handles/native/http_transport_handle.dart index afb776bfd..8a316e5b6 100644 --- a/packages/realm_dart/lib/src/native/http_transport_handle.dart +++ b/packages/realm_dart/lib/src/handles/native/http_transport_handle.dart @@ -1,25 +1,28 @@ // Copyright 2024 MongoDB, Inc. // SPDX-License-Identifier: Apache-2.0 +import 'dart:async'; import 'dart:convert'; import 'dart:ffi'; import 'dart:io'; -import '../logging.dart'; -import '../realm_dart.dart'; -import '../scheduler.dart'; +import 'package:http/http.dart'; + +import '../../logging.dart'; +import '../../realm_dart.dart'; import 'convert_native.dart'; import 'ffi.dart'; import 'handle_base.dart'; import 'realm_bindings.dart'; import 'realm_library.dart'; +import 'scheduler_handle.dart'; class HttpTransportHandle extends HandleBase { HttpTransportHandle(Pointer pointer) : super(pointer, 24); - factory HttpTransportHandle.from(HttpClient httpClient) { + factory HttpTransportHandle.from(Client httpClient) { final requestCallback = Pointer.fromFunction)>(_requestCallback); - final requestCallbackUserdata = realmLib.realm_dart_userdata_async_new(httpClient, requestCallback.cast(), scheduler.handle.pointer); + final requestCallbackUserdata = realmLib.realm_dart_userdata_async_new(httpClient, requestCallback.cast(), schedulerHandle.pointer); return HttpTransportHandle(realmLib.realm_http_transport_new( realmLib.addresses.realm_dart_http_request_callback, requestCallbackUserdata.cast(), @@ -38,9 +41,9 @@ void _requestCallback(Object userData, realm_http_request request, Pointer // We cannot clone request on the native side with realm_clone, // since realm_http_request does not inherit from WrapC. - final client = userData as HttpClient; + final client = userData as Client; - client.connectionTimeout = Duration(milliseconds: request.timeout_ms); + final timeLimit = Duration(milliseconds: request.timeout_ms); final url = Uri.parse(request.url.cast().toRealmDartString()!); @@ -54,16 +57,17 @@ void _requestCallback(Object userData, realm_http_request request, Pointer headers[name] = value; } - _requestCallbackAsync(client, request.method, url, body, headers, requestContext); + _requestCallbackAsync(client, request.method, url, body, headers, timeLimit, requestContext); // The request struct dies here! } Future _requestCallbackAsync( - HttpClient client, + Client client, int requestMethod, Uri url, String? body, Map headers, + Duration timeLimit, Pointer requestContext, ) async { await using((arena) async { @@ -73,32 +77,13 @@ Future _requestCallbackAsync( try { // Build request - late HttpClientRequest request; - - switch (method) { - case HttpMethod.delete: - request = await client.deleteUrl(url); - break; - case HttpMethod.put: - request = await client.putUrl(url); - break; - case HttpMethod.patch: - request = await client.patchUrl(url); - break; - case HttpMethod.post: - request = await client.postUrl(url); - break; - case HttpMethod.get: - request = await client.getUrl(url); - break; - } - + final request = Request(method.name, url); for (final header in headers.entries) { - request.headers.add(header.key, header.value); + request.headers[header.key] = header.value; } if (body != null) { - request.add(utf8.encode(body)); + request.bodyBytes = utf8.encode(body); } Realm.logger.log(LogLevel.debug, "HTTP Transport: Executing ${method.name} $url"); @@ -106,40 +91,37 @@ Future _requestCallbackAsync( final stopwatch = Stopwatch()..start(); // Do the call.. - final response = await request.close(); + final response = await client.send(request).timeout(timeLimit); stopwatch.stop(); Realm.logger.log(LogLevel.debug, "HTTP Transport: Executed ${method.name} $url: ${response.statusCode} in ${stopwatch.elapsedMilliseconds} ms"); - final responseBody = await response.fold>([], (acc, l) => acc..addAll(l)); // gather response + final responseBody = await response.stream.fold>([], (acc, l) => acc..addAll(l)); // gather response // Report back to core responseRef.status_code = response.statusCode; responseRef.body = responseBody.toCharPtr(arena); responseRef.body_size = responseBody.length; - int headerCnt = 0; - response.headers.forEach((name, values) { - headerCnt += values.length; - }); - + int headerCnt = response.headers.length; responseRef.headers = arena(headerCnt); responseRef.num_headers = headerCnt; int index = 0; - response.headers.forEach((name, values) { - for (final value in values) { - final headerRef = (responseRef.headers + index).ref; - headerRef.name = name.toCharPtr(arena); - headerRef.value = value.toCharPtr(arena); - index++; - } + response.headers.forEach((name, value) { + final headerRef = (responseRef.headers + index).ref; + headerRef.name = name.toCharPtr(arena); + headerRef.value = value.toCharPtr(arena); + index++; }); responseRef.custom_status_code = CustomErrorCode.noError.code; + } on TimeoutException catch (timeoutEx) { + Realm.logger.log(LogLevel.warn, "HTTP Transport: TimeoutException executing ${method.name} $url: $timeoutEx"); + responseRef.custom_status_code = CustomErrorCode.timeout.code; } on SocketException catch (socketEx) { Realm.logger.log(LogLevel.warn, "HTTP Transport: SocketException executing ${method.name} $url: $socketEx"); - responseRef.custom_status_code = CustomErrorCode.timeout.code; + responseRef.custom_status_code = CustomErrorCode.socketException.code; } on HttpException catch (httpEx) { Realm.logger.log(LogLevel.warn, "HTTP Transport: HttpException executing ${method.name} $url: $httpEx"); responseRef.custom_status_code = CustomErrorCode.unknownHttp.code; diff --git a/packages/realm_dart/lib/src/init.dart b/packages/realm_dart/lib/src/handles/native/init.dart similarity index 94% rename from packages/realm_dart/lib/src/init.dart rename to packages/realm_dart/lib/src/handles/native/init.dart index 075ca4764..f2fe88d0e 100644 --- a/packages/realm_dart/lib/src/init.dart +++ b/packages/realm_dart/lib/src/handles/native/init.dart @@ -7,11 +7,13 @@ import 'dart:io'; import 'package:package_config/package_config.dart'; import 'package:path/path.dart' as p; -import '../realm.dart' as realm show isFlutterPlatform; -import 'cli/common/target_os_type.dart'; -import 'cli/metrics/metrics_command.dart'; -import 'cli/metrics/options.dart'; -import 'realm_class.dart'; +import '../../cli/common/target_os_type.dart'; +import '../../cli/metrics/metrics_command.dart'; +import '../../cli/metrics/options.dart'; +import '../../realm_class.dart'; + +import '../../../realm.dart' show isFlutterPlatform; +export '../../../realm.dart' show isFlutterPlatform; const realmBinaryName = 'realm_dart'; final targetOsType = Platform.operatingSystem.asTargetOsType ?? _platformNotSupported(); @@ -50,7 +52,6 @@ String _getLibPathDart(Package realmDartPackage) { _platformNotSupported(); } -bool get isFlutterPlatform => realm.isFlutterPlatform; String _getLibName(String stem) => switch (targetOsType) { TargetOsType.android => 'lib$stem.so', diff --git a/packages/realm_dart/lib/src/native/list_handle.dart b/packages/realm_dart/lib/src/handles/native/list_handle.dart similarity index 91% rename from packages/realm_dart/lib/src/native/list_handle.dart rename to packages/realm_dart/lib/src/handles/native/list_handle.dart index 5021b5abc..222710e17 100644 --- a/packages/realm_dart/lib/src/native/list_handle.dart +++ b/packages/realm_dart/lib/src/handles/native/list_handle.dart @@ -3,7 +3,7 @@ import 'dart:ffi'; -import '../realm_dart.dart'; +import '../../realm_dart.dart'; import 'collection_handle_base.dart'; import 'convert_native.dart'; import 'error_handling.dart'; @@ -16,15 +16,20 @@ import 'realm_handle.dart'; import 'realm_library.dart'; import 'results_handle.dart'; -class ListHandle extends CollectionHandleBase { +import '../list_handle.dart' as intf; + +class ListHandle extends CollectionHandleBase implements intf.ListHandle { ListHandle(Pointer pointer, RealmHandle root) : super(root, pointer, 88); + @override bool get isValid => realmLib.realm_list_is_valid(pointer); + @override ResultsHandle asResults() { return ResultsHandle(realmLib.realm_list_to_results(pointer), root); } + @override int get size { return using((arena) { final size = arena(); @@ -33,18 +38,22 @@ class ListHandle extends CollectionHandleBase { }); } + @override void removeAt(int index) { realmLib.realm_list_erase(pointer, index).raiseLastErrorIfFalse(); } + @override void move(int from, int to) { realmLib.realm_list_move(pointer, from, to).raiseLastErrorIfFalse(); } + @override void deleteAll() { realmLib.realm_list_remove_all(pointer).raiseLastErrorIfFalse(); } + @override int indexOf(Object? value) { return using((arena) { final outIndex = arena(); @@ -64,11 +73,12 @@ class ListHandle extends CollectionHandleBase { }); } + @override void clear() { realmLib.realm_list_clear(pointer).raiseLastErrorIfFalse(); } - // TODO: avoid taking the [realm] parameter + @override Object? elementAt(Realm realm, int index) { return using((arena) { final realmValue = arena(); @@ -81,7 +91,8 @@ class ListHandle extends CollectionHandleBase { }); } - ListHandle? resolveIn(RealmHandle frozenRealm) { + @override + ListHandle? resolveIn(covariant RealmHandle frozenRealm) { return using((arena) { final resultPtr = arena>(); realmLib.realm_list_resolve_in(pointer, frozenRealm.pointer, resultPtr).raiseLastErrorIfFalse(); @@ -90,6 +101,7 @@ class ListHandle extends CollectionHandleBase { } // TODO: Consider splitting into two methods + @override void addOrUpdateAt(int index, Object? value, bool insert) { using((arena) { final realmValue = value.toNative(arena); @@ -97,20 +109,23 @@ class ListHandle extends CollectionHandleBase { }); } - // TODO: avoid taking the [realm] parameter + @override void addOrUpdateCollectionAt(Realm realm, int index, RealmValue value, bool insert) { createCollection(realm, value, () => (insert ? realmLib.realm_list_insert_list : realmLib.realm_list_set_list)(pointer, index), () => (insert ? realmLib.realm_list_insert_dictionary : realmLib.realm_list_set_dictionary)(pointer, index)); } + @override ObjectHandle setEmbeddedAt(int index) { return ObjectHandle(realmLib.realm_list_set_embedded(pointer, index), root); } + @override ObjectHandle insertEmbeddedAt(int index) { return ObjectHandle(realmLib.realm_list_insert_embedded(pointer, index), root); } + @override ResultsHandle query(String query, List args) { return using((arena) { final length = args.length; @@ -131,6 +146,7 @@ class ListHandle extends CollectionHandleBase { }); } + @override NotificationTokenHandle subscribeForNotifications(NotificationsController controller) { return NotificationTokenHandle( realmLib.realm_list_add_notification_callback( diff --git a/packages/realm_dart/lib/src/native/map_changes_handle.dart b/packages/realm_dart/lib/src/handles/native/map_changes_handle.dart similarity index 92% rename from packages/realm_dart/lib/src/native/map_changes_handle.dart rename to packages/realm_dart/lib/src/handles/native/map_changes_handle.dart index bccfbb909..98d695f5a 100644 --- a/packages/realm_dart/lib/src/native/map_changes_handle.dart +++ b/packages/realm_dart/lib/src/handles/native/map_changes_handle.dart @@ -3,17 +3,19 @@ import 'dart:ffi'; -import 'package:realm_dart/src/native/from_native.dart'; - -import '../collections.dart'; +import '../../collections.dart'; import 'ffi.dart'; +import 'from_native.dart'; import 'handle_base.dart'; import 'realm_bindings.dart'; import 'realm_library.dart'; -class MapChangesHandle extends HandleBase { +import '../map_changes_handle.dart' as intf; + +class MapChangesHandle extends HandleBase implements intf.MapChangesHandle { MapChangesHandle(Pointer pointer) : super(pointer, 256); + @override MapChanges get changes { return using((arena) { final outNumDeletions = arena(); diff --git a/packages/realm_dart/lib/src/native/map_handle.dart b/packages/realm_dart/lib/src/handles/native/map_handle.dart similarity index 93% rename from packages/realm_dart/lib/src/native/map_handle.dart rename to packages/realm_dart/lib/src/handles/native/map_handle.dart index 27a96402a..0a3877dd2 100644 --- a/packages/realm_dart/lib/src/native/map_handle.dart +++ b/packages/realm_dart/lib/src/handles/native/map_handle.dart @@ -3,7 +3,7 @@ import 'dart:ffi'; -import '../realm_dart.dart'; +import '../../realm_dart.dart'; import 'collection_handle_base.dart'; import 'convert_native.dart'; import 'error_handling.dart'; @@ -17,9 +17,12 @@ import 'realm_handle.dart'; import 'realm_library.dart'; import 'results_handle.dart'; -class MapHandle extends CollectionHandleBase { +import '../map_handle.dart' as intf; + +class MapHandle extends CollectionHandleBase implements intf.MapHandle { MapHandle(Pointer pointer, RealmHandle root) : super(root, pointer, 96); // TODO: check size + @override int get size { return using((arena) { final outSize = arena(); @@ -28,6 +31,7 @@ class MapHandle extends CollectionHandleBase { }); } + @override bool remove(String key) { return using((arena) { final keyNative = key.toNative(arena); @@ -37,7 +41,7 @@ class MapHandle extends CollectionHandleBase { }); } - // TODO: avoid taking the [realm] parameter + @override Object? find(Realm realm, String key) { return using((arena) { final keyNative = key.toNative(arena); @@ -55,14 +59,17 @@ class MapHandle extends CollectionHandleBase { }); } + @override bool get isValid { return realmLib.realm_dictionary_is_valid(pointer); } + @override void clear() { realmLib.realm_dictionary_clear(pointer).raiseLastErrorIfFalse(); } + @override ResultsHandle get keys { return using((arena) { final outSize = arena(); @@ -72,10 +79,12 @@ class MapHandle extends CollectionHandleBase { }); } + @override ResultsHandle get values { return ResultsHandle(realmLib.realm_dictionary_to_results(pointer), root); } + @override bool containsKey(String key) { return using((arena) { final keyNative = key.toNative(arena); @@ -85,6 +94,7 @@ class MapHandle extends CollectionHandleBase { }); } + @override int indexOf(Object? value) { return using((arena) { // TODO: how should this behave for collections @@ -95,8 +105,10 @@ class MapHandle extends CollectionHandleBase { }); } + @override bool containsValue(Object? value) => indexOf(value) > -1; + @override ObjectHandle insertEmbedded(String key) { return using((arena) { final keyNative = key.toNative(arena); @@ -104,6 +116,7 @@ class MapHandle extends CollectionHandleBase { }); } + @override void insert(String key, Object? value) { using((arena) { final keyNative = key.toNative(arena); @@ -120,6 +133,7 @@ class MapHandle extends CollectionHandleBase { }); } + @override void insertCollection(Realm realm, String key, RealmValue value) { using((arena) { final keyNative = key.toNative(arena); @@ -132,6 +146,7 @@ class MapHandle extends CollectionHandleBase { }); } + @override ResultsHandle query(String query, List args) { return using((arena) { final length = args.length; @@ -153,7 +168,8 @@ class MapHandle extends CollectionHandleBase { }); } - MapHandle? resolveIn(RealmHandle frozenRealm) { + @override + MapHandle? resolveIn(covariant RealmHandle frozenRealm) { return using((arena) { final resultPtr = arena>(); realmLib.realm_dictionary_resolve_in(pointer, frozenRealm.pointer, resultPtr).raiseLastErrorIfFalse(); @@ -161,6 +177,7 @@ class MapHandle extends CollectionHandleBase { }); } + @override NotificationTokenHandle subscribeForNotifications(NotificationsController controller) { return NotificationTokenHandle( realmLib.realm_dictionary_add_notification_callback( diff --git a/packages/realm_dart/lib/src/native/mutable_subscription_set_handle.dart b/packages/realm_dart/lib/src/handles/native/mutable_subscription_set_handle.dart similarity index 74% rename from packages/realm_dart/lib/src/native/mutable_subscription_set_handle.dart rename to packages/realm_dart/lib/src/handles/native/mutable_subscription_set_handle.dart index 8e6a4aeab..70de535df 100644 --- a/packages/realm_dart/lib/src/native/mutable_subscription_set_handle.dart +++ b/packages/realm_dart/lib/src/handles/native/mutable_subscription_set_handle.dart @@ -5,7 +5,7 @@ import 'dart:ffi'; import 'ffi.dart'; -import '../realm_dart.dart'; +import '../../realm_dart.dart'; import 'convert_native.dart'; import 'error_handling.dart'; import 'realm_bindings.dart'; @@ -15,14 +15,18 @@ import 'results_handle.dart'; import 'subscription_handle.dart'; import 'subscription_set_handle.dart'; -class MutableSubscriptionSetHandle extends SubscriptionSetHandle { +import '../mutable_subscription_set_handle.dart' as intf; + +class MutableSubscriptionSetHandle extends SubscriptionSetHandle implements intf.MutableSubscriptionSetHandle { MutableSubscriptionSetHandle(Pointer pointer, RealmHandle root) : super(pointer.cast(), root); Pointer get _mutablePointer => super.pointer.cast(); + @override SubscriptionSetHandle commit() => SubscriptionSetHandle(realmLib.realm_sync_subscription_set_commit(_mutablePointer), root); - SubscriptionHandle insertOrAssignSubscription(ResultsHandle results, String? name, bool update) { + @override + SubscriptionHandle insertOrAssignSubscription(covariant ResultsHandle results, String? name, bool update) { if (!update) { if (name != null && findByName(name) != null) { throw RealmException('Duplicate subscription with name: $name'); @@ -44,7 +48,8 @@ class MutableSubscriptionSetHandle extends SubscriptionSetHandle { }); } - bool erase(SubscriptionHandle subscription) { + @override + bool erase(covariant SubscriptionHandle subscription) { return using((arena) { final outErased = arena(); realmLib @@ -58,6 +63,7 @@ class MutableSubscriptionSetHandle extends SubscriptionSetHandle { }); } + @override bool eraseByName(String name) { return using((arena) { final outErased = arena(); @@ -72,7 +78,8 @@ class MutableSubscriptionSetHandle extends SubscriptionSetHandle { }); } - bool eraseByResults(ResultsHandle results) { + @override + bool eraseByResults(covariant ResultsHandle results) { return using((arena) { final outErased = arena(); realmLib @@ -86,5 +93,13 @@ class MutableSubscriptionSetHandle extends SubscriptionSetHandle { }); } + @override void clear() => realmLib.realm_sync_subscription_set_clear(_mutablePointer).raiseLastErrorIfFalse(); + + @override + // Workaround for weird compiler bug. This should be unnecessary, but if you + // remove this override, you get an error that the method is not implemented. + // I believe this is related to the covariant keyword in the method signature. + // ignore: unnecessary_overrides + SubscriptionHandle? findByResults(covariant ResultsHandle results) => super.findByResults(results); } diff --git a/packages/realm_dart/lib/src/native/notification_token_handle.dart b/packages/realm_dart/lib/src/handles/native/notification_token_handle.dart similarity index 88% rename from packages/realm_dart/lib/src/native/notification_token_handle.dart rename to packages/realm_dart/lib/src/handles/native/notification_token_handle.dart index eebee574e..55a7e7cc4 100644 --- a/packages/realm_dart/lib/src/native/notification_token_handle.dart +++ b/packages/realm_dart/lib/src/handles/native/notification_token_handle.dart @@ -1,6 +1,6 @@ import 'dart:ffi'; -import '../realm_dart.dart'; +import '../../realm_dart.dart'; import 'collection_changes_handle.dart'; import 'from_native.dart'; import 'realm_bindings.dart'; @@ -8,7 +8,9 @@ import 'realm_handle.dart'; import 'realm_library.dart'; import 'rooted_handle.dart'; -class NotificationTokenHandle extends RootedHandleBase { +import '../notification_token_handle.dart' as intf; + +class NotificationTokenHandle extends RootedHandleBase implements intf.NotificationTokenHandle { NotificationTokenHandle(Pointer pointer, RealmHandle root) : super(root, pointer, 32); } diff --git a/packages/realm_dart/lib/src/handles/native/object_changes_handle.dart b/packages/realm_dart/lib/src/handles/native/object_changes_handle.dart new file mode 100644 index 000000000..54e183a4c --- /dev/null +++ b/packages/realm_dart/lib/src/handles/native/object_changes_handle.dart @@ -0,0 +1,32 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import 'dart:ffi'; + +import 'ffi.dart'; +import 'handle_base.dart'; +import 'realm_bindings.dart'; +import 'realm_library.dart'; + +import '../object_changes_handle.dart' as intf; + +class ObjectChangesHandle extends HandleBase implements intf.ObjectChangesHandle { + ObjectChangesHandle(Pointer pointer) : super(pointer, 256); + + @override + bool get isDeleted { + return realmLib.realm_object_changes_is_deleted(pointer); + } + + @override + List get properties { + return using((arena) { + final count = realmLib.realm_object_changes_get_num_modified_properties(pointer); + + final outModified = arena(count); + realmLib.realm_object_changes_get_modified_properties(pointer, outModified, count); + + return outModified.asTypedList(count).toList(); + }); + } +} diff --git a/packages/realm_dart/lib/src/native/object_handle.dart b/packages/realm_dart/lib/src/handles/native/object_handle.dart similarity index 87% rename from packages/realm_dart/lib/src/native/object_handle.dart rename to packages/realm_dart/lib/src/handles/native/object_handle.dart index f561e1815..ecc6e0eed 100644 --- a/packages/realm_dart/lib/src/native/object_handle.dart +++ b/packages/realm_dart/lib/src/handles/native/object_handle.dart @@ -1,17 +1,19 @@ // Copyright 2024 MongoDB, Inc. // SPDX-License-Identifier: Apache-2.0 +// ignore_for_file: annotate_overrides + import 'dart:ffi'; -import '../realm_dart.dart'; +import '../../realm_dart.dart'; import 'collection_handle_base.dart'; import 'convert_native.dart'; import 'error_handling.dart'; import 'ffi.dart'; -import 'handle_base.dart'; import 'list_handle.dart'; import 'map_handle.dart'; import 'notification_token_handle.dart'; +import 'object_changes_handle.dart'; import 'realm_bindings.dart'; import 'realm_handle.dart'; import 'realm_library.dart'; @@ -19,13 +21,17 @@ import 'results_handle.dart'; import 'rooted_handle.dart'; import 'set_handle.dart'; -class ObjectHandle extends RootedHandleBase { +import '../object_handle.dart' as intf; + +class ObjectHandle extends RootedHandleBase implements intf.ObjectHandle { ObjectHandle(Pointer pointer, RealmHandle root) : super(root, pointer, 112); + @override ObjectHandle createEmbedded(int propertyKey) { return ObjectHandle(realmLib.realm_set_embedded(pointer, propertyKey), root); } + @override (ObjectHandle, int) get parent { return using((arena) { final parentPtr = arena>(); @@ -38,16 +44,20 @@ class ObjectHandle extends RootedHandleBase { }); } + @override int get classKey => realmLib.realm_object_get_table(pointer); + @override bool get isValid => realmLib.realm_object_is_valid(pointer); + @override Link get asLink { final realmLink = realmLib.realm_object_as_link(pointer); return Link(realmLink); } // TODO: avoid taking the [realm] parameter + @override Object? getValue(Realm realm, int propertyKey) { return using((arena) { final realmValue = arena(); @@ -62,6 +72,7 @@ class ObjectHandle extends RootedHandleBase { // TODO: value should be RealmValue, and perhaps this method should be combined // with setCollection? + @override void setValue(int propertyKey, Object? value, bool isDefault) { using((arena) { final realmValue = value.toNative(arena); @@ -76,22 +87,27 @@ class ObjectHandle extends RootedHandleBase { }); } + @override ListHandle getList(int propertyKey) { return ListHandle(realmLib.realm_get_list(pointer, propertyKey), root); } + @override SetHandle getSet(int propertyKey) { return SetHandle(realmLib.realm_get_set(pointer, propertyKey), root); } + @override MapHandle getMap(int propertyKey) { return MapHandle(realmLib.realm_get_dictionary(pointer, propertyKey), root); } + @override ResultsHandle getBacklinks(int sourceTableKey, int propertyKey) { return ResultsHandle(realmLib.realm_get_backlinks(pointer, sourceTableKey, propertyKey), root); } + @override void setCollection(Realm realm, int propertyKey, RealmValue value) { createCollection( realm, @@ -101,15 +117,17 @@ class ObjectHandle extends RootedHandleBase { ); } + @override String objectToString() { return realmLib.realm_object_to_string(pointer).cast().toRealmDartString(freeRealmMemory: true)!; } + @override void delete() { realmLib.realm_object_delete(pointer).raiseLastErrorIfFalse(); } - ObjectHandle? resolveIn(RealmHandle frozenRealm) { + ObjectHandle? resolveIn(covariant RealmHandle frozenRealm) { return using((arena) { final resultPtr = arena>(); realmLib.realm_object_resolve_in(pointer, frozenRealm.pointer, resultPtr).raiseLastErrorIfFalse(); @@ -117,6 +135,7 @@ class ObjectHandle extends RootedHandleBase { }); } + @override NotificationTokenHandle subscribeForNotifications(NotificationsController controller, [List? keyPaths]) { return using((arena) { final kpNative = buildAndVerifyKeyPath(keyPaths); @@ -151,16 +170,26 @@ class ObjectHandle extends RootedHandleBase { }); } + void verifyKeyPath(List? keyPaths) => buildAndVerifyKeyPath(keyPaths); + @override // equals handled by HandleBase // ignore: hash_and_equals int get hashCode => asLink.hash; } -extension type Link(realm_link_t link) { +class Link implements intf.Link { + final realm_link link; + + Link(this.link); + + @override int get targetKey => link.target; + + @override int get classKey => link.target_table; + @override int get hash => Object.hash(targetKey, classKey); } @@ -185,22 +214,3 @@ void _objectChangeCallback(Pointer userdata, Pointer controller.onError(RealmError("Error handling change notifications. Error: $e")); } } - -class ObjectChangesHandle extends HandleBase { - ObjectChangesHandle(Pointer pointer) : super(pointer, 256); - - bool get isDeleted { - return realmLib.realm_object_changes_is_deleted(pointer); - } - - List get properties { - return using((arena) { - final count = realmLib.realm_object_changes_get_num_modified_properties(pointer); - - final outModified = arena(count); - realmLib.realm_object_changes_get_modified_properties(pointer, outModified, count); - - return outModified.asTypedList(count).toList(); - }); - } -} diff --git a/packages/realm_dart/lib/src/native/query_handle.dart b/packages/realm_dart/lib/src/handles/native/query_handle.dart similarity index 100% rename from packages/realm_dart/lib/src/native/query_handle.dart rename to packages/realm_dart/lib/src/handles/native/query_handle.dart diff --git a/packages/realm_dart/lib/src/native/realm_bindings.dart b/packages/realm_dart/lib/src/handles/native/realm_bindings.dart similarity index 100% rename from packages/realm_dart/lib/src/native/realm_bindings.dart rename to packages/realm_dart/lib/src/handles/native/realm_bindings.dart diff --git a/packages/realm_dart/lib/src/native/realm_core.dart b/packages/realm_dart/lib/src/handles/native/realm_core.dart similarity index 90% rename from packages/realm_dart/lib/src/native/realm_core.dart rename to packages/realm_dart/lib/src/handles/native/realm_core.dart index 2b2a4830e..e96c1ff15 100644 --- a/packages/realm_dart/lib/src/native/realm_core.dart +++ b/packages/realm_dart/lib/src/handles/native/realm_core.dart @@ -8,16 +8,15 @@ import 'dart:io'; import 'package:crypto/crypto.dart'; import 'package:path/path.dart' as path; import 'package:pubspec_parse/pubspec_parse.dart'; +import 'package:realm_dart/realm.dart'; -import '../init.dart'; -import '../realm_class.dart'; -import '../scheduler.dart'; import 'convert_native.dart'; import 'error_handling.dart'; import 'ffi.dart'; import 'realm_library.dart'; +import 'scheduler_handle.dart'; -final realmCore = RealmCore._(); +import '../realm_core.dart' as intf; final _pluginLib = () { if (!isFlutterPlatform) { @@ -40,12 +39,16 @@ final _pluginLib = () { return pluginLib; }(); -class RealmCore { - RealmCore._(); +const realmCore = RealmCore(); + +class RealmCore implements intf.RealmCore { + const RealmCore(); // For debugging + @override int get threadId => realmLib.realm_dart_get_thread_id(); + @override void clearCachedApps() { realmLib.realm_clear_cached_apps(); } @@ -55,6 +58,7 @@ class RealmCore { // realmLib.realm_dart_gc(); // } + @override void deleteRealmFiles(String path) { using((arena) { final realmDeleted = arena(); @@ -62,6 +66,7 @@ class RealmCore { }); } + @override List getAllCategoryNames() { return using((arena) { final count = realmLib.realm_get_category_names(0, nullptr); @@ -71,6 +76,7 @@ class RealmCore { }); } + @override String getAppDirectory() { try { if (!isFlutterPlatform || Platform.environment.containsKey('FLUTTER_TEST')) { @@ -102,6 +108,7 @@ class RealmCore { } } + @override String getBundleId() { readBundleId() { try { @@ -135,10 +142,12 @@ class RealmCore { return base64Encode(sha256.convert([...salt, ...utf8.encode(bundleId)]).bytes); } + @override String getDefaultBaseUrl() { return realmLib.realm_app_get_default_base_url().cast().toRealmDartString()!; } + @override String getDeviceName() { if (Platform.isAndroid || Platform.isIOS) { return realmLib.realm_dart_get_device_name().cast().toRealmDartString()!; @@ -147,6 +156,7 @@ class RealmCore { return ""; } + @override String getDeviceVersion() { if (Platform.isAndroid || Platform.isIOS) { return realmLib.realm_dart_get_device_version().cast().toRealmDartString()!; @@ -155,20 +165,25 @@ class RealmCore { return ""; } + @override String getRealmLibraryCpuArchitecture() { return realmLib.realm_get_library_cpu_arch().cast().toDartString(); } - void loggerAttach() => realmLib.realm_dart_attach_logger(scheduler.nativePort); + @override + void loggerAttach() => realmLib.realm_dart_attach_logger(schedulerHandle.sendPort.nativePort); - void loggerDetach() => realmLib.realm_dart_detach_logger(scheduler.nativePort); + @override + void loggerDetach() => realmLib.realm_dart_detach_logger(schedulerHandle.sendPort.nativePort); + @override void logMessage(LogCategory category, LogLevel logLevel, String message) { return using((arena) { realmLib.realm_dart_log(logLevel.index, category.toString().toCharPtr(arena), message.toCharPtr(arena)); }); } + @override void setLogLevel(LogLevel level, {required LogCategory category}) { using((arena) { realmLib.realm_set_log_level_category(category.toString().toCharPtr(arena), level.index); @@ -189,6 +204,7 @@ class RealmCore { return realmLib.realm_dart_get_files_path().cast().toRealmDartString()!; } + @override int setAndGetRLimit(int limit) { return using((arena) { final outLimit = arena(); @@ -196,4 +212,9 @@ class RealmCore { return outLimit.value; }); } + + @override + bool checkIfRealmExists(String path) { + return File(path).existsSync(); // TODO: Should this not check that file is an actual realm file? + } } diff --git a/packages/realm_dart/lib/src/native/realm_handle.dart b/packages/realm_dart/lib/src/handles/native/realm_handle.dart similarity index 93% rename from packages/realm_dart/lib/src/native/realm_handle.dart rename to packages/realm_dart/lib/src/handles/native/realm_handle.dart index 91a4ac925..9a5831672 100644 --- a/packages/realm_dart/lib/src/native/realm_handle.dart +++ b/packages/realm_dart/lib/src/handles/native/realm_handle.dart @@ -3,14 +3,15 @@ import 'dart:async'; import 'dart:ffi'; +import 'dart:io'; import 'package:cancellation_token/cancellation_token.dart'; import 'ffi.dart'; import 'package:realm_common/realm_common.dart'; -import '../logging.dart'; -import '../realm_class.dart'; -import '../realm_object.dart'; +import '../../logging.dart'; +import '../../realm_class.dart'; +import '../../realm_object.dart'; import 'config_handle.dart'; import 'convert_native.dart'; import 'error_handling.dart'; @@ -25,7 +26,9 @@ import 'schema_handle.dart'; import 'session_handle.dart'; import 'subscription_set_handle.dart'; -class RealmHandle extends HandleBase { +import '../realm_handle.dart' as intf; + +class RealmHandle extends HandleBase implements intf.RealmHandle { int _counter = 0; final Map> _children = {}; @@ -35,19 +38,27 @@ class RealmHandle extends HandleBase { RealmHandle.unowned(super.pointer) : super.unowned(); factory RealmHandle.open(Configuration config) { + var dir = File(config.path).parent; + if (!dir.existsSync()) { + dir.createSync(recursive: true); + } + final configHandle = ConfigHandle.from(config); + return RealmHandle(realmLib .realm_open(configHandle.pointer) // .raiseLastErrorIfNull()); } - int addChild(RootedHandleBase child) { + @override + int addChild(covariant RootedHandleBase child) { final id = _counter++; _children[id] = WeakReference(child); rootedHandleFinalizer.attach(this, FinalizationToken(this, id), detach: this); return id; } + @override void removeChild(int id) { final child = _children.remove(id); if (child != null) { @@ -65,6 +76,7 @@ class RealmHandle extends HandleBase { } } + @override ObjectHandle createWithPrimaryKey(int classKey, Object? primaryKey) { return using((arena) { final realmValue = primaryKey.toNative(arena); @@ -72,10 +84,12 @@ class RealmHandle extends HandleBase { }); } + @override ObjectHandle create(int classKey) { return ObjectHandle(realmLib.realm_object_create(pointer, classKey), this); } + @override ObjectHandle getOrCreateWithPrimaryKey(int classKey, Object? primaryKey) { return using((arena) { final realmValue = primaryKey.toNative(arena); @@ -92,6 +106,7 @@ class RealmHandle extends HandleBase { }); } + @override bool compact() { return using((arena) { final outDidCompact = arena(); @@ -100,11 +115,13 @@ class RealmHandle extends HandleBase { }); } + @override void writeCopy(Configuration config) { final configHandle = ConfigHandle.from(config); realmLib.realm_convert_with_config(pointer, configHandle.pointer, false).raiseLastErrorIfFalse(); } + @override ResultsHandle queryClass(int classKey, String query, List args) { return using((arena) { final length = args.length; @@ -126,40 +143,50 @@ class RealmHandle extends HandleBase { }); } + @override RealmHandle freeze() => RealmHandle(realmLib.realm_freeze(pointer)); + @override SessionHandle getSession() { return SessionHandle(realmLib.realm_sync_session_get(pointer), this); } + @override bool get isFrozen { return realmLib.realm_is_frozen(pointer.cast()); } + @override SubscriptionSetHandle get subscriptions { return SubscriptionSetHandle(realmLib.realm_sync_get_active_subscription_set(pointer), this); } + @override void disableAutoRefreshForTesting() { realmLib.realm_set_auto_refresh(pointer, false); } + @override void close() { realmLib.realm_close(pointer).raiseLastErrorIfFalse(); } + @override bool get isClosed { return realmLib.realm_is_closed(pointer); } + @override void beginWrite() { realmLib.realm_begin_write(pointer).raiseLastErrorIfFalse(); } + @override void commitWrite() { realmLib.realm_commit(pointer).raiseLastErrorIfFalse(); } + @override Future beginWriteAsync(CancellationToken? ct) { int? id; final completer = CancellableCompleter(ct, onCancel: () { @@ -186,6 +213,7 @@ class RealmHandle extends HandleBase { return completer.future; } + @override Future commitWriteAsync(CancellationToken? ct) { int? id; final completer = CancellableCompleter(ct, onCancel: () { @@ -234,14 +262,17 @@ class RealmHandle extends HandleBase { } } + @override bool get isWritable { return realmLib.realm_is_writable(pointer); } + @override void rollbackWrite() { realmLib.realm_rollback(pointer).raiseLastErrorIfFalse(); } + @override bool refresh() { return using((arena) { final didRefresh = arena(); @@ -250,6 +281,7 @@ class RealmHandle extends HandleBase { }); } + @override Future refreshAsync() async { final completer = Completer(); final callback = Pointer.fromFunction)>(_realmRefreshAsyncCallback); @@ -271,10 +303,12 @@ class RealmHandle extends HandleBase { completer.complete(true); } + @override ResultsHandle findAll(int classKey) { return ResultsHandle(realmLib.realm_object_find_all(pointer, classKey), this); } + @override ObjectHandle? find(int classKey, Object? primaryKey) { return using((arena) { final realmValue = primaryKey.toNative(arena); @@ -288,12 +322,14 @@ class RealmHandle extends HandleBase { }); } - ObjectHandle? findExisting(int classKey, ObjectHandle other) { + @override + ObjectHandle? findExisting(int classKey, covariant ObjectHandle other) { final key = realmLib.realm_object_get_key(other.pointer); return ObjectHandle(realmLib.realm_get_object(pointer, classKey, key), this); } - void renameProperty(String objectType, String oldName, String newName, SchemaHandle schema) { + @override + void renameProperty(String objectType, String oldName, String newName, covariant SchemaHandle schema) { using((arena) { realmLib .realm_schema_rename_property( @@ -307,6 +343,7 @@ class RealmHandle extends HandleBase { }); } + @override bool deleteType(String objectType) { return using((arena) { final tableDeleted = arena(); @@ -315,10 +352,12 @@ class RealmHandle extends HandleBase { }); } + @override ObjectHandle getObject(int classKey, int objectKey) { return ObjectHandle(realmLib.realm_get_object(pointer, classKey, objectKey), this); } + @override CallbackTokenHandle subscribeForSchemaNotifications(Realm realm) { return CallbackTokenHandle( realmLib.realm_add_schema_changed_callback( @@ -331,6 +370,7 @@ class RealmHandle extends HandleBase { ); } + @override RealmSchema readSchema() { return using((arena) { return _readSchema(arena); @@ -397,12 +437,14 @@ class RealmHandle extends HandleBase { return SchemaObject(baseType, type, name, result); } + @override Map getPropertiesMetadata(int classKey, String? primaryKeyName) { return using((arena) { return _getPropertiesMetadata(classKey, primaryKeyName, arena); }); } + @override RealmObjectMetadata getObjectMetadata(SchemaObject schema) { return using((arena) { final found = arena(); @@ -438,7 +480,7 @@ class RealmHandle extends HandleBase { } } -class CallbackTokenHandle extends RootedHandleBase { +class CallbackTokenHandle extends RootedHandleBase implements intf.CallbackTokenHandle { CallbackTokenHandle(Pointer pointer, RealmHandle root) : super(root, pointer, 32); } diff --git a/packages/realm_dart/lib/src/native/realm_library.dart b/packages/realm_dart/lib/src/handles/native/realm_library.dart similarity index 90% rename from packages/realm_dart/lib/src/native/realm_library.dart rename to packages/realm_dart/lib/src/handles/native/realm_library.dart index 678c395b1..a574bc298 100644 --- a/packages/realm_dart/lib/src/native/realm_library.dart +++ b/packages/realm_dart/lib/src/handles/native/realm_library.dart @@ -1,10 +1,11 @@ // Copyright 2024 MongoDB, Inc. // SPDX-License-Identifier: Apache-2.0 -import 'ffi.dart'; import 'package:realm_common/realm_common.dart'; -import 'package:realm_dart/src/init.dart'; -import 'package:realm_dart/src/native/realm_bindings.dart'; + +import 'init.dart'; +import 'ffi.dart'; +import 'realm_bindings.dart'; const bugInTheSdkMessage = "This is likely a bug in the Realm SDK - please file an issue at https://github.com/realm/realm-dart/issues"; diff --git a/packages/realm_dart/lib/src/native/results_handle.dart b/packages/realm_dart/lib/src/handles/native/results_handle.dart similarity index 90% rename from packages/realm_dart/lib/src/native/results_handle.dart rename to packages/realm_dart/lib/src/handles/native/results_handle.dart index 0f4277776..43c32a06f 100644 --- a/packages/realm_dart/lib/src/native/results_handle.dart +++ b/packages/realm_dart/lib/src/handles/native/results_handle.dart @@ -5,7 +5,7 @@ import 'dart:ffi'; import 'ffi.dart'; -import '../realm_dart.dart'; +import '../../realm_dart.dart'; import 'convert_native.dart'; import 'error_handling.dart'; import 'notification_token_handle.dart'; @@ -16,9 +16,12 @@ import 'realm_handle.dart'; import 'realm_library.dart'; import 'rooted_handle.dart'; -class ResultsHandle extends RootedHandleBase { +import '../results_handle.dart' as intf; + +class ResultsHandle extends RootedHandleBase implements intf.ResultsHandle { ResultsHandle(Pointer pointer, RealmHandle root) : super(root, pointer, 872); + @override ResultsHandle queryResults(String query, List args) { return using((arena) { final length = args.length; @@ -39,6 +42,7 @@ class ResultsHandle extends RootedHandleBase { }); } + @override int find(Object? value) { return using((arena) { final outIndex = arena(); @@ -58,10 +62,12 @@ class ResultsHandle extends RootedHandleBase { }); } + @override ObjectHandle getObjectAt(int index) { return ObjectHandle(realmLib.realm_results_get_object(pointer, index), root); } + @override int get count { return using((arena) { final countPtr = arena(); @@ -70,6 +76,7 @@ class ResultsHandle extends RootedHandleBase { }); } + @override bool isValid() { return using((arena) { final isValid = arena(); @@ -78,18 +85,22 @@ class ResultsHandle extends RootedHandleBase { }); } + @override void deleteAll() { realmLib.realm_results_delete_all(pointer).raiseLastErrorIfFalse(); } + @override ResultsHandle snapshot() { return ResultsHandle(realmLib.realm_results_snapshot(pointer), root); } - ResultsHandle resolveIn(RealmHandle realmHandle) { + @override + ResultsHandle resolveIn(covariant RealmHandle realmHandle) { return ResultsHandle(realmLib.realm_results_resolve_in(pointer, realmHandle.pointer), realmHandle); } + @override Object? elementAt(Realm realm, int index) { return using((arena) { final realmValue = arena(); @@ -102,6 +113,7 @@ class ResultsHandle extends RootedHandleBase { }); } + @override NotificationTokenHandle subscribeForNotifications(NotificationsController controller) { return NotificationTokenHandle( realmLib.realm_results_add_notification_callback( diff --git a/packages/realm_dart/lib/src/native/rooted_handle.dart b/packages/realm_dart/lib/src/handles/native/rooted_handle.dart similarity index 100% rename from packages/realm_dart/lib/src/native/rooted_handle.dart rename to packages/realm_dart/lib/src/handles/native/rooted_handle.dart diff --git a/packages/realm_dart/lib/src/handles/native/scheduler_handle.dart b/packages/realm_dart/lib/src/handles/native/scheduler_handle.dart new file mode 100644 index 000000000..de01a2d0a --- /dev/null +++ b/packages/realm_dart/lib/src/handles/native/scheduler_handle.dart @@ -0,0 +1,30 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import 'dart:ffi'; +import 'dart:isolate'; + +import '../../scheduler.dart'; +import 'handle_base.dart'; +import 'realm_bindings.dart'; +import 'realm_library.dart'; + +import '../scheduler_handle.dart' as intf; + +class SchedulerHandle extends HandleBase implements intf.SchedulerHandle { + final SendPort sendPort; + SchedulerHandle._(this.sendPort, Pointer pointer) : super(pointer, 24); + + factory SchedulerHandle(int isolateId, SendPort sendPort) { + final schedulerPtr = realmLib.realm_dart_create_scheduler(isolateId, sendPort.nativePort); + return SchedulerHandle._(sendPort, schedulerPtr); + } + + @override + void invoke(int workQueue) { + final queuePointer = Pointer.fromAddress(workQueue); + realmLib.realm_scheduler_perform_work(queuePointer); + } +} + +final schedulerHandle = scheduler.handle as SchedulerHandle; diff --git a/packages/realm_dart/lib/src/native/schema_handle.dart b/packages/realm_dart/lib/src/handles/native/schema_handle.dart similarity index 95% rename from packages/realm_dart/lib/src/native/schema_handle.dart rename to packages/realm_dart/lib/src/handles/native/schema_handle.dart index e11512d46..5ff6a38cf 100644 --- a/packages/realm_dart/lib/src/native/schema_handle.dart +++ b/packages/realm_dart/lib/src/handles/native/schema_handle.dart @@ -6,13 +6,15 @@ import 'dart:ffi'; import 'ffi.dart'; import 'package:realm_common/realm_common.dart'; -import '../configuration.dart'; +import '../../configuration.dart'; import 'handle_base.dart'; import 'realm_bindings.dart'; import 'realm_library.dart'; import 'to_native.dart'; -class SchemaHandle extends HandleBase { +import '../schema_handle.dart' as intf; + +class SchemaHandle extends HandleBase implements intf.SchemaHandle { SchemaHandle(Pointer pointer) : super(pointer, 24); SchemaHandle.unowned(super.pointer) : super.unowned(); diff --git a/packages/realm_dart/lib/src/native/session_handle.dart b/packages/realm_dart/lib/src/handles/native/session_handle.dart similarity index 90% rename from packages/realm_dart/lib/src/native/session_handle.dart rename to packages/realm_dart/lib/src/handles/native/session_handle.dart index 0d0119d22..dcc3415ca 100644 --- a/packages/realm_dart/lib/src/native/session_handle.dart +++ b/packages/realm_dart/lib/src/handles/native/session_handle.dart @@ -4,38 +4,43 @@ import 'dart:ffi'; import 'package:cancellation_token/cancellation_token.dart'; -import 'ffi.dart'; -import '../realm_dart.dart'; -import '../scheduler.dart'; -import '../session.dart'; +import '../../realm_dart.dart'; +import '../../session.dart'; +import '../session_handle.dart' as intf; import 'convert_native.dart'; +import 'ffi.dart'; import 'handle_base.dart'; import 'realm_bindings.dart'; import 'realm_handle.dart'; import 'realm_library.dart'; import 'rooted_handle.dart'; +import 'scheduler_handle.dart'; import 'user_handle.dart'; -class SessionHandle extends RootedHandleBase { +class SessionHandle extends RootedHandleBase implements intf.SessionHandle { @override bool get shouldRoot => true; SessionHandle(Pointer pointer, RealmHandle root) : super(root, pointer, 24); + @override String get path { return realmLib.realm_sync_session_get_file_path(pointer).cast().toRealmDartString()!; } + @override ConnectionState get connectionState { final value = realmLib.realm_sync_session_get_connection_state(pointer); return ConnectionState.values[value]; } + @override UserHandle get user { return UserHandle(realmLib.realm_sync_session_get_user(pointer)); } + @override SessionState get state { final value = realmLib.realm_sync_session_get_state(pointer); return _convertCoreSessionState(value); @@ -55,14 +60,17 @@ class SessionHandle extends RootedHandleBase { } } + @override void pause() { realmLib.realm_sync_session_pause(pointer); } + @override void resume() { realmLib.realm_sync_session_resume(pointer); } + @override void raiseError(int errorCode, bool isFatal) { using((arena) { final message = "Simulated session error".toCharPtr(arena); @@ -70,11 +78,12 @@ class SessionHandle extends RootedHandleBase { }); } + @override Future waitForUpload([CancellationToken? cancellationToken]) { final completer = CancellableCompleter(cancellationToken); if (!completer.isCancelled) { final callback = Pointer.fromFunction)>(_waitCompletionCallback); - final userdata = realmLib.realm_dart_userdata_async_new(completer, callback.cast(), scheduler.handle.pointer); + final userdata = realmLib.realm_dart_userdata_async_new(completer, callback.cast(), schedulerHandle.pointer); realmLib.realm_sync_session_wait_for_upload_completion( pointer, realmLib.addresses.realm_dart_sync_wait_for_completion_callback, @@ -85,11 +94,12 @@ class SessionHandle extends RootedHandleBase { return completer.future; } + @override Future waitForDownload([CancellationToken? cancellationToken]) { final completer = CancellableCompleter(cancellationToken); if (!completer.isCancelled) { final callback = Pointer.fromFunction)>(_waitCompletionCallback); - final userdata = realmLib.realm_dart_userdata_async_new(completer, callback.cast(), scheduler.handle.pointer); + final userdata = realmLib.realm_dart_userdata_async_new(completer, callback.cast(), schedulerHandle.pointer); realmLib.realm_sync_session_wait_for_download_completion( pointer, realmLib.addresses.realm_dart_sync_wait_for_completion_callback, @@ -113,9 +123,10 @@ class SessionHandle extends RootedHandleBase { } } + @override SyncSessionNotificationTokenHandle subscribeForConnectionStateNotifications(SessionConnectionStateController controller) { final callback = Pointer.fromFunction(_onConnectionStateChange); - final userdata = realmLib.realm_dart_userdata_async_new(controller, callback.cast(), scheduler.handle.pointer); + final userdata = realmLib.realm_dart_userdata_async_new(controller, callback.cast(), schedulerHandle.pointer); return SyncSessionNotificationTokenHandle( realmLib.realm_sync_session_register_connection_state_change_callback( pointer, @@ -126,6 +137,7 @@ class SessionHandle extends RootedHandleBase { ); } + @override SyncSessionNotificationTokenHandle subscribeForProgressNotifications( ProgressDirection direction, ProgressMode mode, @@ -133,7 +145,7 @@ class SessionHandle extends RootedHandleBase { ) { final isStreaming = mode == ProgressMode.reportIndefinitely; final callback = Pointer.fromFunction(syncProgressCallback); - final userdata = realmLib.realm_dart_userdata_async_new(controller, callback.cast(), scheduler.handle.pointer); + final userdata = realmLib.realm_dart_userdata_async_new(controller, callback.cast(), schedulerHandle.pointer); return SyncSessionNotificationTokenHandle( realmLib.realm_sync_session_register_progress_notifier( pointer, @@ -147,7 +159,8 @@ class SessionHandle extends RootedHandleBase { } } -class SyncSessionNotificationTokenHandle extends HandleBase { +class SyncSessionNotificationTokenHandle extends HandleBase + implements intf.SyncSessionNotificationTokenHandle { SyncSessionNotificationTokenHandle(Pointer pointer) : super(pointer, 32); } diff --git a/packages/realm_dart/lib/src/native/set_handle.dart b/packages/realm_dart/lib/src/handles/native/set_handle.dart similarity index 91% rename from packages/realm_dart/lib/src/native/set_handle.dart rename to packages/realm_dart/lib/src/handles/native/set_handle.dart index 6135ae620..0a2de4a01 100644 --- a/packages/realm_dart/lib/src/native/set_handle.dart +++ b/packages/realm_dart/lib/src/handles/native/set_handle.dart @@ -5,7 +5,7 @@ import 'dart:ffi'; import 'ffi.dart'; -import '../realm_dart.dart'; +import '../../realm_dart.dart'; import 'convert_native.dart'; import 'error_handling.dart'; import 'notification_token_handle.dart'; @@ -16,13 +16,17 @@ import 'realm_library.dart'; import 'results_handle.dart'; import 'rooted_handle.dart'; -class SetHandle extends RootedHandleBase { +import '../set_handle.dart' as intf; + +class SetHandle extends RootedHandleBase implements intf.SetHandle { SetHandle(Pointer pointer, RealmHandle root) : super(root, pointer, 96); + @override ResultsHandle get asResults { return ResultsHandle(realmLib.realm_set_to_results(pointer), root); } + @override ResultsHandle query(String query, List args) { return using((arena) { final length = args.length; @@ -43,6 +47,7 @@ class SetHandle extends RootedHandleBase { }); } + @override bool insert(Object? value) { return using((arena) { final realmValue = value.toNative(arena); @@ -54,6 +59,7 @@ class SetHandle extends RootedHandleBase { } // TODO: avoid taking the [realm] parameter + @override Object? elementAt(Realm realm, int index) { return using((arena) { final realmValue = arena(); @@ -67,6 +73,7 @@ class SetHandle extends RootedHandleBase { }); } + @override bool find(Object? value) { return using((arena) { // TODO: how should this behave for collections @@ -78,6 +85,7 @@ class SetHandle extends RootedHandleBase { }); } + @override bool remove(Object? value) { return using((arena) { // TODO: do we support sets containing mixed collections @@ -88,10 +96,12 @@ class SetHandle extends RootedHandleBase { }); } + @override void clear() { realmLib.realm_set_clear(pointer).raiseLastErrorIfFalse(); } + @override int get size { return using((arena) { final outSize = arena(); @@ -100,15 +110,18 @@ class SetHandle extends RootedHandleBase { }); } + @override bool get isValid { return realmLib.realm_set_is_valid(pointer); } + @override void deleteAll() { realmLib.realm_set_remove_all(pointer).raiseLastErrorIfFalse(); } - SetHandle? resolveIn(RealmHandle frozenRealm) { + @override + SetHandle? resolveIn(covariant RealmHandle frozenRealm) { return using((arena) { final resultPtr = arena>(); realmLib.realm_set_resolve_in(pointer, frozenRealm.pointer, resultPtr).raiseLastErrorIfFalse(); @@ -116,6 +129,7 @@ class SetHandle extends RootedHandleBase { }); } + @override NotificationTokenHandle subscribeForNotifications(NotificationsController controller) { return NotificationTokenHandle( realmLib.realm_set_add_notification_callback( diff --git a/packages/realm_dart/lib/src/native/subscription_handle.dart b/packages/realm_dart/lib/src/handles/native/subscription_handle.dart similarity index 73% rename from packages/realm_dart/lib/src/native/subscription_handle.dart rename to packages/realm_dart/lib/src/handles/native/subscription_handle.dart index 06eb4cf87..b2b6b4fed 100644 --- a/packages/realm_dart/lib/src/native/subscription_handle.dart +++ b/packages/realm_dart/lib/src/handles/native/subscription_handle.dart @@ -3,26 +3,34 @@ import 'dart:ffi'; -import '../realm_class.dart'; +import '../../realm_class.dart'; import 'from_native.dart'; import 'handle_base.dart'; import 'realm_bindings.dart'; import 'realm_library.dart'; -class SubscriptionHandle extends HandleBase { +import '../subscription_handle.dart' as intf; +class SubscriptionHandle extends HandleBase implements intf.SubscriptionHandle{ SubscriptionHandle(Pointer pointer) : super(pointer, 184); + @override ObjectId get id => realmLib.realm_sync_subscription_id(pointer).toDart(); + @override String? get name => realmLib.realm_sync_subscription_name(pointer).toDart(); + @override String get objectClassName => realmLib.realm_sync_subscription_object_class_name(pointer).toDart()!; + @override String get queryString => realmLib.realm_sync_subscription_query_string(pointer).toDart()!; + @override DateTime get createdAt => realmLib.realm_sync_subscription_created_at(pointer).toDart(); + @override DateTime get updatedAt => realmLib.realm_sync_subscription_updated_at(pointer).toDart(); - bool equalTo(SubscriptionHandle other) => realmLib.realm_equals(pointer.cast(), other.pointer.cast()); + @override + bool equalTo(covariant SubscriptionHandle other) => realmLib.realm_equals(pointer.cast(), other.pointer.cast()); } diff --git a/packages/realm_dart/lib/src/native/subscription_set_handle.dart b/packages/realm_dart/lib/src/handles/native/subscription_set_handle.dart similarity index 87% rename from packages/realm_dart/lib/src/native/subscription_set_handle.dart rename to packages/realm_dart/lib/src/handles/native/subscription_set_handle.dart index a4eadae0a..271291913 100644 --- a/packages/realm_dart/lib/src/native/subscription_set_handle.dart +++ b/packages/realm_dart/lib/src/handles/native/subscription_set_handle.dart @@ -4,39 +4,44 @@ import 'dart:ffi'; import 'package:cancellation_token/cancellation_token.dart'; -import 'ffi.dart'; -import '../realm_dart.dart'; -import '../scheduler.dart'; +import '../../realm_dart.dart'; +import '../subscription_set_handle.dart' as intf; import 'convert.dart'; import 'convert_native.dart'; import 'error_handling.dart'; +import 'ffi.dart'; import 'mutable_subscription_set_handle.dart'; import 'realm_bindings.dart'; import 'realm_handle.dart'; import 'realm_library.dart'; import 'results_handle.dart'; import 'rooted_handle.dart'; +import 'scheduler_handle.dart'; import 'subscription_handle.dart'; - -class SubscriptionSetHandle extends RootedHandleBase { +class SubscriptionSetHandle extends RootedHandleBase implements intf.SubscriptionSetHandle { @override bool get shouldRoot => true; SubscriptionSetHandle(Pointer pointer, RealmHandle root) : super(root, pointer, 128); + @override void refresh() => realmLib.realm_sync_subscription_set_refresh(pointer).raiseLastErrorIfFalse(); + @override int get size => realmLib.realm_sync_subscription_set_size(pointer); + @override Exception? get error { final error = realmLib.realm_sync_subscription_set_error_str(pointer); final message = error.cast().toRealmDartString(treatEmptyAsNull: true); return message.convert(RealmException.new); } + @override SubscriptionHandle operator [](int index) => SubscriptionHandle(realmLib.realm_sync_subscription_at(pointer, index)); + @override SubscriptionHandle? findByName(String name) { return using((arena) { final result = realmLib.realm_sync_find_subscription_by_name( @@ -47,7 +52,8 @@ class SubscriptionSetHandle extends RootedHandleBase realmLib.realm_sync_subscription_set_version(pointer); + @override SubscriptionSetState get state => SubscriptionSetState.values[realmLib.realm_sync_subscription_set_state(pointer)]; + @override MutableSubscriptionSetHandle toMutable() => MutableSubscriptionSetHandle(realmLib.realm_sync_make_subscription_set_mutable(pointer), root); static void _stateChangeCallback(Object userdata, int state) { @@ -68,11 +77,12 @@ class SubscriptionSetHandle extends RootedHandleBase waitForStateChange(SubscriptionSetState notifyWhen, [CancellationToken? cancellationToken]) { final completer = CancellableCompleter(cancellationToken); if (!completer.isCancelled) { final callback = Pointer.fromFunction(_stateChangeCallback); - final userdata = realmLib.realm_dart_userdata_async_new(completer, callback.cast(), scheduler.handle.pointer); + final userdata = realmLib.realm_dart_userdata_async_new(completer, callback.cast(), schedulerHandle.pointer); realmLib.realm_sync_on_subscription_set_state_change_async(pointer, notifyWhen.index, realmLib.addresses.realm_dart_sync_on_subscription_state_changed_callback, userdata.cast(), realmLib.addresses.realm_dart_userdata_async_free); } diff --git a/packages/realm_dart/lib/src/native/to_native.dart b/packages/realm_dart/lib/src/handles/native/to_native.dart similarity index 98% rename from packages/realm_dart/lib/src/native/to_native.dart rename to packages/realm_dart/lib/src/handles/native/to_native.dart index 5497146fc..476a5fbe5 100644 --- a/packages/realm_dart/lib/src/native/to_native.dart +++ b/packages/realm_dart/lib/src/handles/native/to_native.dart @@ -2,12 +2,12 @@ import 'dart:convert'; import 'dart:ffi'; import 'dart:typed_data'; -import 'package:realm_dart/src/native/realm_library.dart'; +import 'package:realm_common/realm_common.dart' hide Decimal128; -import '../realm_dart.dart'; -import '../realm_object.dart'; +import '../../realm_object.dart'; import 'decimal128.dart'; import 'realm_bindings.dart'; +import 'realm_library.dart'; extension ListEx on List { Pointer toCharPtr(Allocator allocator) { diff --git a/packages/realm_dart/lib/src/native/user_handle.dart b/packages/realm_dart/lib/src/handles/native/user_handle.dart similarity index 89% rename from packages/realm_dart/lib/src/native/user_handle.dart rename to packages/realm_dart/lib/src/handles/native/user_handle.dart index 1092a0143..643f8b20b 100644 --- a/packages/realm_dart/lib/src/native/user_handle.dart +++ b/packages/realm_dart/lib/src/handles/native/user_handle.dart @@ -5,40 +5,45 @@ import 'dart:async'; import 'dart:convert'; import 'dart:ffi'; -import 'ffi.dart'; - -import '../credentials.dart'; -import '../realm_dart.dart'; -import '../scheduler.dart'; -import '../user.dart'; +import '../../credentials.dart'; +import '../../realm_dart.dart'; +import '../../user.dart'; import 'app_handle.dart'; import 'convert.dart'; import 'convert_native.dart'; import 'credentials_handle.dart'; import 'error_handling.dart'; +import 'ffi.dart'; import 'handle_base.dart'; import 'realm_bindings.dart'; import 'realm_library.dart'; +import 'scheduler_handle.dart'; + +import '../user_handle.dart' as intf; -class UserHandle extends HandleBase { +class UserHandle extends HandleBase implements intf.UserHandle { UserHandle(Pointer pointer) : super(pointer, 24); + @override AppHandle get app { return realmLib.realm_user_get_app(pointer).convert(AppHandle.new) ?? (throw RealmException('User does not have an associated app. This is likely due to the user being logged out.')); } + @override UserState get state { final nativeUserState = realmLib.realm_user_get_state(pointer); return UserState.values.fromIndex(nativeUserState); } + @override String get id { final idPtr = realmLib.realm_user_get_identity(pointer).raiseLastErrorIfNull(); final userId = idPtr.cast().toDartString(); return userId; } + @override List get identities { return using((arena) { return _userGetIdentities(arena); @@ -67,31 +72,37 @@ class UserHandle extends HandleBase { return result; } + @override Future logOut() async { realmLib.realm_user_log_out(pointer).raiseLastErrorIfFalse(); } + @override String? get deviceId { final deviceId = realmLib.realm_user_get_device_id(pointer).raiseLastErrorIfNull(); return deviceId.cast().toRealmDartString(treatEmptyAsNull: true, freeRealmMemory: true); } + @override UserProfile get profileData { final data = realmLib.realm_user_get_profile_data(pointer).raiseLastErrorIfNull(); final dynamic profileData = jsonDecode(data.cast().toRealmDartString(freeRealmMemory: true)!); return UserProfile(profileData as Map); } + @override String get refreshToken { final token = realmLib.realm_user_get_refresh_token(pointer).raiseLastErrorIfNull(); return token.cast().toRealmDartString(freeRealmMemory: true)!; } + @override String get accessToken { final token = realmLib.realm_user_get_access_token(pointer).raiseLastErrorIfNull(); return token.cast().toRealmDartString(freeRealmMemory: true)!; } + @override String get path { final syncConfigPtr = realmLib.realm_flx_sync_config_new(pointer).raiseLastErrorIfNull(); try { @@ -102,12 +113,14 @@ class UserHandle extends HandleBase { } } + @override String? get customData { final customDataPtr = realmLib.realm_user_get_custom_data(pointer); return customDataPtr.cast().toRealmDartString(freeRealmMemory: true, treatEmptyAsNull: true); } - Future linkCredentials(AppHandle app, CredentialsHandle credentials) { + @override + Future linkCredentials(covariant AppHandle app, covariant CredentialsHandle credentials) { final completer = Completer(); realmLib .realm_app_link_user( @@ -122,7 +135,8 @@ class UserHandle extends HandleBase { return completer.future; } - Future createApiKey(AppHandle app, String name) { + @override + Future createApiKey(covariant AppHandle app, String name) { return using((arena) { final namePtr = name.toCharPtr(arena); final completer = Completer(); @@ -141,7 +155,8 @@ class UserHandle extends HandleBase { }); } - Future fetchApiKey(AppHandle app, ObjectId id) { + @override + Future fetchApiKey(covariant AppHandle app, ObjectId id) { return using((arena) { final completer = Completer(); final nativeId = id.toNative(arena); @@ -160,7 +175,8 @@ class UserHandle extends HandleBase { }); } - Future> fetchAllApiKeys(AppHandle app) { + @override + Future> fetchAllApiKeys(covariant AppHandle app) { return using((arena) { final completer = Completer>(); realmLib @@ -177,7 +193,8 @@ class UserHandle extends HandleBase { }); } - Future deleteApiKey(AppHandle app, ObjectId id) { + @override + Future deleteApiKey(covariant AppHandle app, ObjectId id) { return using((arena) { final completer = Completer(); final nativeId = id.toNative(arena); @@ -196,7 +213,8 @@ class UserHandle extends HandleBase { }); } - Future disableApiKey(AppHandle app, ObjectId objectId) { + @override + Future disableApiKey(covariant AppHandle app, ObjectId objectId) { return using((arena) { final completer = Completer(); final nativeId = objectId.toNative(arena); @@ -216,7 +234,8 @@ class UserHandle extends HandleBase { }); } - Future enableApiKey(AppHandle app, ObjectId objectId) { + @override + Future enableApiKey(covariant AppHandle app, ObjectId objectId) { return using((arena) { final completer = Completer(); final nativeId = objectId.toNative(arena); @@ -236,9 +255,10 @@ class UserHandle extends HandleBase { }); } + @override UserNotificationTokenHandle subscribeForNotifications(UserNotificationsController controller) { final callback = Pointer.fromFunction(_userChangeCallback); - final userdata = realmLib.realm_dart_userdata_async_new(controller, callback.cast(), scheduler.handle.pointer); + final userdata = realmLib.realm_dart_userdata_async_new(controller, callback.cast(), schedulerHandle.pointer); final notificationToken = realmLib.realm_sync_user_on_state_change_register_callback( pointer, realmLib.addresses.realm_dart_user_change_callback, @@ -249,7 +269,7 @@ class UserHandle extends HandleBase { } } -class UserNotificationTokenHandle extends HandleBase { +class UserNotificationTokenHandle extends HandleBase implements intf.UserNotificationTokenHandle { UserNotificationTokenHandle(Pointer pointer) : super(pointer, 32); } @@ -270,7 +290,7 @@ Pointer createAsyncUserCallbackUserdata(Completer completer) { final userdata = realmLib.realm_dart_userdata_async_new( completer, callback.cast(), - scheduler.handle.pointer, + schedulerHandle.pointer, ); return userdata.cast(); @@ -304,7 +324,7 @@ Pointer _createAsyncApikeyCallbackUserdata(Completer _createAsyncApikeyListCallbackUserdata(Complet final userdata = realmLib.realm_dart_userdata_async_new( completer, callback.cast(), - scheduler.handle.pointer, + schedulerHandle.pointer, ); return userdata.cast(); diff --git a/packages/realm_dart/lib/src/handles/notification_token_handle.dart b/packages/realm_dart/lib/src/handles/notification_token_handle.dart new file mode 100644 index 000000000..a1698ea02 --- /dev/null +++ b/packages/realm_dart/lib/src/handles/notification_token_handle.dart @@ -0,0 +1,6 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import 'handle_base.dart'; + +abstract interface class NotificationTokenHandle extends HandleBase {} diff --git a/packages/realm_dart/lib/src/handles/object_changes_handle.dart b/packages/realm_dart/lib/src/handles/object_changes_handle.dart new file mode 100644 index 000000000..6a42fa2d7 --- /dev/null +++ b/packages/realm_dart/lib/src/handles/object_changes_handle.dart @@ -0,0 +1,9 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import 'handle_base.dart'; + +abstract interface class ObjectChangesHandle extends HandleBase { + bool get isDeleted; + List get properties; +} diff --git a/packages/realm_dart/lib/src/handles/object_handle.dart b/packages/realm_dart/lib/src/handles/object_handle.dart new file mode 100644 index 000000000..a3ceaa60a --- /dev/null +++ b/packages/realm_dart/lib/src/handles/object_handle.dart @@ -0,0 +1,55 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import '../realm_class.dart'; +import 'handle_base.dart'; +import 'list_handle.dart'; +import 'map_handle.dart'; +import 'notification_token_handle.dart'; +import 'realm_handle.dart'; +import 'results_handle.dart'; +import 'set_handle.dart'; + +abstract interface class ObjectHandle extends HandleBase { + ObjectHandle createEmbedded(int propertyKey); + (ObjectHandle, int) get parent; + + int get classKey; + + bool get isValid; + + Link get asLink; + + // TODO: avoid taking the [realm] parameter + Object? getValue(Realm realm, int propertyKey); + + // TODO: value should be RealmValue, and perhaps this method should be combined + // with setCollection? + void setValue(int propertyKey, Object? value, bool isDefault); + ListHandle getList(int propertyKey); + SetHandle getSet(int propertyKey); + MapHandle getMap(int propertyKey); + ResultsHandle getBacklinks(int sourceTableKey, int propertyKey); + + void setCollection(Realm realm, int propertyKey, RealmValue value); + + String objectToString(); + void delete(); + + ObjectHandle? resolveIn(RealmHandle frozenRealm); + + NotificationTokenHandle subscribeForNotifications(NotificationsController controller, [List? keyPaths]); + + void verifyKeyPath(List? keyPaths); + + @override + // equals handled by HandleBase + // ignore: hash_and_equals + int get hashCode => asLink.hash; +} + +abstract class Link { + int get targetKey; + int get classKey; + int get hash; +} diff --git a/packages/realm_dart/lib/src/handles/realm_core.dart b/packages/realm_dart/lib/src/handles/realm_core.dart new file mode 100644 index 000000000..169db13c3 --- /dev/null +++ b/packages/realm_dart/lib/src/handles/realm_core.dart @@ -0,0 +1,33 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import 'package:realm_dart/realm.dart'; + +import 'native/realm_core.dart' if (dart.library.js_interop) 'web/realm_core.dart' as impl; + +abstract interface class RealmCore { + int get threadId; + + void clearCachedApps(); + + String getAppDirectory(); + String getBundleId(); + String getDeviceName(); + String getDeviceVersion(); + String getRealmLibraryCpuArchitecture(); + + String getDefaultBaseUrl(); + + void loggerAttach(); + void loggerDetach(); + List getAllCategoryNames(); + void setLogLevel(LogLevel level, {required LogCategory category}); + void logMessage(LogCategory category, LogLevel logLevel, String message); + + int setAndGetRLimit(int limit); + + bool checkIfRealmExists(String path); + void deleteRealmFiles(String path); +} + +const realmCore = impl.RealmCore(); diff --git a/packages/realm_dart/lib/src/handles/realm_handle.dart b/packages/realm_dart/lib/src/handles/realm_handle.dart new file mode 100644 index 000000000..a70d0817f --- /dev/null +++ b/packages/realm_dart/lib/src/handles/realm_handle.dart @@ -0,0 +1,69 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import '../realm_class.dart'; +import '../realm_object.dart'; +import 'handle_base.dart'; +import 'object_handle.dart'; +import 'results_handle.dart'; +import 'schema_handle.dart'; +import 'session_handle.dart'; +import 'subscription_set_handle.dart'; + +import 'native/realm_handle.dart' if (dart.library.js_interop) 'web/realm_handle.dart' as impl; + +abstract interface class RealmHandle extends HandleBase { + factory RealmHandle.open(Configuration config) = impl.RealmHandle.open; + + int addChild(HandleBase child); + void removeChild(int id); + + @override + void releaseCore(); + + ObjectHandle createWithPrimaryKey(int classKey, Object? primaryKey); + + ObjectHandle create(int classKey); + ObjectHandle getOrCreateWithPrimaryKey(int classKey, Object? primaryKey); + + bool compact(); + + void writeCopy(Configuration config); + ResultsHandle queryClass(int classKey, String query, List args); + RealmHandle freeze(); + SessionHandle getSession(); + bool get isFrozen; + + SubscriptionSetHandle get subscriptions; + + void disableAutoRefreshForTesting(); + void close(); + + bool get isClosed; + + void beginWrite(); + + void commitWrite(); + + Future beginWriteAsync(CancellationToken? ct); + Future commitWriteAsync(CancellationToken? ct); + bool get isWritable; + void rollbackWrite(); + bool refresh(); + Future refreshAsync(); + ResultsHandle findAll(int classKey); + ObjectHandle? find(int classKey, Object? primaryKey); + ObjectHandle? findExisting(int classKey, ObjectHandle other); + void renameProperty(String objectType, String oldName, String newName, SchemaHandle schema); + bool deleteType(String objectType); + ObjectHandle getObject(int classKey, int objectKey); + + CallbackTokenHandle subscribeForSchemaNotifications(Realm realm); + + RealmSchema readSchema(); + Map getPropertiesMetadata(int classKey, String? primaryKeyName); + + RealmObjectMetadata getObjectMetadata(SchemaObject schema); +} + +abstract class CallbackTokenHandle extends HandleBase {} diff --git a/packages/realm_dart/lib/src/handles/results_handle.dart b/packages/realm_dart/lib/src/handles/results_handle.dart new file mode 100644 index 000000000..69e05cc42 --- /dev/null +++ b/packages/realm_dart/lib/src/handles/results_handle.dart @@ -0,0 +1,28 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import 'package:realm_dart/src/handles/handle_base.dart'; +import 'package:realm_dart/src/handles/notification_token_handle.dart'; + +import '../realm_class.dart'; +import 'object_handle.dart'; +import 'realm_handle.dart'; + +abstract interface class ResultsHandle extends HandleBase { + ResultsHandle queryResults(String query, List args); + + int find(Object? value); + + ObjectHandle getObjectAt(int index); + + int get count; + + bool isValid(); + + void deleteAll(); + ResultsHandle snapshot(); + ResultsHandle resolveIn(RealmHandle realmHandle); + + Object? elementAt(Realm realm, int index); + NotificationTokenHandle subscribeForNotifications(NotificationsController controller); +} diff --git a/packages/realm_dart/lib/src/handles/scheduler_handle.dart b/packages/realm_dart/lib/src/handles/scheduler_handle.dart new file mode 100644 index 000000000..4bc340cf2 --- /dev/null +++ b/packages/realm_dart/lib/src/handles/scheduler_handle.dart @@ -0,0 +1,14 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import 'dart:isolate'; + +import 'handle_base.dart'; + +import 'native/scheduler_handle.dart' if (dart.library.js_interop) 'web/scheduler_handle.dart' as impl; + +abstract interface class SchedulerHandle extends HandleBase { + factory SchedulerHandle(int isolateId, SendPort port) = impl.SchedulerHandle; + + void invoke(int workQueue); +} diff --git a/packages/realm_dart/lib/src/handles/schema_handle.dart b/packages/realm_dart/lib/src/handles/schema_handle.dart new file mode 100644 index 000000000..39d153028 --- /dev/null +++ b/packages/realm_dart/lib/src/handles/schema_handle.dart @@ -0,0 +1,11 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import '../configuration.dart'; +import 'handle_base.dart'; + +import 'native/schema_handle.dart' if (dart.library.js_interop) 'web/schema_handle.dart' as impl; + +abstract interface class SchemaHandle extends HandleBase { + factory SchemaHandle.from(Iterable schema) = impl.SchemaHandle.from; +} diff --git a/packages/realm_dart/lib/src/handles/session_handle.dart b/packages/realm_dart/lib/src/handles/session_handle.dart new file mode 100644 index 000000000..f5c3aeb15 --- /dev/null +++ b/packages/realm_dart/lib/src/handles/session_handle.dart @@ -0,0 +1,35 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import 'package:cancellation_token/cancellation_token.dart'; + +import '../session.dart'; +import 'handle_base.dart'; +import 'user_handle.dart'; + +abstract interface class SessionHandle extends HandleBase { + String get path; + ConnectionState get connectionState; + + UserHandle get user; + + SessionState get state; + + void pause(); + + void resume(); + + void raiseError(int errorCode, bool isFatal); + + Future waitForUpload([CancellationToken? cancellationToken]); + Future waitForDownload([CancellationToken? cancellationToken]); + SyncSessionNotificationTokenHandle subscribeForConnectionStateNotifications(SessionConnectionStateController controller); + + SyncSessionNotificationTokenHandle subscribeForProgressNotifications( + ProgressDirection direction, + ProgressMode mode, + SessionProgressNotificationsController controller, + ); +} + +abstract interface class SyncSessionNotificationTokenHandle extends HandleBase {} \ No newline at end of file diff --git a/packages/realm_dart/lib/src/handles/set_handle.dart b/packages/realm_dart/lib/src/handles/set_handle.dart new file mode 100644 index 000000000..bbad63523 --- /dev/null +++ b/packages/realm_dart/lib/src/handles/set_handle.dart @@ -0,0 +1,34 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import 'package:realm_dart/src/handles/handle_base.dart'; + +import '../realm_class.dart'; +import 'notification_token_handle.dart'; +import 'realm_handle.dart'; +import 'results_handle.dart'; + +abstract interface class SetHandle extends HandleBase { + ResultsHandle get asResults; + + ResultsHandle query(String query, List args); + + bool insert(Object? value); + + // TODO: avoid taking the [realm] parameter + Object? elementAt(Realm realm, int index); + bool find(Object? value); + + bool remove(Object? value); + + void clear(); + int get size; + + bool get isValid; + + void deleteAll(); + + SetHandle? resolveIn(RealmHandle frozenRealm); + + NotificationTokenHandle subscribeForNotifications(NotificationsController controller); +} diff --git a/packages/realm_dart/lib/src/handles/subscription_handle.dart b/packages/realm_dart/lib/src/handles/subscription_handle.dart new file mode 100644 index 000000000..7f3cc40e9 --- /dev/null +++ b/packages/realm_dart/lib/src/handles/subscription_handle.dart @@ -0,0 +1,16 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import 'package:realm_common/realm_common.dart'; + +import 'handle_base.dart'; + +abstract interface class SubscriptionHandle extends HandleBase { + ObjectId get id; + String? get name; + String get objectClassName; + String get queryString; + DateTime get createdAt; + DateTime get updatedAt; + bool equalTo(SubscriptionHandle other); +} diff --git a/packages/realm_dart/lib/src/handles/subscription_set_handle.dart b/packages/realm_dart/lib/src/handles/subscription_set_handle.dart new file mode 100644 index 000000000..82a2e1abe --- /dev/null +++ b/packages/realm_dart/lib/src/handles/subscription_set_handle.dart @@ -0,0 +1,31 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import 'package:cancellation_token/cancellation_token.dart'; + +import '../subscription.dart'; +import 'handle_base.dart'; +import 'mutable_subscription_set_handle.dart'; +import 'results_handle.dart'; +import 'subscription_handle.dart'; + +abstract class SubscriptionSetHandle extends HandleBase { + void refresh(); + + int get size; + + Exception? get error; + + SubscriptionHandle operator [](int index); + + SubscriptionHandle? findByName(String name); + + SubscriptionHandle? findByResults(ResultsHandle results); + + int get version; + SubscriptionSetState get state; + + MutableSubscriptionSetHandle toMutable(); + + Future waitForStateChange(SubscriptionSetState notifyWhen, [CancellationToken? cancellationToken]); +} diff --git a/packages/realm_dart/lib/src/handles/user_handle.dart b/packages/realm_dart/lib/src/handles/user_handle.dart new file mode 100644 index 000000000..cf498b10f --- /dev/null +++ b/packages/realm_dart/lib/src/handles/user_handle.dart @@ -0,0 +1,33 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import 'package:realm_common/realm_common.dart'; + +import '../user.dart'; +import 'app_handle.dart'; +import 'credentials_handle.dart'; +import 'handle_base.dart'; + +abstract interface class UserHandle extends HandleBase { + AppHandle get app; + UserState get state; + String get id; + List get identities; + Future logOut(); + String? get deviceId; + UserProfile get profileData; + String get refreshToken; + String get accessToken; + String get path; + String? get customData; + Future linkCredentials(AppHandle app, CredentialsHandle credentials); + Future createApiKey(AppHandle app, String name); + Future fetchApiKey(AppHandle app, ObjectId id); + Future> fetchAllApiKeys(AppHandle app); + Future deleteApiKey(AppHandle app, ObjectId id); + Future disableApiKey(AppHandle app, ObjectId objectId); + Future enableApiKey(AppHandle app, ObjectId objectId); + UserNotificationTokenHandle subscribeForNotifications(UserNotificationsController controller); +} + +abstract interface class UserNotificationTokenHandle extends HandleBase {} diff --git a/packages/realm_dart/lib/src/handles/web/app_handle.dart b/packages/realm_dart/lib/src/handles/web/app_handle.dart new file mode 100644 index 000000000..9a20302be --- /dev/null +++ b/packages/realm_dart/lib/src/handles/web/app_handle.dart @@ -0,0 +1,11 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import '../../../realm.dart'; +import '../app_handle.dart' as intf; +import 'handle_base.dart'; + +class AppHandle extends HandleBase implements intf.AppHandle { + factory AppHandle.from(AppConfiguration configuration) => webNotSupported(); + static AppHandle? get(String id, String? baseUrl) => webNotSupported(); +} diff --git a/packages/realm_dart/lib/src/handles/web/async_open_task_handle.dart b/packages/realm_dart/lib/src/handles/web/async_open_task_handle.dart new file mode 100644 index 000000000..35d716b51 --- /dev/null +++ b/packages/realm_dart/lib/src/handles/web/async_open_task_handle.dart @@ -0,0 +1,19 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import 'package:realm_dart/realm.dart'; + +import '../async_open_task_handle.dart' as intf; +import 'handle_base.dart'; + +class AsyncOpenTaskHandle extends HandleBase implements intf.AsyncOpenTaskHandle { + factory AsyncOpenTaskHandle.from(FlexibleSyncConfiguration config) => webNotSupported(); + + @override + noSuchMethod(Invocation invocation) => webNotSupported(); +} + +abstract class AsyncOpenTaskProgressNotificationTokenHandle implements intf.AsyncOpenTaskProgressNotificationTokenHandle { + @override + noSuchMethod(Invocation invocation) => webNotSupported(); +} diff --git a/packages/realm_dart/lib/src/handles/web/collection_changes_handle.dart b/packages/realm_dart/lib/src/handles/web/collection_changes_handle.dart new file mode 100644 index 000000000..9714b860b --- /dev/null +++ b/packages/realm_dart/lib/src/handles/web/collection_changes_handle.dart @@ -0,0 +1,7 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import '../collection_changes_handle.dart' as intf; +import 'handle_base.dart'; + +class CollectionChangesHandle extends HandleBase implements intf.CollectionChangesHandle {} diff --git a/packages/realm_dart/lib/src/handles/web/credentials_handle.dart b/packages/realm_dart/lib/src/handles/web/credentials_handle.dart new file mode 100644 index 000000000..e68f5484d --- /dev/null +++ b/packages/realm_dart/lib/src/handles/web/credentials_handle.dart @@ -0,0 +1,25 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import '../credentials_handle.dart' as intf; +import 'handle_base.dart'; + +class CredentialsHandle extends HandleBase implements intf.CredentialsHandle { + factory CredentialsHandle.anonymous(bool reuseCredentials) => webNotSupported(); + + factory CredentialsHandle.emailPassword(String email, String password) => webNotSupported(); + + factory CredentialsHandle.jwt(String token) => webNotSupported(); + + factory CredentialsHandle.apple(String idToken) => webNotSupported(); + + factory CredentialsHandle.facebook(String accessToken) => webNotSupported(); + + factory CredentialsHandle.googleIdToken(String idToken) => webNotSupported(); + + factory CredentialsHandle.googleAuthCode(String authCode) => webNotSupported(); + + factory CredentialsHandle.function(String payload) => webNotSupported(); + + factory CredentialsHandle.apiKey(String key) => webNotSupported(); +} diff --git a/packages/realm_dart/lib/src/handles/web/decimal128.dart b/packages/realm_dart/lib/src/handles/web/decimal128.dart new file mode 100644 index 000000000..58240b22b --- /dev/null +++ b/packages/realm_dart/lib/src/handles/web/decimal128.dart @@ -0,0 +1,39 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import '../decimal128.dart' as intf; +import 'web_not_supported.dart'; + +class Decimal128 implements intf.Decimal128 { + static final zero = Decimal128.fromInt(0); + + /// The value 1. + static final one = Decimal128.fromInt(1); + + /// The value 10. + static final ten = Decimal128.fromInt(10); + + /// The value NaN. + static Decimal128 get nan => webNotSupported(); + + /// The value +Inf. + static final infinity = one / zero; // +Inf + + /// The value -Inf. + static final negativeInfinity = -infinity; + + /// Parses a string into a [Decimal128]. Returns `null` if the string is not a valid [Decimal128]. + static Decimal128? tryParse(String source) => webNotSupported(); + + /// Parses a string into a [Decimal128]. Throws a [FormatException] if the string is not a valid [Decimal128]. + factory Decimal128.parse(String source) => webNotSupported(); + + /// Converts a `int` into a [Decimal128]. + factory Decimal128.fromInt(int value) => webNotSupported(); + + /// Converts a `double` into a [Decimal128]. + factory Decimal128.fromDouble(double value) => webNotSupported(); + + @override + noSuchMethod(Invocation invocation) => webNotSupported(); +} diff --git a/packages/realm_dart/lib/src/handles/web/default_client.dart b/packages/realm_dart/lib/src/handles/web/default_client.dart new file mode 100644 index 000000000..e4b040cc6 --- /dev/null +++ b/packages/realm_dart/lib/src/handles/web/default_client.dart @@ -0,0 +1,7 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import 'package:http/browser_client.dart'; +import 'package:http/http.dart'; + +Client defaultClient() => BrowserClient(); diff --git a/packages/realm_dart/lib/src/handles/web/handle_base.dart b/packages/realm_dart/lib/src/handles/web/handle_base.dart new file mode 100644 index 000000000..702f001e2 --- /dev/null +++ b/packages/realm_dart/lib/src/handles/web/handle_base.dart @@ -0,0 +1,12 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import '../handle_base.dart' as intf; +import 'web_not_supported.dart'; + +export 'web_not_supported.dart'; + +class HandleBase implements intf.HandleBase { + @override + noSuchMethod(Invocation invocation) => webNotSupported(); +} diff --git a/packages/realm_dart/lib/src/handles/web/list_handle.dart b/packages/realm_dart/lib/src/handles/web/list_handle.dart new file mode 100644 index 000000000..34148b316 --- /dev/null +++ b/packages/realm_dart/lib/src/handles/web/list_handle.dart @@ -0,0 +1,7 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import '../list_handle.dart' as intf; +import 'handle_base.dart'; + +class ListHandle extends HandleBase implements intf.ListHandle {} diff --git a/packages/realm_dart/lib/src/handles/web/map_changes_handle.dart b/packages/realm_dart/lib/src/handles/web/map_changes_handle.dart new file mode 100644 index 000000000..32a48f70c --- /dev/null +++ b/packages/realm_dart/lib/src/handles/web/map_changes_handle.dart @@ -0,0 +1,7 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import '../map_changes_handle.dart' as intf; +import 'handle_base.dart'; + +class MapChangesHandle extends HandleBase implements intf.MapChangesHandle {} diff --git a/packages/realm_dart/lib/src/handles/web/map_handle.dart b/packages/realm_dart/lib/src/handles/web/map_handle.dart new file mode 100644 index 000000000..5660c6166 --- /dev/null +++ b/packages/realm_dart/lib/src/handles/web/map_handle.dart @@ -0,0 +1,7 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import '../map_handle.dart' as intf; +import 'handle_base.dart'; + +class MapHandle extends HandleBase implements intf.MapHandle {} diff --git a/packages/realm_dart/lib/src/handles/web/mutable_subscription_set_handle.dart b/packages/realm_dart/lib/src/handles/web/mutable_subscription_set_handle.dart new file mode 100644 index 000000000..05e98f401 --- /dev/null +++ b/packages/realm_dart/lib/src/handles/web/mutable_subscription_set_handle.dart @@ -0,0 +1,7 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import '../mutable_subscription_set_handle.dart' as intf; +import 'handle_base.dart'; + +class MutableSubscriptionSetHandle extends HandleBase implements intf.MutableSubscriptionSetHandle {} diff --git a/packages/realm_dart/lib/src/handles/web/notification_token_handle.dart b/packages/realm_dart/lib/src/handles/web/notification_token_handle.dart new file mode 100644 index 000000000..26b1f5e08 --- /dev/null +++ b/packages/realm_dart/lib/src/handles/web/notification_token_handle.dart @@ -0,0 +1,7 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import '../notification_token_handle.dart' as intf; +import 'handle_base.dart'; + +class NotificationTokenHandle extends HandleBase implements intf.NotificationTokenHandle {} diff --git a/packages/realm_dart/lib/src/handles/web/object_changes_handle.dart b/packages/realm_dart/lib/src/handles/web/object_changes_handle.dart new file mode 100644 index 000000000..824b23120 --- /dev/null +++ b/packages/realm_dart/lib/src/handles/web/object_changes_handle.dart @@ -0,0 +1,7 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import '../object_changes_handle.dart' as intf; +import 'handle_base.dart'; + +class ObjectChangesHandle extends HandleBase implements intf.ObjectChangesHandle {} diff --git a/packages/realm_dart/lib/src/handles/web/object_handle.dart b/packages/realm_dart/lib/src/handles/web/object_handle.dart new file mode 100644 index 000000000..62c0a86fb --- /dev/null +++ b/packages/realm_dart/lib/src/handles/web/object_handle.dart @@ -0,0 +1,7 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import '../object_handle.dart' as intf; +import 'handle_base.dart'; + +class ObjectHandle extends HandleBase implements intf.ObjectHandle {} diff --git a/packages/realm_dart/lib/src/handles/web/realm_core.dart b/packages/realm_dart/lib/src/handles/web/realm_core.dart new file mode 100644 index 000000000..41e049cf8 --- /dev/null +++ b/packages/realm_dart/lib/src/handles/web/realm_core.dart @@ -0,0 +1,14 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import '../realm_core.dart' as intf; +import 'web_not_supported.dart'; + +const realmCore = RealmCore(); + +class RealmCore implements intf.RealmCore { + const RealmCore(); + + @override + noSuchMethod(Invocation invocation) => webNotSupported(); +} diff --git a/packages/realm_dart/lib/src/handles/web/realm_handle.dart b/packages/realm_dart/lib/src/handles/web/realm_handle.dart new file mode 100644 index 000000000..205fb7f23 --- /dev/null +++ b/packages/realm_dart/lib/src/handles/web/realm_handle.dart @@ -0,0 +1,11 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import 'package:realm_dart/realm.dart'; + +import '../realm_handle.dart' as intf; +import 'handle_base.dart'; + +class RealmHandle extends HandleBase implements intf.RealmHandle { + factory RealmHandle.open(Configuration config) => webNotSupported(); +} diff --git a/packages/realm_dart/lib/src/handles/web/results_handle.dart b/packages/realm_dart/lib/src/handles/web/results_handle.dart new file mode 100644 index 000000000..694dbfffe --- /dev/null +++ b/packages/realm_dart/lib/src/handles/web/results_handle.dart @@ -0,0 +1,7 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import '../results_handle.dart' as intf; +import 'handle_base.dart'; + +class ResultsHandle extends HandleBase implements intf.ResultsHandle {} diff --git a/packages/realm_dart/lib/src/handles/web/scheduler_handle.dart b/packages/realm_dart/lib/src/handles/web/scheduler_handle.dart new file mode 100644 index 000000000..8ad91238c --- /dev/null +++ b/packages/realm_dart/lib/src/handles/web/scheduler_handle.dart @@ -0,0 +1,11 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import 'dart:isolate'; + +import '../scheduler_handle.dart' as intf; +import 'handle_base.dart'; + +class SchedulerHandle extends HandleBase implements intf.SchedulerHandle { + factory SchedulerHandle(int isolateId, SendPort sendPort) => webNotSupported(); +} diff --git a/packages/realm_dart/lib/src/handles/web/schema_handle.dart b/packages/realm_dart/lib/src/handles/web/schema_handle.dart new file mode 100644 index 000000000..80ae51855 --- /dev/null +++ b/packages/realm_dart/lib/src/handles/web/schema_handle.dart @@ -0,0 +1,11 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import 'package:realm_dart/realm.dart'; + +import '../schema_handle.dart' as intf; +import 'handle_base.dart'; + +class SchemaHandle extends HandleBase implements intf.SchemaHandle { + factory SchemaHandle.from(Iterable schema) => webNotSupported(); +} diff --git a/packages/realm_dart/lib/src/handles/web/session_handle.dart b/packages/realm_dart/lib/src/handles/web/session_handle.dart new file mode 100644 index 000000000..f6b74bee0 --- /dev/null +++ b/packages/realm_dart/lib/src/handles/web/session_handle.dart @@ -0,0 +1,7 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import '../session_handle.dart' as intf; +import 'handle_base.dart'; + +class SessionHandle extends HandleBase implements intf.SessionHandle {} diff --git a/packages/realm_dart/lib/src/handles/web/set_handle.dart b/packages/realm_dart/lib/src/handles/web/set_handle.dart new file mode 100644 index 000000000..6cbf95968 --- /dev/null +++ b/packages/realm_dart/lib/src/handles/web/set_handle.dart @@ -0,0 +1,7 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import '../set_handle.dart' as intf; +import 'handle_base.dart'; + +class SetHandle extends HandleBase implements intf.SetHandle {} diff --git a/packages/realm_dart/lib/src/handles/web/subscription_handle.dart b/packages/realm_dart/lib/src/handles/web/subscription_handle.dart new file mode 100644 index 000000000..df3212a92 --- /dev/null +++ b/packages/realm_dart/lib/src/handles/web/subscription_handle.dart @@ -0,0 +1,7 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import '../subscription_handle.dart' as intf; +import 'handle_base.dart'; + +class SubscriptionHandle extends HandleBase implements intf.SubscriptionHandle {} diff --git a/packages/realm_dart/lib/src/handles/web/subscription_set_handle.dart b/packages/realm_dart/lib/src/handles/web/subscription_set_handle.dart new file mode 100644 index 000000000..20508d1b7 --- /dev/null +++ b/packages/realm_dart/lib/src/handles/web/subscription_set_handle.dart @@ -0,0 +1,7 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import '../subscription_set_handle.dart' as intf; +import 'handle_base.dart'; + +class SubscriptionSetHandle extends HandleBase implements intf.SubscriptionSetHandle {} diff --git a/packages/realm_dart/lib/src/handles/web/user_handle.dart b/packages/realm_dart/lib/src/handles/web/user_handle.dart new file mode 100644 index 000000000..a0e0638c0 --- /dev/null +++ b/packages/realm_dart/lib/src/handles/web/user_handle.dart @@ -0,0 +1,7 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import '../user_handle.dart' as intf; +import 'handle_base.dart'; + +class UserHandle extends HandleBase implements intf.UserHandle {} diff --git a/packages/realm_dart/lib/src/handles/web/web_not_supported.dart b/packages/realm_dart/lib/src/handles/web/web_not_supported.dart new file mode 100644 index 000000000..4232bd6f2 --- /dev/null +++ b/packages/realm_dart/lib/src/handles/web/web_not_supported.dart @@ -0,0 +1,4 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +Never webNotSupported() => throw UnsupportedError('web not supported'); diff --git a/packages/realm_dart/lib/src/list.dart b/packages/realm_dart/lib/src/list.dart index c2da5baf5..4f6ae75f2 100644 --- a/packages/realm_dart/lib/src/list.dart +++ b/packages/realm_dart/lib/src/list.dart @@ -1,18 +1,18 @@ // Copyright 2021 MongoDB, Inc. // SPDX-License-Identifier: Apache-2.0 -import 'dart:core'; import 'dart:async'; import 'dart:collection'; +import 'dart:core'; import 'package:collection/collection.dart' as collection; import 'collections.dart'; -import 'native/collection_changes_handle.dart'; -import 'native/handle_base.dart'; -import 'native/list_handle.dart'; -import 'native/notification_token_handle.dart'; -import 'native/object_handle.dart'; +import 'handles/collection_changes_handle.dart'; +import 'handles/handle_base.dart'; +import 'handles/list_handle.dart'; +import 'handles/notification_token_handle.dart'; +import 'handles/object_handle.dart'; import 'realm_class.dart'; import 'realm_object.dart'; import 'results.dart'; diff --git a/packages/realm_dart/lib/src/logging.dart b/packages/realm_dart/lib/src/logging.dart index 466dd8043..1555ca062 100644 --- a/packages/realm_dart/lib/src/logging.dart +++ b/packages/realm_dart/lib/src/logging.dart @@ -5,7 +5,7 @@ import 'dart:async'; import 'package:logging/logging.dart'; -import 'native/realm_core.dart'; +import 'handles/realm_core.dart'; // Using classes to make a fancy hierarchical enum sealed class LogCategory { diff --git a/packages/realm_dart/lib/src/map.dart b/packages/realm_dart/lib/src/map.dart index 093713cad..ba7e70df2 100644 --- a/packages/realm_dart/lib/src/map.dart +++ b/packages/realm_dart/lib/src/map.dart @@ -6,15 +6,14 @@ import 'dart:collection'; import 'package:collection/collection.dart' as collection; - import 'collections.dart'; -import 'native/handle_base.dart'; -import 'native/map_changes_handle.dart'; -import 'native/map_handle.dart'; -import 'native/notification_token_handle.dart'; -import 'native/object_handle.dart'; -import 'realm_object.dart'; +import 'handles/handle_base.dart'; +import 'handles/map_changes_handle.dart'; +import 'handles/map_handle.dart'; +import 'handles/notification_token_handle.dart'; +import 'handles/object_handle.dart'; import 'realm_class.dart'; +import 'realm_object.dart'; import 'results.dart'; /// RealmMap is a collection that contains key-value pairs of . diff --git a/packages/realm_dart/lib/src/migration.dart b/packages/realm_dart/lib/src/migration.dart index 86eaa0d50..7d2f4736a 100644 --- a/packages/realm_dart/lib/src/migration.dart +++ b/packages/realm_dart/lib/src/migration.dart @@ -1,7 +1,7 @@ // Copyright 2022 MongoDB, Inc. // SPDX-License-Identifier: Apache-2.0 -import 'native/schema_handle.dart'; +import 'handles/schema_handle.dart'; import 'realm_class.dart'; import 'realm_object.dart'; diff --git a/packages/realm_dart/lib/src/native/scheduler_handle.dart b/packages/realm_dart/lib/src/native/scheduler_handle.dart deleted file mode 100644 index 4ad210542..000000000 --- a/packages/realm_dart/lib/src/native/scheduler_handle.dart +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright 2024 MongoDB, Inc. -// SPDX-License-Identifier: Apache-2.0 - -import 'dart:ffi'; - -import 'handle_base.dart'; -import 'realm_bindings.dart'; -import 'realm_library.dart'; - -class SchedulerHandle extends HandleBase { - SchedulerHandle._(Pointer pointer) : super(pointer, 24); - - factory SchedulerHandle(int isolateId, int sendPort) { - final schedulerPtr = realmLib.realm_dart_create_scheduler(isolateId, sendPort); - return SchedulerHandle._(schedulerPtr); - } - - void invoke(int workQueue) { - final queuePointer = Pointer.fromAddress(workQueue); - realmLib.realm_scheduler_perform_work(queuePointer); - } -} diff --git a/packages/realm_dart/lib/src/realm_class.dart b/packages/realm_dart/lib/src/realm_class.dart index 5cc18c3fc..15a91bb95 100644 --- a/packages/realm_dart/lib/src/realm_class.dart +++ b/packages/realm_dart/lib/src/realm_class.dart @@ -2,25 +2,25 @@ // SPDX-License-Identifier: Apache-2.0 import 'dart:async'; -import 'dart:io'; +import 'dart:isolate'; import 'package:cancellation_token/cancellation_token.dart'; import 'package:collection/collection.dart'; import 'package:realm_common/realm_common.dart'; import 'configuration.dart'; +import 'handles/async_open_task_handle.dart'; +import 'handles/handle_base.dart'; +import 'handles/list_handle.dart'; +import 'handles/map_handle.dart'; +import 'handles/notification_token_handle.dart'; +import 'handles/object_handle.dart'; +import 'handles/realm_core.dart'; +import 'handles/realm_handle.dart'; +import 'handles/set_handle.dart'; import 'list.dart'; import 'logging.dart'; import 'map.dart'; -import 'native/async_open_task_handle.dart'; -import 'native/handle_base.dart'; -import 'native/list_handle.dart'; -import 'native/map_handle.dart'; -import 'native/notification_token_handle.dart'; -import 'native/object_handle.dart'; -import 'native/realm_core.dart'; -import 'native/realm_handle.dart'; -import 'native/set_handle.dart'; import 'realm_object.dart'; import 'results.dart'; import 'scheduler.dart'; @@ -88,11 +88,11 @@ export "configuration.dart" SyncError, SyncErrorHandler; export 'credentials.dart' show AuthProviderType, Credentials, EmailPasswordAuthProvider; +export 'handles/decimal128.dart' show Decimal128; export 'list.dart' show RealmList, RealmListOfObject, RealmListChanges, ListExtension; export 'logging.dart' hide RealmLoggerInternal; export 'map.dart' show RealmMap, RealmMapChanges, RealmMapOfObject; export 'migration.dart' show Migration; -export 'native/decimal128.dart' show Decimal128; export 'realm_object.dart' show AsymmetricObject, @@ -179,7 +179,6 @@ class Realm { return await CancellableFuture.value(realm, cancellationToken); } - _ensureDirectory(config); final asyncOpenHandle = AsyncOpenTaskHandle.from(config); return await CancellableFuture.from(() async { @@ -205,17 +204,9 @@ class Realm { } static RealmHandle _openRealm(Configuration config) { - _ensureDirectory(config); return RealmHandle.open(config); } - static void _ensureDirectory(Configuration config) { - var dir = File(config.path).parent; - if (!dir.existsSync()) { - dir.createSync(recursive: true); - } - } - void _populateMetadata() { schema = config.schemaObjects.isNotEmpty ? RealmSchema(config.schemaObjects) : handle.readSchema(); _metadata = RealmMetadata._(schema.map((c) => handle.getObjectMetadata(c))); @@ -230,22 +221,12 @@ class Realm { /// Synchronously checks whether a `Realm` exists at [path] static bool existsSync(String path) { - try { - final fileEntity = File(path); - return fileEntity.existsSync(); - } catch (e) { - throw RealmException("Error while checking if Realm exists at $path. Error: $e"); - } + return realmCore.checkIfRealmExists(path); } /// Checks whether a `Realm` exists at [path]. static Future exists(String path) async { - try { - final fileEntity = File(path); - return await fileEntity.exists(); - } catch (e) { - throw RealmException("Error while checking if Realm exists at $path. Error: $e"); - } + return await Isolate.run(() => realmCore.checkIfRealmExists(path)); } /// Adds a [RealmObject] to the `Realm`. @@ -365,7 +346,6 @@ class Realm { T write(T Function() writeCallback) { assert(!_isFuture(), 'writeCallback must be synchronous'); final transaction = beginWrite(); - try { T result = writeCallback(); transaction.commit(); @@ -565,7 +545,7 @@ class Realm { throw RealmException("Can't compact an in-memory Realm"); } - if (!File(config.path).existsSync()) { + if (!Realm.existsSync(config.path)) { print("realm file doesn't exist: ${config.path}"); return false; } diff --git a/packages/realm_dart/lib/src/realm_object.dart b/packages/realm_dart/lib/src/realm_object.dart index b2127da63..9e8fa98ce 100644 --- a/packages/realm_dart/lib/src/realm_object.dart +++ b/packages/realm_dart/lib/src/realm_object.dart @@ -7,14 +7,14 @@ import 'package:collection/collection.dart'; import 'package:realm_common/realm_common.dart'; import 'configuration.dart'; +import 'handles/handle_base.dart'; +import 'handles/notification_token_handle.dart'; +import 'handles/object_changes_handle.dart'; +import 'handles/object_handle.dart'; import 'list.dart'; -import 'native/handle_base.dart'; -import 'native/notification_token_handle.dart'; -import 'native/object_handle.dart'; -import 'native/realm_library.dart'; +import 'map.dart'; import 'realm_class.dart'; import 'results.dart'; -import 'map.dart'; typedef DartDynamic = dynamic; @@ -175,6 +175,7 @@ class RealmCoreAccessor implements RealmAccessor { if (listMetadata != null && _isTypeGenericObject()) { switch (listMetadata.schema.baseType) { case ObjectType.realmObject: + //ManagedRealmList._(handle, object.realm, listMetadata); return object.realm.createList(handle, listMetadata); case ObjectType.embeddedObject: return object.realm.createList(handle, listMetadata); @@ -541,7 +542,7 @@ mixin RealmObjectBase on RealmEntity implements RealmObjectBaseMarker { if (invocation.isGetter) { final name = _symbolRegex.firstMatch(invocation.memberName.toString())?.namedGroup("symbolName"); if (name == null) { - throw RealmError("Could not find symbol name for ${invocation.memberName}. $bugInTheSdkMessage"); + throw RealmError("Could not find symbol name for ${invocation.memberName}"); } return get(this, name); @@ -550,7 +551,7 @@ mixin RealmObjectBase on RealmEntity implements RealmObjectBaseMarker { if (invocation.isSetter) { final name = _symbolRegex.firstMatch(invocation.memberName.toString())?.namedGroup("symbolName"); if (name == null) { - throw RealmError("Could not find symbol name for ${invocation.memberName}. $bugInTheSdkMessage"); + throw RealmError("Could not find symbol name for ${invocation.memberName}"); } return set(this, name, invocation.positionalArguments.single); @@ -647,7 +648,7 @@ extension EmbeddedObjectExtension on EmbeddedObject { extension RealmObjectInternal on RealmObjectBase { void manage(Realm realm, ObjectHandle handle, RealmCoreAccessor accessor, bool update) { if (_handle != null) { - //most certainly a bug hence we throw an Error + // most certainly a bug hence we throw an Error throw ArgumentError("Object is already managed"); } @@ -758,7 +759,7 @@ class RealmObjectNotificationsController extends Noti throw RealmException("It is not allowed to have empty key paths."); } // throw early if the key paths are invalid - realmObject.handle.buildAndVerifyKeyPath(keyPaths); + realmObject.handle.verifyKeyPath(keyPaths); } } diff --git a/packages/realm_dart/lib/src/realm_web.dart b/packages/realm_dart/lib/src/realm_web.dart new file mode 100644 index 000000000..2cf514f97 --- /dev/null +++ b/packages/realm_dart/lib/src/realm_web.dart @@ -0,0 +1,9 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +/// @nodoc +// is Realm loaded in Flutter or Dart +const bool isFlutterPlatform = false; + +/// @nodoc +const String realmBinaryName = 'realm_dart'; diff --git a/packages/realm_dart/lib/src/results.dart b/packages/realm_dart/lib/src/results.dart index f632bdd4d..ea054067e 100644 --- a/packages/realm_dart/lib/src/results.dart +++ b/packages/realm_dart/lib/src/results.dart @@ -6,11 +6,11 @@ import 'dart:async'; import 'package:cancellation_token/cancellation_token.dart'; import 'collections.dart'; -import 'native/collection_changes_handle.dart'; -import 'native/handle_base.dart'; -import 'native/notification_token_handle.dart'; -import 'native/object_handle.dart'; -import 'native/results_handle.dart'; +import 'handles/collection_changes_handle.dart'; +import 'handles/handle_base.dart'; +import 'handles/notification_token_handle.dart'; +import 'handles/object_handle.dart'; +import 'handles/results_handle.dart'; import 'realm_class.dart'; import 'realm_object.dart'; diff --git a/packages/realm_dart/lib/src/scheduler.dart b/packages/realm_dart/lib/src/scheduler.dart index 8c60f1ebe..f42240b8f 100644 --- a/packages/realm_dart/lib/src/scheduler.dart +++ b/packages/realm_dart/lib/src/scheduler.dart @@ -2,12 +2,11 @@ // SPDX-License-Identifier: Apache-2.0 import 'dart:async'; -import 'dart:ffi'; import 'dart:isolate'; import 'package:realm_dart/src/logging.dart'; -import 'native/scheduler_handle.dart'; +import 'handles/scheduler_handle.dart'; import 'realm_class.dart'; final _receivePortFinalizer = Finalizer((p) => p.close()); @@ -17,8 +16,6 @@ class Scheduler { late final SchedulerHandle handle; final RawReceivePort _receivePort = RawReceivePort(); - int get nativePort => _receivePort.sendPort.nativePort; - Scheduler._() { _receivePortFinalizer.attach(this, _receivePort, detach: this); // There be dragons here!!! @@ -34,7 +31,7 @@ class Scheduler { // these. _receivePort.handler = Zone.current.bindUnaryCallbackGuarded(_handle); final sendPort = _receivePort.sendPort; - handle = SchedulerHandle(Isolate.current.hashCode, sendPort.nativePort); + handle = SchedulerHandle(Isolate.current.hashCode, sendPort); } void _handle(dynamic message) { diff --git a/packages/realm_dart/lib/src/session.dart b/packages/realm_dart/lib/src/session.dart index a99aec871..6ba07de14 100644 --- a/packages/realm_dart/lib/src/session.dart +++ b/packages/realm_dart/lib/src/session.dart @@ -4,8 +4,7 @@ import 'dart:async'; import '../realm.dart'; -import '../src/native/realm_bindings.dart'; -import 'native/session_handle.dart'; +import 'handles/session_handle.dart'; import 'user.dart'; /// An object encapsulating a synchronization session. Sessions represent the @@ -232,65 +231,56 @@ enum ProgressMode { /// Error code enumeration, indicating the type of [SyncError]. enum SyncErrorCode { /// Unrecognized error code. It usually indicates incompatibility between the App Services server and client SDK versions. - runtimeError(realm_errno.RLM_ERR_RUNTIME), + runtimeError, /// The partition value specified by the user is not valid - i.e. its the wrong type or is encoded incorrectly. - badPartitionValue(realm_errno.RLM_ERR_BAD_SYNC_PARTITION_VALUE), + badPartitionValue, /// A fundamental invariant in the communication between the client and the server was not upheld. This typically indicates /// a bug in the synchronization layer and should be reported at https://github.com/realm/realm-core/issues. - protocolInvariantFailed(realm_errno.RLM_ERR_SYNC_PROTOCOL_INVARIANT_FAILED), + protocolInvariantFailed, /// The changeset is invalid. - badChangeset(realm_errno.RLM_ERR_BAD_CHANGESET), + badChangeset, /// The client attempted to create a subscription for a query is invalid/malformed. - invalidSubscriptionQuery(realm_errno.RLM_ERR_INVALID_SUBSCRIPTION_QUERY), + invalidSubscriptionQuery, /// A client reset has occurred. This error code will only be reported via a [ClientResetError] and only /// in the case manual client reset handling is required - either via [ManualRecoveryHandler] or when /// `onManualReset` is invoked on one of the automatic client reset handlers. - clientReset(realm_errno.RLM_ERR_SYNC_CLIENT_RESET_REQUIRED), + clientReset, /// The client attempted to upload an invalid schema change - either an additive schema change /// when developer mode is off or a destructive schema change. - invalidSchemaChange(realm_errno.RLM_ERR_SYNC_INVALID_SCHEMA_CHANGE), + invalidSchemaChange, /// Permission to Realm has been denied. - permissionDenied(realm_errno.RLM_ERR_SYNC_PERMISSION_DENIED), + permissionDenied, /// The server permissions for this file have changed since the last time it was used. - serverPermissionsChanged(realm_errno.RLM_ERR_SYNC_SERVER_PERMISSIONS_CHANGED), + serverPermissionsChanged, /// The user for this session doesn't match the user who originally created the file. This can happen /// if you explicitly specify the Realm file path in the configuration and you open the Realm first with /// user A, then with user B without changing the on-disk path. - userMismatch(realm_errno.RLM_ERR_SYNC_USER_MISMATCH), + userMismatch, /// Client attempted a write that is disallowed by permissions, or modifies an object /// outside the current query - this will result in a [CompensatingWriteError]. - writeNotAllowed(realm_errno.RLM_ERR_SYNC_WRITE_NOT_ALLOWED), + writeNotAllowed, /// Automatic client reset has failed. This will only be reported via [ClientResetError] /// when an automatic client reset handler was used but it failed to perform the client reset operation - /// typically due to a breaking schema change in the server schema or due to an exception occurring in the /// before or after client reset callbacks. - autoClientResetFailed(realm_errno.RLM_ERR_AUTO_CLIENT_RESET_FAILED), + autoClientResetFailed, /// The wrong sync type was used to connect to the server. This means that you're trying to connect /// to an app configured to use partition sync. - wrongSyncType(realm_errno.RLM_ERR_WRONG_SYNC_TYPE), + wrongSyncType, /// Client attempted a write that is disallowed by permissions, or modifies an /// object outside the current query, and the server undid the modification. - compensatingWrite(realm_errno.RLM_ERR_SYNC_COMPENSATING_WRITE); - - static final Map _valuesMap = {for (var value in SyncErrorCode.values) value.code: value}; - - static SyncErrorCode fromInt(int code) { - return SyncErrorCode._valuesMap[code] ?? SyncErrorCode.runtimeError; - } - - final int code; - const SyncErrorCode(this.code); + compensatingWrite; } diff --git a/packages/realm_dart/lib/src/set.dart b/packages/realm_dart/lib/src/set.dart index ecb5043a4..613350ee3 100644 --- a/packages/realm_dart/lib/src/set.dart +++ b/packages/realm_dart/lib/src/set.dart @@ -1,20 +1,20 @@ // Copyright 2023 MongoDB, Inc. // SPDX-License-Identifier: Apache-2.0 -import 'dart:core'; import 'dart:async'; import 'dart:collection'; +import 'dart:core'; import 'package:collection/collection.dart' as collection; -import 'native/collection_changes_handle.dart'; -import 'native/handle_base.dart'; -import 'native/notification_token_handle.dart'; -import 'native/object_handle.dart'; -import 'native/set_handle.dart'; +import 'collections.dart'; +import 'handles/collection_changes_handle.dart'; +import 'handles/handle_base.dart'; +import 'handles/notification_token_handle.dart'; +import 'handles/object_handle.dart'; +import 'handles/set_handle.dart'; import 'realm_class.dart'; import 'realm_object.dart'; -import 'collections.dart'; import 'results.dart'; /// RealmSet is a collection that contains no duplicate elements. @@ -141,7 +141,7 @@ class ManagedRealmSet with RealmEntity, SetMixin implement _throwOnRealmValueCollection(value); if (_isManagedRealmObject(value)) { - //It is valid to call `add` with managed objects already in the set. + // It is valid to call `add` with managed objects already in the set. _ensureManagedByThis(value, "add"); } else { // might be updating an existing realm object diff --git a/packages/realm_dart/lib/src/subscription.dart b/packages/realm_dart/lib/src/subscription.dart index e40f86cf8..4afb2c5fa 100644 --- a/packages/realm_dart/lib/src/subscription.dart +++ b/packages/realm_dart/lib/src/subscription.dart @@ -3,10 +3,10 @@ import 'dart:core'; -import 'native/convert.dart'; -import 'native/mutable_subscription_set_handle.dart'; -import 'native/subscription_handle.dart'; -import 'native/subscription_set_handle.dart'; +import 'convert.dart'; +import 'handles/mutable_subscription_set_handle.dart'; +import 'handles/subscription_handle.dart'; +import 'handles/subscription_set_handle.dart'; import 'realm_class.dart'; import 'results.dart'; @@ -111,11 +111,9 @@ enum SubscriptionSetState { /// {@category Sync} sealed class SubscriptionSet with Iterable { final Realm _realm; - SubscriptionSetHandle __handle; - SubscriptionSetHandle get _handle => __handle.nullPtrAsNull ?? (throw RealmClosedError('Cannot access a SubscriptionSet that belongs to a closed Realm')); - set _handle(SubscriptionSetHandle value) => __handle = value; + SubscriptionSetHandle _handle; - SubscriptionSet._(this._realm, this.__handle); + SubscriptionSet._(this._realm, this._handle); /// Finds an existing [Subscription] in this set by its query /// diff --git a/packages/realm_dart/lib/src/user.dart b/packages/realm_dart/lib/src/user.dart index 9ff597fe8..d10347da3 100644 --- a/packages/realm_dart/lib/src/user.dart +++ b/packages/realm_dart/lib/src/user.dart @@ -6,7 +6,7 @@ import 'dart:convert'; import 'app.dart'; import 'credentials.dart'; -import 'native/user_handle.dart'; +import 'handles/user_handle.dart'; import 'realm_class.dart'; /// Describes the changes to a [User] instance - for example when the access token is updated or the user state changes. diff --git a/packages/realm_dart/pubspec.yaml b/packages/realm_dart/pubspec.yaml index dd5301aae..b4a63abd2 100644 --- a/packages/realm_dart/pubspec.yaml +++ b/packages/realm_dart/pubspec.yaml @@ -37,3 +37,4 @@ dev_dependencies: lints: ^3.0.0 test: ^1.14.3 timezone: ^0.9.0 + universal_platform: ^1.1.0 diff --git a/packages/realm_dart/test/app_test.dart b/packages/realm_dart/test/app_test.dart index 9dd41f46e..13f758250 100644 --- a/packages/realm_dart/test/app_test.dart +++ b/packages/realm_dart/test/app_test.dart @@ -2,15 +2,16 @@ // SPDX-License-Identifier: Apache-2.0 import 'dart:convert'; -import 'dart:io'; import 'dart:isolate'; -import 'package:test/expect.dart' hide throws; -import 'package:path/path.dart' as path; -import 'package:crypto/crypto.dart'; +import 'package:crypto/crypto.dart'; +import 'package:http/http.dart'; +import 'package:path/path.dart' as path; import 'package:realm_dart/realm.dart'; -import 'package:realm_dart/src/native/realm_core.dart'; +import 'package:realm_dart/src/handles/realm_core.dart'; + import 'test.dart'; +import 'utils/platform_util.dart'; void main() { setupTests(); @@ -19,15 +20,15 @@ void main() { Configuration.defaultRealmPath = path.join(Configuration.defaultStoragePath, Configuration.defaultRealmName); final defaultAppConfig = AppConfiguration('myapp'); expect(defaultAppConfig.appId, 'myapp'); - expect(defaultAppConfig.baseFilePath.path, Configuration.defaultStoragePath); + expect(defaultAppConfig.baseFilePath, Configuration.defaultStoragePath); expect(defaultAppConfig.baseUrl, Uri.parse('https://services.cloud.mongodb.com')); expect(defaultAppConfig.defaultRequestTimeout, const Duration(minutes: 1)); expect(defaultAppConfig.metadataPersistenceMode, MetadataPersistenceMode.plaintext); - final httpClient = HttpClient(context: SecurityContext(withTrustedRoots: false)); + final httpClient = Client(); final appConfig = AppConfiguration( 'myapp1', - baseFilePath: Directory.systemTemp, + baseFilePath: platformUtil.systemTempPath, baseUrl: Uri.parse('https://not_re.al'), defaultRequestTimeout: const Duration(seconds: 2), metadataPersistenceMode: MetadataPersistenceMode.disabled, @@ -35,7 +36,7 @@ void main() { httpClient: httpClient, ); expect(appConfig.appId, 'myapp1'); - expect(appConfig.baseFilePath.path, Directory.systemTemp.path); + expect(appConfig.baseFilePath, platformUtil.systemTempPath); expect(appConfig.baseUrl, Uri.parse('https://not_re.al')); expect(appConfig.defaultRequestTimeout, const Duration(seconds: 2)); expect(appConfig.metadataPersistenceMode, MetadataPersistenceMode.disabled); @@ -57,10 +58,10 @@ void main() { }); test('AppConfiguration can be created', () { - final httpClient = HttpClient(context: SecurityContext(withTrustedRoots: false)); + final httpClient = Client(); final appConfig = AppConfiguration( 'myapp1', - baseFilePath: Directory.systemTemp, + baseFilePath: platformUtil.systemTempPath, baseUrl: Uri.parse('https://not_re.al'), defaultRequestTimeout: const Duration(seconds: 2), metadataPersistenceMode: MetadataPersistenceMode.encrypted, @@ -70,7 +71,7 @@ void main() { ); expect(appConfig.appId, 'myapp1'); - expect(appConfig.baseFilePath.path, Directory.systemTemp.path); + expect(appConfig.baseFilePath, platformUtil.systemTempPath); expect(appConfig.baseUrl, Uri.parse('https://not_re.al')); expect(appConfig.defaultRequestTimeout, const Duration(seconds: 2)); expect(appConfig.metadataPersistenceMode, MetadataPersistenceMode.encrypted); diff --git a/packages/realm_dart/test/baas_helper.dart b/packages/realm_dart/test/baas_helper.dart index 52a5a368b..a8810ef99 100644 --- a/packages/realm_dart/test/baas_helper.dart +++ b/packages/realm_dart/test/baas_helper.dart @@ -1,13 +1,12 @@ // Copyright 2024 MongoDB, Inc. // SPDX-License-Identifier: Apache-2.0 -import 'dart:io'; - -import 'package:test/test.dart' as testing; - import 'package:realm_dart/realm.dart'; import 'package:realm_dart/src/cli/atlas_apps/baas_client.dart'; -import 'package:realm_dart/src/native/realm_core.dart'; +import 'package:realm_dart/src/handles/realm_core.dart'; +import 'package:test/test.dart' as testing; + +import 'utils/platform_util.dart'; export 'package:realm_dart/src/cli/atlas_apps/baas_client.dart' show AppName; @@ -37,7 +36,7 @@ enum Env { const Env(this.name, this._dartDefined); String? get dartDefined => _dartDefined.emptyAsNull; - String? get shellDefined => Platform.environment[name]; + String? get shellDefined => platformUtil.environment[name]; String? get value => dartDefined ?? shellDefined; bool get isDefined => value != null; @@ -187,11 +186,11 @@ class BaasHelper { throw app.error!; } - final temporaryDir = await Directory.systemTemp.createTemp('realm_test_'); + final temporaryPath = await platformUtil.createTempPath(); return AppConfiguration( app.clientAppId, baseUrl: Uri.parse(customBaseUrl ?? baseUrl), - baseFilePath: temporaryDir, + baseFilePath: temporaryPath, maxConnectionTimeout: Duration(minutes: 10), defaultRequestTimeout: Duration(minutes: 7), ); @@ -234,7 +233,7 @@ class BaasHelper { } print('Failed to trigger client reset: $e'); - await Future.delayed(Duration(seconds: i)); + await Future.delayed(Duration(seconds: i)); } } diff --git a/packages/realm_dart/test/client_reset_test.dart b/packages/realm_dart/test/client_reset_test.dart index 36cc5ba1e..5e76c177a 100644 --- a/packages/realm_dart/test/client_reset_test.dart +++ b/packages/realm_dart/test/client_reset_test.dart @@ -2,9 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import 'dart:async'; -import 'dart:io'; -import 'package:test/test.dart' hide test, throws; import 'package:realm_dart/realm.dart'; import 'package:realm_dart/src/configuration.dart' show ClientResetHandlerInternal, ClientResyncModeInternal; import 'test.dart'; @@ -95,7 +93,7 @@ void main() { await resetRealmFuture.wait(defaultWaitTimeout, "ManualRecoveryHandler is not reported."); - expect(File(config.path).existsSync(), isFalse); + expect(Realm.existsSync(config.path), isFalse); }); baasTest('Initiate resetRealm after ManualRecoveryHandler callback fails when Realm is opened', (appConfig) async { @@ -121,7 +119,7 @@ void main() { await baasHelper!.triggerClientReset(realm); expect(await resetRealmFuture.timeout(defaultWaitTimeout), !Platform.isWindows); - expect(File(config.path).existsSync(), Platform.isWindows); // posix and windows semantics are different + expect(Realm.existsSync(config.path), Platform.isWindows); // posix and windows semantics are different }); for (Type clientResetHandlerType in [ diff --git a/packages/realm_dart/test/configuration_test.dart b/packages/realm_dart/test/configuration_test.dart index 5dbdf98eb..942dba9f1 100644 --- a/packages/realm_dart/test/configuration_test.dart +++ b/packages/realm_dart/test/configuration_test.dart @@ -1,13 +1,13 @@ // Copyright 2021 MongoDB, Inc. // SPDX-License-Identifier: Apache-2.0 -import 'dart:io'; import 'dart:math'; import 'package:path/path.dart' as path; import 'package:realm_dart/realm.dart'; import 'test.dart'; +import 'utils/platform_util.dart'; void main() { setupTests(); @@ -44,7 +44,7 @@ void main() { }); test('Configuration defaultRealmPath can be set for LocalConfiguration', () async { - final customDefaultRealmPath = path.join((await Directory.systemTemp.createTemp()).path, Configuration.defaultRealmName); + final customDefaultRealmPath = path.join(await platformUtil.createTempPath(), Configuration.defaultRealmName); Configuration.defaultRealmPath = customDefaultRealmPath; final config = Configuration.local([Car.schema]); expect(path.dirname(config.path), path.dirname(customDefaultRealmPath)); @@ -53,7 +53,7 @@ void main() { expect(path.dirname(realm.config.path), path.dirname(customDefaultRealmPath)); //set a new defaultRealmPath - final customDefaultRealmPath1 = path.join((await Directory.systemTemp.createTemp()).path, Configuration.defaultRealmName); + final customDefaultRealmPath1 = path.join(await platformUtil.createTempPath(), Configuration.defaultRealmName); Configuration.defaultRealmPath = customDefaultRealmPath1; final config1 = Configuration.local([Car.schema]); final realm1 = getRealm(config1); @@ -81,13 +81,13 @@ void main() { }); baasTest('Configuration defaultRealmPath can be set for FlexibleSyncConfiguration', (_) async { - var customDefaultRealmPath = path.join((await Directory.systemTemp.createTemp()).path, Configuration.defaultRealmName); + var customDefaultRealmPath = path.join(await platformUtil.createTempPath(), Configuration.defaultRealmName); Configuration.defaultRealmPath = customDefaultRealmPath; final appClientId = baasHelper!.getClientAppId(appName: AppName.flexible); final baasUrl = baasHelper!.baseUrl; var appConfig = AppConfiguration(appClientId, baseUrl: Uri.parse(baasUrl)); - expect(appConfig.baseFilePath.path, path.dirname(customDefaultRealmPath)); + expect(appConfig.baseFilePath, path.dirname(customDefaultRealmPath)); var app = App(appConfig); var user = await app.logIn(Credentials.anonymous()); @@ -99,11 +99,11 @@ void main() { expect(path.dirname(realm.config.path), startsWith(path.dirname(customDefaultRealmPath))); //set a new defaultRealmPath - customDefaultRealmPath = path.join((await Directory.systemTemp.createTemp()).path, Configuration.defaultRealmName); + customDefaultRealmPath = path.join(await platformUtil.createTempPath(), Configuration.defaultRealmName); Configuration.defaultRealmPath = customDefaultRealmPath; appConfig = AppConfiguration(appClientId, baseUrl: Uri.parse(baasUrl)); - expect(appConfig.baseFilePath.path, path.dirname(customDefaultRealmPath)); + expect(appConfig.baseFilePath, path.dirname(customDefaultRealmPath)); clearCachedApps(); @@ -442,15 +442,15 @@ void main() { } } - for (var shouldCompact in [true, false]) { - test('Configuration.shouldCompact when return $shouldCompact triggers compaction', () { + for (var shouldCompact in [true, false]) { + test('Configuration.shouldCompact when return $shouldCompact triggers compaction', () async { var config = Configuration.local([Person.schema]); final populateRealm = Realm(config); addDummyData(populateRealm); populateRealm.close(); - final oldSize = File(config.path).lengthSync(); + final oldSize = await platformUtil.sizeOnStorage(config); var projectedNewSize = 0; config = Configuration.local([Person.schema], shouldCompactCallback: (totalSize, usedSize) { @@ -459,7 +459,7 @@ void main() { }); final compactedRealm = getRealm(config); - final newSize = File(config.path).lengthSync(); + final newSize = await platformUtil.sizeOnStorage(config); if (shouldCompact) { expect(newSize, lessThan(oldSize)); @@ -522,8 +522,8 @@ void main() { final app = App(appConfig); final user = await app.logIn(Credentials.emailPassword(testUsername, testPassword)); - final dir = await Directory.systemTemp.createTemp(); - final realmPath = path.join(dir.path, 'test.realm'); + final temporaryPath = await platformUtil.createTempPath(); + final realmPath = path.join(temporaryPath, 'test.realm'); final flexibleSyncConfig = Configuration.flexibleSync(user, getSyncSchema(), path: realmPath); final realm = getRealm(flexibleSyncConfig); diff --git a/packages/realm_dart/test/decimal128_test.dart b/packages/realm_dart/test/decimal128_test.dart index 85aec4ca7..59c9e1bf5 100644 --- a/packages/realm_dart/test/decimal128_test.dart +++ b/packages/realm_dart/test/decimal128_test.dart @@ -4,10 +4,8 @@ import 'dart:math'; import 'package:meta/meta.dart'; -import 'package:realm_dart/src/native/decimal128.dart'; -import 'package:test/expect.dart' hide throws; +import 'package:realm_dart/src/handles/decimal128.dart'; -import 'package:realm_dart/src/native/realm_core.dart'; import 'test.dart'; const int defaultTimes = 100; diff --git a/packages/realm_dart/test/embedded_test.dart b/packages/realm_dart/test/embedded_test.dart index 9936d7d67..baf5c570f 100644 --- a/packages/realm_dart/test/embedded_test.dart +++ b/packages/realm_dart/test/embedded_test.dart @@ -1,15 +1,13 @@ // Copyright 2022 MongoDB, Inc. // SPDX-License-Identifier: Apache-2.0 -import 'package:realm_dart/src/native/decimal128.dart'; -import 'package:test/test.dart' hide test, throws; import 'package:realm_dart/realm.dart'; -import 'package:realm_dart/src/realm_object.dart'; -import 'test.dart'; - // This is required to be able to use the API for querying embedded objects. import 'package:realm_dart/src/realm_class.dart' show RealmInternal; +import 'package:realm_dart/src/realm_object.dart'; + +import 'test.dart'; void main() { setupTests(); diff --git a/packages/realm_dart/test/realm_logger_test.dart b/packages/realm_dart/test/realm_logger_test.dart index b50cda9b1..25ef317a9 100644 --- a/packages/realm_dart/test/realm_logger_test.dart +++ b/packages/realm_dart/test/realm_logger_test.dart @@ -6,8 +6,7 @@ import 'dart:isolate'; import 'package:logging/logging.dart' hide LogRecord; import 'package:logging/logging.dart' as logging show LogRecord; import 'package:realm_dart/src/logging.dart'; -import 'package:realm_dart/src/native/realm_core.dart'; -import 'package:test/test.dart' hide test, throws; +import 'package:realm_dart/src/handles/realm_core.dart'; import 'package:realm_dart/realm.dart'; import 'test.dart'; diff --git a/packages/realm_dart/test/realm_map_test.dart b/packages/realm_dart/test/realm_map_test.dart index 032cc18de..9056e370a 100644 --- a/packages/realm_dart/test/realm_map_test.dart +++ b/packages/realm_dart/test/realm_map_test.dart @@ -2,12 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 import 'dart:async'; -import 'dart:io'; import 'dart:typed_data'; import 'package:collection/collection.dart'; import 'package:meta/meta.dart'; -import 'package:test/test.dart' hide test, throws; import 'package:realm_dart/realm.dart'; import 'test.dart'; @@ -263,8 +261,8 @@ List> intTestCases() => [ TestCaseData(123456789, initialValues: [('123', -123)]), TestCaseData(123456789, initialValues: [('a', 1), ('b', 1), ('c', 1)]), TestCaseData(123456789, initialValues: [('a', 1), ('b', 2), ('c', 3)]), - TestCaseData(123456789, initialValues: [('a', -0x8000000000000000), ('z', 0x7FFFFFFFFFFFFFFF)]), - TestCaseData(123456789, initialValues: [('a', -0x8000000000000000), ('zero', 0), ('one', 1), ('z', 0x7FFFFFFFFFFFFFFF)]), + TestCaseData(123456789, initialValues: [('a', minInt), ('z', maxInt)]), + TestCaseData(123456789, initialValues: [('a', minInt), ('zero', 0), ('one', 1), ('z', maxInt)]), ]; List> nullableIntTestCases() => [ @@ -274,8 +272,8 @@ List> nullableIntTestCases() => [ TestCaseData(1234, initialValues: [('null', null)]), TestCaseData(1234, initialValues: [('null1', null), ('null2', null), ('null3', null)]), TestCaseData(null, initialValues: [('a', 1), ('b', null), ('c', 3)]), - TestCaseData(1234, initialValues: [('a', -0x8000000000000000), ('m', null), ('z', 0x7FFFFFFFFFFFFFFF)]), - TestCaseData(1234, initialValues: [('a', -0x8000000000000000), ('zero', 0), ('null', null), ('one', 1), ('z', 0x7FFFFFFFFFFFFFFF)]), + TestCaseData(1234, initialValues: [('a', minInt), ('m', null), ('z', maxInt)]), + TestCaseData(1234, initialValues: [('a', minInt), ('zero', 0), ('null', null), ('one', 1), ('z', maxInt)]), ]; List> stringTestValues() => [ diff --git a/packages/realm_dart/test/realm_test.dart b/packages/realm_dart/test/realm_test.dart index cb7c094de..08f577c15 100644 --- a/packages/realm_dart/test/realm_test.dart +++ b/packages/realm_dart/test/realm_test.dart @@ -3,16 +3,16 @@ import 'dart:async'; import 'dart:convert'; -import 'dart:io'; import 'dart:isolate'; import 'package:path/path.dart' as p; import 'package:realm_dart/realm.dart'; -import 'package:realm_dart/src/native/realm_core.dart'; +import 'package:realm_dart/src/handles/realm_core.dart'; import 'package:timezone/data/latest.dart' as tz; import 'package:timezone/timezone.dart' as tz; import 'test.dart'; +import 'utils/platform_util.dart'; void main() { setupTests(); @@ -108,8 +108,7 @@ void main() { realm.close(); Realm.deleteRealm(config.path); - expect(File(config.path).existsSync(), false); - expect(Directory("${config.path}.management").existsSync(), false); + expect(Realm.existsSync(config.path), false); }); test('Realm deleteRealm throws exception on an open realm', () { @@ -118,8 +117,7 @@ void main() { expect(() => Realm.deleteRealm(config.path), throws()); - expect(File(config.path).existsSync(), true); - expect(Directory("${config.path}.management").existsSync(), true); + expect(Realm.existsSync(config.path), true); }); test('Realm add object', () { @@ -931,21 +929,21 @@ void main() { }); }); - test('Realm - encryption works', () { + test('Realm - encryption works', () async { var config = Configuration.local([Friend.schema], path: p.join(Configuration.defaultStoragePath, "${generateRandomString(8)}.realm")); var realm = getRealm(config); - readFile(String path) { - final bytes = File(path).readAsBytesSync(); + readFile(String path) async { + final bytes = await platformUtil.readAsBytes(path); return utf8.decode(bytes, allowMalformed: true); } - var decoded = readFile(realm.config.path); + var decoded = await readFile(realm.config.path); expect(decoded, contains("bestFriend")); config = Configuration.local([Friend.schema], encryptionKey: generateEncryptionKey(), path: p.join(Configuration.defaultStoragePath, "${generateRandomString(8)}.realm")); realm = getRealm(config); - decoded = readFile(realm.config.path); + decoded = await readFile(realm.config.path); expect(decoded, isNot(contains("bestFriend"))); }); @@ -1429,15 +1427,15 @@ void main() { await realm.syncSession.waitForUpload(); } - final beforeSize = await File(config.path).stat().then((value) => value.size); + final beforeSize = platformUtil.sizeOnStorage(config); realm.close(); return beforeSize; } - void validateCompact(bool compacted, String realmPath, int beforeCompactSizeSize) async { + void validateCompact(bool compacted, Configuration config, int beforeCompactSizeSize) async { expect(compacted, true); - final afterCompactSize = await File(realmPath).stat().then((value) => value.size); + final afterCompactSize = await platformUtil.sizeOnStorage(config); expect(beforeCompactSizeSize, greaterThan(afterCompactSize)); } @@ -1447,7 +1445,7 @@ void main() { final compacted = Realm.compact(config); - validateCompact(compacted, config.path, beforeCompactSizeSize); + validateCompact(compacted, config, beforeCompactSizeSize); //test the realm can be opened. final realm = getRealm(config); @@ -1474,7 +1472,7 @@ void main() { final compacted = await receivePort.first as bool; - validateCompact(compacted, config.path, beforeCompactSizeSize); + validateCompact(compacted, config, beforeCompactSizeSize); //test the realm can be opened. final realm = getRealm(config); @@ -1488,7 +1486,7 @@ void main() { final compacted = Realm.compact(config); - validateCompact(compacted, config.path, beforeCompactSizeSize); + validateCompact(compacted, config, beforeCompactSizeSize); //test the realm can be opened. final realm = getRealm(config); @@ -1518,7 +1516,7 @@ void main() { final compacted = Realm.compact(config); - validateCompact(compacted, config.path, beforeCompactSize); + validateCompact(compacted, config, beforeCompactSize); // test the realm can be opened. expect(() => getRealm(config), returnsNormally); @@ -1531,7 +1529,7 @@ void main() { final beforeCompactSize = await createRealmForCompact(config); final compacted = await runWithRetries(() => Realm.compact(config)); - validateCompact(compacted, config.path, beforeCompactSize); + validateCompact(compacted, config, beforeCompactSize); // test the realm can be opened. expect(() => getRealm(config), returnsNormally); @@ -1547,7 +1545,7 @@ void main() { final beforeCompactSize = await createRealmForCompact(config); final compacted = await runWithRetries(() => Realm.compact(config)); - validateCompact(compacted, config.path, beforeCompactSize); + validateCompact(compacted, config, beforeCompactSize); // test the realm can be opened. expect(() => getRealm(config), returnsNormally); @@ -1619,7 +1617,7 @@ void main() { originalRealm.writeCopy(configCopy); originalRealm.close(); - expect(File(pathCopy).existsSync(), isTrue); + expect(Realm.existsSync(pathCopy), isTrue); final copiedRealm = getRealm(configCopy); expect(copiedRealm.all().length, itemsCount); copiedRealm.close(); @@ -1674,7 +1672,7 @@ void main() { originalRealm.writeCopy(configCopy); originalRealm.close(); - expect(File(pathCopy).existsSync(), isTrue); + expect(Realm.existsSync(pathCopy), isTrue); final copiedRealm = getRealm(configCopy); expect(copiedRealm.all().length, itemsCount); copiedRealm.close(); @@ -1704,7 +1702,7 @@ void main() { originalRealm.writeCopy(configCopy); originalRealm.close(); - expect(File(configCopy.path).existsSync(), isTrue); + expect(Realm.existsSync(configCopy.path), isTrue); // Check data in copied realm before synchronization final disconnectedConfig = Configuration.disconnectedSync([Product.schema], path: configCopy.path, encryptionKey: destinationEncryptedKey); final disconnectedCopiedRealm = getRealm(disconnectedConfig); @@ -1752,7 +1750,7 @@ void main() { originalRealm.writeCopy(configCopy); originalRealm.close(); - expect(File(pathCopy).existsSync(), isTrue); + expect(Realm.existsSync(pathCopy), isTrue); final copiedRealm = getRealm(configCopy); expect(copiedRealm.all().length, itemsCount); copiedRealm.close(); @@ -1966,8 +1964,8 @@ void openEncryptedRealm(List? encryptionKey, List? decryptionKey, {voi afterEncrypt(realm); } if (encryptionKey == decryptionKey) { - final decriptedRealm = getRealm(config2); - expect(decriptedRealm.isClosed, false); + final decryptedRealm = getRealm(config2); + expect(decryptedRealm.isClosed, false); } else { expect( () => getRealm(config2), diff --git a/packages/realm_dart/test/subscription_test.dart b/packages/realm_dart/test/subscription_test.dart index 555fd7ae6..a24c05ade 100644 --- a/packages/realm_dart/test/subscription_test.dart +++ b/packages/realm_dart/test/subscription_test.dart @@ -2,16 +2,15 @@ // SPDX-License-Identifier: Apache-2.0 import 'dart:async'; -import 'dart:io'; import 'dart:math'; import 'dart:typed_data'; -import 'package:test/expect.dart' hide throws; - import 'package:realm_dart/realm.dart'; -import 'package:realm_dart/src/native/realm_core.dart'; +import 'package:realm_dart/src/handles/realm_core.dart'; import 'package:realm_dart/src/subscription.dart'; + import 'test.dart'; +import 'utils/platform_util.dart'; void main() { setupTests(); @@ -470,11 +469,11 @@ void main() { final appX = App(appConfigurationX); realmCore.clearCachedApps(); - final temporaryDir = await Directory.systemTemp.createTemp('realm_test_flexible_sync_roundtrip_'); + final temporaryPath = await platformUtil.createTempPath(); final appConfigurationY = AppConfiguration( appConfigurationX.appId, baseUrl: appConfigurationX.baseUrl, - baseFilePath: temporaryDir, + baseFilePath: temporaryPath, ); final appY = App(appConfigurationY); @@ -549,7 +548,7 @@ void main() { expect(() => subscriptions.state, returnsNormally); realm.close(); - expect(() => subscriptions.state, throws()); + expect(() => subscriptions.state, throws()); }); baasTest('SyncSessionErrorCode.compensatingWrite', (configuration) async { diff --git a/packages/realm_dart/test/test.dart b/packages/realm_dart/test/test.dart index f88e5d0ec..ff0432f26 100644 --- a/packages/realm_dart/test/test.dart +++ b/packages/realm_dart/test/test.dart @@ -3,8 +3,6 @@ import 'dart:async'; import 'dart:collection'; -import 'dart:ffi'; -import 'dart:io'; import 'dart:math'; import 'dart:typed_data'; @@ -12,12 +10,14 @@ import 'package:meta/meta.dart'; import 'package:path/path.dart' as _path; import 'package:realm_dart/realm.dart'; import 'package:realm_dart/src/configuration.dart'; +import 'package:realm_dart/src/handles/realm_core.dart'; import 'package:realm_dart/src/logging.dart'; -import 'package:realm_dart/src/native/realm_core.dart'; import 'package:realm_dart/src/realm_object.dart'; import 'package:test/test.dart'; +import 'package:universal_platform/universal_platform.dart'; import 'baas_helper.dart'; +import 'utils/platform_util.dart'; export 'package:test/test.dart'; @@ -25,6 +25,8 @@ export 'baas_helper.dart' show AppName; part 'test.realm.dart'; +typedef Platform = UniversalPlatform; + @RealmModel() class _Car { @PrimaryKey() @@ -410,8 +412,8 @@ final _openRealms = Queue(); String testUsername = "realm-test@realm.io"; String testPassword = "123456"; -const int maxInt = 9223372036854775807; -const int minInt = -9223372036854775808; +final int maxInt = platformUtil.maxInt; +final int minInt = platformUtil.minInt; const int jsMaxInt = 9007199254740991; const int jsMinInt = -9007199254740991; @@ -436,7 +438,7 @@ void setupTests() { } // Enable this to print platform info, including current PID - _printPlatformInfo(); + platformUtil.printPlatformInfo(); }); setUp(() { @@ -465,7 +467,7 @@ void setupTests() { Matcher throws([String? message]) => throwsA(isA().having((dynamic exception) => exception.message, 'message', contains(message ?? ''))); String generateRandomRealmPath() { - final path = _path.join(Directory.systemTemp.createTempSync("realm_test_").path, "${generateRandomString(10)}.realm"); + final path = _path.join(platformUtil.createTempPathSync(), "${generateRandomString(10)}.realm"); return path; } @@ -550,10 +552,9 @@ dynamic freezeDynamic(dynamic object) { return frozen; } -final dummy = File(""); Future tryDeleteRealm(String path) async { //Skip on CI to speed it up. We are creating the realms in $TEMP anyways. - if (Platform.environment.containsKey("REALM_CI")) { + if (platformUtil.environment.containsKey("REALM_CI")) { return; } @@ -561,10 +562,6 @@ Future tryDeleteRealm(String path) async { for (var i = 0; i < 5; i++) { try { Realm.deleteRealm(path); - - //delete lock file - await File('$path.lock').delete().onError((error, stackTrace) => dummy); - return; } catch (e) { Realm.logger.log(LogLevel.info, 'Failed to delete realm at path $path. Trying again in ${duration.inMilliseconds}ms'); @@ -708,23 +705,6 @@ extension DateTimeTest on DateTime { void clearCachedApps() => realmCore.clearCachedApps(); -void _printPlatformInfo() { - final pointerSize = sizeOf() * 8; - final os = Platform.operatingSystem; - String? cpu; - - if (!isFlutterPlatform) { - if (Platform.isWindows) { - cpu = Platform.environment['PROCESSOR_ARCHITECTURE']; - } else { - final info = Process.runSync('uname', ['-m']); - cpu = info.stdout.toString().replaceAll('\n', ''); - } - } - - print('Current PID $pid; OS $os, $pointerSize bit, CPU ${cpu ?? 'unknown'}'); -} - extension StreamEx on Stream> { Stream switchLatest() async* { StreamSubscription? inner; @@ -764,13 +744,13 @@ List getSyncSchema() { ]; } -Future runWithRetries(bool Function() tester, {int retryDelay = 100, int attempts = 100}) async { - var success = tester(); +Future runWithRetries(FutureOr Function() tester, {int retryDelay = 100, int attempts = 100}) async { + var success = await tester(); var timeout = retryDelay * attempts; while (!success && attempts > 0) { await Future.delayed(Duration(milliseconds: retryDelay)); - success = tester(); + success = await tester(); attempts--; } @@ -781,8 +761,4 @@ Future runWithRetries(bool Function() tester, {int retryDelay = 100, int a return success; } -Future _copyFile(String fromPath, String toPath) async { - await File(fromPath).copy(toPath); -} - -var copyFile = _copyFile; // default, but allow integration_test to override +var copyFile = platformUtil.copy; // default, but allow integration_test to override diff --git a/packages/realm_dart/test/user_test.dart b/packages/realm_dart/test/user_test.dart index df21c1087..082304074 100644 --- a/packages/realm_dart/test/user_test.dart +++ b/packages/realm_dart/test/user_test.dart @@ -4,9 +4,8 @@ import 'dart:async'; import 'dart:isolate'; -import 'package:test/expect.dart' hide throws; - import 'package:realm_dart/realm.dart'; + import 'test.dart'; void main() { diff --git a/packages/realm_dart/test/utils/native/platform_util.dart b/packages/realm_dart/test/utils/native/platform_util.dart new file mode 100644 index 000000000..3b82afc65 --- /dev/null +++ b/packages/realm_dart/test/utils/native/platform_util.dart @@ -0,0 +1,56 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:realm_dart/realm.dart'; + +import '../platform_util.dart' as intf; + +class PlatformUtil implements intf.PlatformUtil { + const PlatformUtil(); + + @override + String get systemTempPath => Directory.systemTemp.path; + @override + Future createTempPath([String? prefix]) => Directory.systemTemp.createTemp(prefix).then((d) => d.path); + @override + String createTempPathSync([String? prefix]) => Directory.systemTemp.createTempSync(prefix).path; + @override + Future sizeOnStorage(Configuration config) => File(config.path).stat().then((fs) => fs.size); + + @override + void printPlatformInfo() { + final os = Platform.operatingSystem; + String? cpu; + + if (!isFlutterPlatform) { + if (Platform.isWindows) { + cpu = Platform.environment['PROCESSOR_ARCHITECTURE']; + } else { + final info = Process.runSync('uname', ['-m']); + cpu = info.stdout.toString().replaceAll('\n', ''); + } + } + + print('Current PID $pid; OS $os, CPU ${cpu ?? 'unknown'}'); + } + + @override + Future copy(String fromPath, String toPath) async { + await File(fromPath).copy(toPath); + } + + @override + Future readAsBytes(String path) => File(path).readAsBytes(); + + @override + Map get environment => Platform.environment; + + @override + int get maxInt => 0x7FFFFFFFFFFFFFFF; + + @override + int get minInt => -0x8000000000000000; +} diff --git a/packages/realm_dart/test/utils/platform_util.dart b/packages/realm_dart/test/utils/platform_util.dart new file mode 100644 index 000000000..350ba846e --- /dev/null +++ b/packages/realm_dart/test/utils/platform_util.dart @@ -0,0 +1,29 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import 'dart:typed_data'; + +import 'package:realm_dart/realm.dart'; + +import 'native/platform_util.dart' if (dart.library.js_interop) 'web/platform_util.dart' as impl; + +abstract interface class PlatformUtil { + const factory PlatformUtil() = impl.PlatformUtil; + + String get systemTempPath; + Future createTempPath(); + String createTempPathSync(); + Future sizeOnStorage(Configuration config); + + void printPlatformInfo(); + + Future copy(String fromPath, String toPath); + Future readAsBytes(String path); + + Map get environment; + + int get maxInt; + int get minInt; +} + +const platformUtil = PlatformUtil(); diff --git a/packages/realm_dart/test/utils/web/platform_util.dart b/packages/realm_dart/test/utils/web/platform_util.dart new file mode 100644 index 000000000..d414ae5e4 --- /dev/null +++ b/packages/realm_dart/test/utils/web/platform_util.dart @@ -0,0 +1,11 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import '../platform_util.dart' as intf; + +class PlatformUtil implements intf.PlatformUtil { + const PlatformUtil(); + + @override + noSuchMethod(Invocation invocation) => throw UnsupportedError('web not supported'); +} From 9979ea43112a4005476a5cd8d5ef4fa33c550528 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Fri, 7 Jun 2024 10:18:19 +0200 Subject: [PATCH 08/16] Refresh after awaiting download to stabilize tests --- packages/realm_dart/test/realm_value_test.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/realm_dart/test/realm_value_test.dart b/packages/realm_dart/test/realm_value_test.dart index eb679f203..9ba4233d7 100644 --- a/packages/realm_dart/test/realm_value_test.dart +++ b/packages/realm_dart/test/realm_value_test.dart @@ -50,6 +50,7 @@ void main() { Future waitForSynchronization({required Realm uploadRealm, required Realm downloadRealm}) async { await uploadRealm.syncSession.waitForUpload(); await downloadRealm.syncSession.waitForDownload(); + downloadRealm.refresh(); } group('RealmValue', () { From 558ab3d0b681b7f2783a4639c8b083927e180477 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Fri, 7 Jun 2024 10:57:52 +0200 Subject: [PATCH 09/16] Reduce expected gain of memEquals for test stability --- packages/realm_dart/test/realm_value_test.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/realm_dart/test/realm_value_test.dart b/packages/realm_dart/test/realm_value_test.dart index 9ba4233d7..d67c1e47f 100644 --- a/packages/realm_dart/test/realm_value_test.dart +++ b/packages/realm_dart/test/realm_value_test.dart @@ -2455,8 +2455,8 @@ void main() { expect(RealmValue.from(bin1), isNot(RealmValue.from(bin2))); } - // Not quite 8 times speedup, but close enough. Setting the threshold to 4 + // Not quite 8 times speedup, but close enough. Setting the threshold to 2 // for test stability sake. - expect(listEqualsClock.elapsedTicks ~/ 4, greaterThan(memEqualsClock.elapsedTicks)); + expect(listEqualsClock.elapsedTicks ~/ 2, greaterThan(memEqualsClock.elapsedTicks)); }); } From 889e4340325c5acfac1baed46e1610ce81ccb51b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Fri, 7 Jun 2024 14:25:30 +0200 Subject: [PATCH 10/16] RDART-866: kn/decimal128 web support (#1713) * Implement Decimal128 for web * fromDouble * ==, hashCode * Add deps on decimal and rational * Match IEEE 754 Decimal128 precision (+/- 10^6144) * Comment on compliancy * Fix compile for web regression * Update CHANGELOG --- .vscode/settings.json | 1 + CHANGELOG.md | 3 +- .../lib/src/handles/web/decimal128.dart | 88 +++++++++++++++++-- packages/realm_dart/pubspec.yaml | 2 + 4 files changed, 87 insertions(+), 7 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 483d755b2..44b79f873 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -24,6 +24,7 @@ "fnum", "geospatial", "HRESULT", + "intf", "keepalive", "keypaths", "loggable", diff --git a/CHANGELOG.md b/CHANGELOG.md index 009d15b6c..40429f0dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,8 @@ ### Enhancements * Report the originating error that caused a client reset to occur. (Core 14.9.0) * Allow the realm package, and code generated by realm_generator to be included when building - for web without breaking compilation. (Issue [#1374](https://github.com/realm/realm-dart/issues/1374)) + for web without breaking compilation. (Issue [#1374](https://github.com/realm/realm-dart/issues/1374), + PR [#1713](https://github.com/realm/realm-dart/pull/1713)) ### Fixed * `Realm.writeAsync` did not handle async callbacks (`Future Function()`) correctly. (Issue [#1667](https://github.com/realm/realm-dart/issues/1667)) diff --git a/packages/realm_dart/lib/src/handles/web/decimal128.dart b/packages/realm_dart/lib/src/handles/web/decimal128.dart index 58240b22b..342433918 100644 --- a/packages/realm_dart/lib/src/handles/web/decimal128.dart +++ b/packages/realm_dart/lib/src/handles/web/decimal128.dart @@ -1,9 +1,27 @@ // Copyright 2024 MongoDB, Inc. // SPDX-License-Identifier: Apache-2.0 -import '../decimal128.dart' as intf; +import 'package:decimal/decimal.dart'; + +import 'package:realm_dart/src/convert.dart'; + import 'web_not_supported.dart'; +import '../decimal128.dart' as intf; + +/// This is not a compliant IEEE 754-2008 Decimal128 implementation, as it is +/// just based on the [decimal](https://pub.dev/packages/decimal) package. +/// Which is based on the [rational](https://pub.dev/packages/rational) package, +/// which is again based on the [BigInt] class. +/// +/// The issues are mostly in some of the odd corner cases of IEEE 754-2008 +/// Decimal128, such as: +/// * -0 < 0, +/// * NaN != NaN, +/// * 1 / 0 = Inf, etc. +/// +/// Also, be warned that this class is incredible slow compared to the native +/// implementation. class Decimal128 implements intf.Decimal128 { static final zero = Decimal128.fromInt(0); @@ -23,17 +41,75 @@ class Decimal128 implements intf.Decimal128 { static final negativeInfinity = -infinity; /// Parses a string into a [Decimal128]. Returns `null` if the string is not a valid [Decimal128]. - static Decimal128? tryParse(String source) => webNotSupported(); + static Decimal128? tryParse(String source) => Decimal.tryParse(source).convert(Decimal128._); /// Parses a string into a [Decimal128]. Throws a [FormatException] if the string is not a valid [Decimal128]. - factory Decimal128.parse(String source) => webNotSupported(); + factory Decimal128.parse(String source) => Decimal128._(Decimal.parse(source)); /// Converts a `int` into a [Decimal128]. - factory Decimal128.fromInt(int value) => webNotSupported(); + factory Decimal128.fromInt(int value) => Decimal128._(Decimal.fromInt(value)); /// Converts a `double` into a [Decimal128]. - factory Decimal128.fromDouble(double value) => webNotSupported(); + factory Decimal128.fromDouble(double value) { + if (value.isNaN) return nan; + if (value.isInfinite) return value.isNegative ? negativeInfinity : infinity; + return Decimal128._(Decimal.parse(value.toString())); + } + + final Decimal _value; + Decimal128._(Decimal value) : _value = value.truncate(scale: 6144); + + @override + Decimal128 operator *(covariant Decimal128 other) => Decimal128._(_value * other._value); + + @override + Decimal128 operator +(covariant Decimal128 other) => Decimal128._(_value + other._value); + + @override + Decimal128 operator -() => Decimal128._(-_value); + + @override + Decimal128 operator -(covariant Decimal128 other) => Decimal128._(_value - other._value); + + // Note IEEE 754 Decimal128 defines division with zero as infinity, similar to double + @override + Decimal128 operator /(covariant Decimal128 other) => Decimal128._((_value / other._value).toDecimal(scaleOnInfinitePrecision: 6144)); + + @override + bool operator <(covariant Decimal128 other) => _value < other._value; + + @override + bool operator <=(covariant Decimal128 other) => _value <= other._value; + + @override + bool operator >(covariant Decimal128 other) => _value > other._value; + + @override + bool operator >=(covariant Decimal128 other) => _value >= other._value; + + @override + Decimal128 abs() => Decimal128._(_value.abs()); + + @override + int compareTo(covariant Decimal128 other) { + final sign = _value.compareTo(other._value); + if (sign < 0) return -1; + if (sign > 0) return 1; + return 0; + } + + @override + bool get isNaN => this == nan; + + @override + int toInt() => _value.toBigInt().toInt(); + + @override + String toString() => _value.toStringAsExponential(27); + + @override + operator ==(Object other) => other is Decimal128 && _value == other._value; @override - noSuchMethod(Invocation invocation) => webNotSupported(); + int get hashCode => _value.hashCode; } diff --git a/packages/realm_dart/pubspec.yaml b/packages/realm_dart/pubspec.yaml index b4a63abd2..86e38dff5 100644 --- a/packages/realm_dart/pubspec.yaml +++ b/packages/realm_dart/pubspec.yaml @@ -29,6 +29,8 @@ dependencies: build_runner: ^2.1.0 http: ^1.0.0 cancellation_token: ^2.0.0 + decimal: ^3.0.1 + rational: ^2.2.3 dev_dependencies: build_cli: ^2.2.2 From 9eabca269599cd1f872bef2d8ccc5639e148330a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Fri, 7 Jun 2024 14:26:33 +0200 Subject: [PATCH 11/16] Github composite action for setting up flutter on runner (#1710) * Use workflow_dispatch * source ~/.profile to update paths after install of puro * Update $GITHUB_PATH * Checkout first * Add cache + simplify puro install * use --global, no melos setup, and some cleanup * .puro != ~/.puro * export $PUB_CACHE and $PURO_ROOT * Don't source ~/.profile. Set env variables explicitly * Tweak cache key * Support windows * Convert setup-runner from workflow to action * Blunt hack to get windows working * Skip stuff on partial (or exact) cache-hit * Switch from run_id to hashFiles * Use runPubGetOffline * Populate pub-cache explicitly on cache miss, as melos bootstrap no longer does * Update cSpell.words * Use for: compile for web * Remove inverted save cache condition (Debug code) * Update CHANGELOG * Ensure caches are cycled atleast once a week --- .github/actions/setup-runner/action.yml | 94 +++++++++++++++++++++ .github/workflows/ci.yml | 54 +++--------- .github/workflows/dart-desktop-tests.yml | 13 +-- .github/workflows/deploy-baas.yml | 15 +--- .github/workflows/flutter-desktop-tests.yml | 15 +--- .github/workflows/prepare-release.yml | 10 +-- .github/workflows/publish-release.yml | 22 +---- .github/workflows/setup-runner.yml | 39 --------- .github/workflows/terminate-baas.yml | 15 +--- .vscode/settings.json | 2 + CHANGELOG.md | 2 +- melos.yaml | 19 +++-- 12 files changed, 136 insertions(+), 164 deletions(-) create mode 100644 .github/actions/setup-runner/action.yml delete mode 100644 .github/workflows/setup-runner.yml diff --git a/.github/actions/setup-runner/action.yml b/.github/actions/setup-runner/action.yml new file mode 100644 index 000000000..33474b85b --- /dev/null +++ b/.github/actions/setup-runner/action.yml @@ -0,0 +1,94 @@ +name: Setup runner +description: Setup the runner with the necessary tools and dependencies. + +inputs: + flutter-version: + description: Flutter version to use. + default: stable + cache-key: + description: Cache key to use for restoring and saving the cache. + default: ${{ hashFiles('**/pubspec.yaml') }} + +runs: + using: "composite" + steps: + - name: Setup environment + # By forcing puro (and hence the pub-cache) onto the same drive as the checkout, + # we can avoid symlink issue between local and network drives on windows. + id: setup + shell: bash + run: | + export PURO_ROOT=$(dirname $GITHUB_WORKSPACE)/puro + mkdir -p $PURO_ROOT + + echo $PURO_ROOT/bin >> $GITHUB_PATH + echo $PURO_ROOT/shared/pub_cache/bin/ >> $GITHUB_PATH + echo $PURO_ROOT/envs/default/flutter/bin/ >> $GITHUB_PATH + + echo PURO_ROOT=$PURO_ROOT >> $GITHUB_ENV + echo PUB_CACHE=$PURO_ROOT/shared/pub_cache >> $GITHUB_ENV + echo MELOS_SDK_PATH=$PURO_ROOT/envs/default/flutter/ >> $GITHUB_ENV + + echo "week = $(date +'%Y-%U')" >> $GITHUB_OUTPUT + + - name: Restore cache + # TODO: Avoid skipping restore on Windows + # (see https://github.com/pingbird/puro/issues/87) + if: runner.os != 'Windows' + id: restore + uses: actions/cache/restore@v4 + with: + path: ${{ env.PURO_ROOT }} + key: setup-runner-${{ runner.os }}-${{ runner.arch }}-${{ steps.setup.outputs.week }}-${{ inputs.cache-key }} + restore-keys: setup-runner-${{ runner.os }}-${{ runner.arch }}- + + - name: Install puro (posix) + # See https://puro.dev/ + # Skip on any cache hit (partial or full) + if: runner.os != 'Windows' && steps.restore.outputs.cache-matched-key == '' + shell: bash + run: curl -sS -o- https://puro.dev/install.sh | PURO_VERSION="1.4.6" bash + + - name: Install puro (windows) + # We always install on windows, as we currently don't restore a cache (see TODO above) + if: runner.os == 'Windows' && steps.restore.outputs.cache-matched-key == '' + shell: pwsh + run: >- + Invoke-WebRequest + -Uri "https://puro.dev/builds/1.4.6/windows-x64/puro.exe" + -OutFile "$Env:PURO_ROOT"; + &"$Env:PURO_ROOT\puro.exe" install-puro --promote + + - name: Upgrade puro and flutter + shell: bash + run: | + puro upgrade-puro + puro use --global ${{ inputs.flutter-version }} + puro upgrade ${{ inputs.flutter-version }} + puro gc + + - name: Activate melos + if: steps.restore.outputs.cache-matched-key == '' + shell: bash + run: puro pub global run melos --version || puro pub global activate melos + + - name: Pub get + # We need to run `flutter pub get` first, to ensure the pub-cache is populated, + # if we don't have an exact cache hit. + if: steps.restore.outputs.cache-hit != 'true' + shell: bash + run: puro pub global run melos exec -- flutter pub get + + - name: Melos bootstrap + shell: bash + run: puro pub global run melos bootstrap + + - name: Save cache + # Saving on feature branches will only benefit reruns, so we don't bother. + # Feature branches inherit the cache from the main branch anyway. + # This saves on cache storage. + if: github.ref == 'refs/heads/main' && steps.restore.outputs.cache-hit != 'true' + uses: actions/cache/save@v4 + with: + path: ${{ env.PURO_ROOT }} + key: ${{ steps.restore.outputs.cache-primary-key }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4058963b5..e866b86a7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -321,13 +321,16 @@ jobs: with: submodules: "recursive" + - name: Setup runner + uses: ./.github/actions/setup-runner + - name: Bump ulimit run: | ulimit -n ulimit -n 10240 - name: Enable ccache - run: echo "PATH=/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" >> $GITHUB_ENV + run: echo "/usr/lib/ccache:/usr/local/opt/ccache/libexec" >> $GITHUB_PATH - name: Fetch artifacts uses: actions/download-artifact@v4 @@ -335,19 +338,6 @@ jobs: name: librealm-ios path: packages/realm_dart/binary/ios - - name: Setup Flutter - uses: subosito/flutter-action@v2 - with: - channel: "stable" - - - name: Setup Melos - run: | - dart pub global activate melos - dart pub global run melos bootstrap - - - name: Install dependencies - run: dart pub get - - name: Launch Simulator uses: futureware-tech/simulator-action@v3 with: @@ -431,18 +421,8 @@ jobs: name: librealm-android path: packages/realm_dart/binary/android - - name: Setup Flutter - uses: subosito/flutter-action@v2 - with: - channel: "stable" - - - name: Setup Melos - run: | - dart pub global activate melos - dart pub global run melos bootstrap - - - name: Install dependencies - run: dart pub get + - name: Setup Runner + uses: ./.github/actions/setup-runner # Hack to free up space on the runner to ensure we have enough diskspace to run the emulator - name: Remove unnecessary files (dotnet, etc.) @@ -526,15 +506,8 @@ jobs: with: submodules: "recursive" - - name: Setup Flutter - uses: subosito/flutter-action@v2 - with: - channel: "stable" - - - name: Setup Melos - run: | - dart pub global activate melos - dart pub global run melos bootstrap + - name: Setup Runner + uses: ./.github/actions/setup-runner - name: Delete generated files in realm_dart run: | @@ -629,15 +602,8 @@ jobs: with: submodules: "recursive" - - name: Setup Flutter - uses: subosito/flutter-action@v2 - with: - channel: "stable" - - - name: Setup Melos - run: | - dart pub global activate melos - dart pub global run melos bootstrap + - name: Setup Runner + uses: ./.github/actions/setup-runner - name: Compile to wasm run: flutter build web --wasm -t integration_test/all_tests.dart diff --git a/.github/workflows/dart-desktop-tests.yml b/.github/workflows/dart-desktop-tests.yml index 1d65b3cca..0a22a26c2 100644 --- a/.github/workflows/dart-desktop-tests.yml +++ b/.github/workflows/dart-desktop-tests.yml @@ -50,16 +50,8 @@ jobs: name: librealm-${{ steps.runner_os_lowercase.outputs.os }} path: packages/realm_dart/binary/${{ steps.runner_os_lowercase.outputs.os }} - - name: Setup Dart SDK - uses: dart-lang/setup-dart@main - with: - sdk: stable - - - name: Setup Melos - run: | - dart pub global activate melos - melos bootstrap --no-flutter - melos setup + - name: Setup Runner + uses: ./.github/actions/setup-runner - name: Bump ulimit on macos run: | @@ -85,6 +77,7 @@ jobs: if: inputs.runner == 'ubuntu-latest' run: | sudo apt-get install lcov + melos setup melos coverage:convert melos coverage:gather melos coverage:groom diff --git a/.github/workflows/deploy-baas.yml b/.github/workflows/deploy-baas.yml index 3e7263aa8..15a5a897a 100644 --- a/.github/workflows/deploy-baas.yml +++ b/.github/workflows/deploy-baas.yml @@ -25,19 +25,8 @@ jobs: with: submodules: false - - name: Setup Dart SDK - uses: dart-lang/setup-dart@main - with: - sdk: stable - architecture: 'x64' - - - name: Setup Melos - run: | - dart pub global activate melos - dart pub global run melos bootstrap --no-flutter - - - name: Install dependencies - run: dart pub get + - name: Setup Runner + uses: ./.github/actions/setup-runner - name: Deploy cluster and apps run: dart run realm_dart deploy-apps --baasaas-api-key ${{ secrets.BAASAAS_API_KEY }} --differentiator ${{ inputs.differentiator }} diff --git a/.github/workflows/flutter-desktop-tests.yml b/.github/workflows/flutter-desktop-tests.yml index a5234499b..13c55e297 100644 --- a/.github/workflows/flutter-desktop-tests.yml +++ b/.github/workflows/flutter-desktop-tests.yml @@ -61,19 +61,8 @@ jobs: name: librealm-${{ steps.runner_os_lowercase.outputs.os }} path: packages/realm_dart/binary/${{ steps.runner_os_lowercase.outputs.os }} - - name: Setup Flutter - uses: subosito/flutter-action@v2 - with: - channel: 'stable' - architecture: ${{ inputs.arch }} - - - name: Setup Melos - run: | - dart pub global activate melos - dart pub global run melos bootstrap - - - name: Install dependencies - run: dart pub get + - name: Setup Runner + uses: ./.github/actions/setup-runner - name: Bump ulimit run: | diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index 06a0edf42..2cc7deb14 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -27,14 +27,8 @@ jobs: version: ${{ inputs.version }} version-suffix: '' - - name : Setup Dart SDK - uses: dart-lang/setup-dart@main - with: - sdk: stable - architecture: 'x64' - - - name: Setup Melos - run: dart pub global activate melos + - name: Setup Runner + uses: ./.github/actions/setup-runner - name: Update pubspec.yaml files run: REALM_VERSION=${{ steps.update-changelog.outputs.new-version }} melos run update:version:realm diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index d531907b0..ead5bb1a1 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -30,15 +30,8 @@ jobs: echo "version=$pkgVersion$pkgSuffix" >> $GITHUB_OUTPUT shell: bash - - name: Setup Flutter - uses: subosito/flutter-action@v2 - with: - channel: 'stable' - - - name: Setup Melos - run: | - dart pub global activate melos - dart pub global run melos bootstrap + - name: Setup Runner + uses: ./.github/actions/setup-runner - name: Download all artifacts uses: dawidd6/action-download-artifact@71072fbb1229e1317f1a8de6b04206afb461bd67 # 3.1.2 @@ -161,15 +154,8 @@ jobs: name: release-bundle path: release - - name: Setup Flutter - uses: subosito/flutter-action@v2 - with: - channel: 'stable' - - - name: Setup Melos - run: | - dart pub global activate melos - dart pub global run melos bootstrap + - name: Setup Runner + uses: ./.github/actions/setup-runner # realm_flutter has symlinks to native binaries which should not be packaged - name: Cleanup symlinks diff --git a/.github/workflows/setup-runner.yml b/.github/workflows/setup-runner.yml deleted file mode 100644 index 5cb457cca..000000000 --- a/.github/workflows/setup-runner.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: Setup runner - -on: - workflow_dispatch: # For debug - workflow_call: - inputs: - runner: - description: Platforms to execute on. - required: true - type: string - flutter-version: - description: Flutter version to use. - type: string - default: stable - -jobs: - setup-runner: - runs-on: ${{ inputs.runner }} - steps: - - name: Puro - run: >- - which puro - || curl -o- https://puro.dev/install.sh | PURO_VERSION="1.4.5" bash - shell: bash - - run: puro upgrade-puro - - run: puro use ${{ inputs.flutter-version }} - - - name: Checkout - uses: actions/checkout@v4 - with: - submodules: "recursive" - - - name: Melos - run: puro pub global activate melos - - run: melos bootstrap - - run: melos setup - - - diff --git a/.github/workflows/terminate-baas.yml b/.github/workflows/terminate-baas.yml index 547d5a32a..7336dc931 100644 --- a/.github/workflows/terminate-baas.yml +++ b/.github/workflows/terminate-baas.yml @@ -25,19 +25,8 @@ jobs: with: submodules: false - - name: Setup Dart SDK - uses: dart-lang/setup-dart@main - with: - sdk: stable - architecture: 'x64' - - - name: Setup Melos - run: | - dart pub global activate melos - dart pub global run melos bootstrap --no-flutter - - - name: Install dependencies - run: dart pub get + - name: Setup Runner + uses: ./.github/actions/setup-runner - name: Terminate baas run: dart run realm_dart delete-apps --baasaas-api-key ${{ secrets.BAASAAS_API_KEY }} --differentiator ${{ inputs.differentiator }} diff --git a/.vscode/settings.json b/.vscode/settings.json index 44b79f873..86eebf893 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -33,6 +33,8 @@ "nodoc", "nullptr", "posix", + "puro", + "pwsh", "riscv", "sublist", "sublists", diff --git a/CHANGELOG.md b/CHANGELOG.md index 40429f0dc..de1cccb8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,6 @@ * To avoid dependency on `dart:io` - `AppConfiguration.httpClient` is now of type [`Client`](https://pub.dev/documentation/http/latest/http/Client-class.html) and - `AppConfiguration.baseFilePath` is now of type `String` - (Issue [#1374](https://github.com/realm/realm-dart/issues/1374)) ### Enhancements @@ -30,6 +29,7 @@ * Disabled codesigning of Apple binaries. (Issue [#1679](https://github.com/realm/realm-dart/issues/1679)) * Drop building xcframework for catalyst. (Issue [#1695](https://github.com/realm/realm-dart/issues/1695)) * Using xcode 15.4 for native build. (Issue [#1547](https://github.com/realm/realm-dart/issues/1547)) +* Using puro on CI. ([#1710](https://github.com/realm/realm-dart/pull/1710)) ## 2.3.0 (2024-05-23) diff --git a/melos.yaml b/melos.yaml index 7cc7c4cba..269598487 100644 --- a/melos.yaml +++ b/melos.yaml @@ -13,6 +13,16 @@ packages: command: bootstrap: + # To benefit most from the cached pub-cache on CI we want to use + # `flutter pub get --offline` when running `melos bootstrap`. + # Unfortunately, melos doesn't support '--offline' as a cli argument yet + # (see https://github.com/invertase/melos/issues/714). + # + # Instead we set `runPubGetOffline` to `true` to achieve the same effect. + # This has the downside of not updating deps when running + # 'melos bootstrap' locally as well, but that can easily by mitigated + # by running: `melos exec -- flutter pub get`. + runPubGetOffline: true environment: sdk: ^3.3.0 flutter: ^3.19.0 @@ -40,7 +50,7 @@ scripts: - build:native - build:binding - build:dart - + build:native: exec: dev build packageFilters: @@ -121,7 +131,7 @@ scripts: run: >- find lib test integration_test -name '*.dart' -not -name '*.g.dart' -not -name '*.realm.dart' -not -name 'realm_bindings.dart' 2> /dev/null | xargs dart format --fix --line-length 160 - exec: + exec: concurrency: 1 # only one project at a time to keep output sane upgrade: @@ -143,7 +153,7 @@ scripts: run: >- find lib test integration_test -name '*.dart' -not -name '*.g.dart' -not -name '*.realm.dart' -not -name 'realm_bindings.dart' 2> /dev/null | xargs dart format --fix --line-length 160 --output none --set-exit-if-changed - exec: + exec: concurrency: 1 # only one project at a time to keep output sane lint:pana: @@ -152,7 +162,7 @@ scripts: concurrency: 1 # only one project at a time to keep output sane packageFilters: published: true - + coverage: description: Generate, check and render coverage. steps: @@ -223,4 +233,3 @@ scripts: - coverage - analyze - lint - \ No newline at end of file From 560a584f88262f8ab7692f927cf4fe155f4c797a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Fri, 7 Jun 2024 16:05:44 +0200 Subject: [PATCH 12/16] Update CHANGELOG (#1715) --- CHANGELOG.md | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index de1cccb8c..73439e533 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,14 +3,42 @@ ### Breaking Changes * To avoid dependency on `dart:io` - `AppConfiguration.httpClient` is now of type [`Client`](https://pub.dev/documentation/http/latest/http/Client-class.html) and - - `AppConfiguration.baseFilePath` is now of type `String` + - `AppConfiguration.baseFilePath` is now of type `String`. + + Assuming you are configuring these today, migration is easy: + ```dart + import 'dart:io'; + import 'package:realm_dart/realm.dart'; + + final client = HttpClient(); + final dir = Directory.current; + final config = AppConfiguration( + 'your-app-id', + httpClient: client, + baseFilePath: dir, + ); + ``` + becomes: + ```dart + import 'dart:io'; + import 'package:realm_dart/realm.dart'; + import 'package:http/io_client.dart'; + + final client = HttpClient(); + final dir = Directory.current; + final config = AppConfiguration( + 'your-app-id', + httpClient: IOClient(client), + baseFilePath: dir.path, + ); + ``` (Issue [#1374](https://github.com/realm/realm-dart/issues/1374)) ### Enhancements * Report the originating error that caused a client reset to occur. (Core 14.9.0) * Allow the realm package, and code generated by realm_generator to be included when building for web without breaking compilation. (Issue [#1374](https://github.com/realm/realm-dart/issues/1374), - PR [#1713](https://github.com/realm/realm-dart/pull/1713)) + PR [#1713](https://github.com/realm/realm-dart/pull/1713)). This does **not** imply that realm works on web! ### Fixed * `Realm.writeAsync` did not handle async callbacks (`Future Function()`) correctly. (Issue [#1667](https://github.com/realm/realm-dart/issues/1667)) From ec63c736b7ea51b0a170ffbba77c12afa837803b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Fri, 7 Jun 2024 16:23:50 +0200 Subject: [PATCH 13/16] libraryVersion moved to realm_libary.dart --- .github/workflows/prepare-release.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index 2cc7deb14..c4a98d177 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -40,15 +40,15 @@ jobs: replace: " s.version$1= '${{ steps.update-changelog.outputs.new-version }}'" include: '**realm.podspec' - - name: Update libraryVersion in realm_core.dart + - name: Update libraryVersion in realm_library.dart id: update-libraryVersion uses: jacobtomlinson/gha-find-replace@b76729678e8d52dadb12e0e16454a93e301a919d #! 2.0.0 with: find: "const libraryVersion = '[^']*';" replace: "const libraryVersion = '${{ steps.update-changelog.outputs.new-version }}';" - include: 'packages/realm_dart/lib/src/native/realm_core.dart' + include: 'packages/realm_dart/lib/src/handles/native/realm_core.dart' - - name: Make sure we updated libraryVersion in realm_core.dart + - name: Make sure we updated libraryVersion in realm_library.dart run: | if [ '${{ steps.update-libraryVersion.outputs.modifiedFiles }}' != '1' ]; then echo 'Expected exactly one modified file, got ${{ steps.update-libraryVersion.outputs.modifiedFiles }}' From c7c7767162bc4782a7169084ab501bec8c7f1992 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Fri, 7 Jun 2024 16:27:55 +0200 Subject: [PATCH 14/16] libraryVersion moved to realm_libary.dart (take 2) --- .github/workflows/prepare-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index c4a98d177..334e97cf7 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -46,7 +46,7 @@ jobs: with: find: "const libraryVersion = '[^']*';" replace: "const libraryVersion = '${{ steps.update-changelog.outputs.new-version }}';" - include: 'packages/realm_dart/lib/src/handles/native/realm_core.dart' + include: 'packages/realm_dart/lib/src/handles/native/realm_library.dart' - name: Make sure we updated libraryVersion in realm_library.dart run: | From 925963cdfd64d6640c54bb6aa8843ea57f030d71 Mon Sep 17 00:00:00 2001 From: Yavor Georgiev Date: Fri, 7 Jun 2024 17:38:55 +0200 Subject: [PATCH 15/16] [Release 3.0.0] (#1716) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Release 3.0.0] * A bit too clever. We need to wait for: https://github.com/invertase/melos/issues/714 --------- Co-authored-by: nielsenko Co-authored-by: Kasper Overgård Nielsen --- .github/actions/setup-runner/action.yml | 9 +-------- CHANGELOG.md | 2 +- melos.yaml | 10 ---------- packages/realm/example/pubspec.yaml | 2 +- packages/realm/ios/realm.podspec | 2 +- packages/realm/macos/realm.podspec | 2 +- packages/realm/pubspec.yaml | 4 ++-- packages/realm_common/pubspec.yaml | 2 +- .../lib/src/cli/metrics/metrics_command.dart | 2 +- .../lib/src/handles/native/realm_library.dart | 2 +- packages/realm_dart/pubspec.yaml | 6 +++--- packages/realm_dart/src/realm_dart.cpp | 2 +- packages/realm_generator/pubspec.yaml | 4 ++-- 13 files changed, 16 insertions(+), 33 deletions(-) diff --git a/.github/actions/setup-runner/action.yml b/.github/actions/setup-runner/action.yml index 33474b85b..9948f0a5d 100644 --- a/.github/actions/setup-runner/action.yml +++ b/.github/actions/setup-runner/action.yml @@ -29,7 +29,7 @@ runs: echo PUB_CACHE=$PURO_ROOT/shared/pub_cache >> $GITHUB_ENV echo MELOS_SDK_PATH=$PURO_ROOT/envs/default/flutter/ >> $GITHUB_ENV - echo "week = $(date +'%Y-%U')" >> $GITHUB_OUTPUT + echo "week=$(date +'%Y-%U')" >> $GITHUB_OUTPUT - name: Restore cache # TODO: Avoid skipping restore on Windows @@ -72,13 +72,6 @@ runs: shell: bash run: puro pub global run melos --version || puro pub global activate melos - - name: Pub get - # We need to run `flutter pub get` first, to ensure the pub-cache is populated, - # if we don't have an exact cache hit. - if: steps.restore.outputs.cache-hit != 'true' - shell: bash - run: puro pub global run melos exec -- flutter pub get - - name: Melos bootstrap shell: bash run: puro pub global run melos bootstrap diff --git a/CHANGELOG.md b/CHANGELOG.md index 73439e533..ab6831cc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## vNext (TBD) +## 3.0.0 (2024-06-07) ### Breaking Changes * To avoid dependency on `dart:io` diff --git a/melos.yaml b/melos.yaml index 269598487..92539f09d 100644 --- a/melos.yaml +++ b/melos.yaml @@ -13,16 +13,6 @@ packages: command: bootstrap: - # To benefit most from the cached pub-cache on CI we want to use - # `flutter pub get --offline` when running `melos bootstrap`. - # Unfortunately, melos doesn't support '--offline' as a cli argument yet - # (see https://github.com/invertase/melos/issues/714). - # - # Instead we set `runPubGetOffline` to `true` to achieve the same effect. - # This has the downside of not updating deps when running - # 'melos bootstrap' locally as well, but that can easily by mitigated - # by running: `melos exec -- flutter pub get`. - runPubGetOffline: true environment: sdk: ^3.3.0 flutter: ^3.19.0 diff --git a/packages/realm/example/pubspec.yaml b/packages/realm/example/pubspec.yaml index 507f4ac87..9325d070a 100644 --- a/packages/realm/example/pubspec.yaml +++ b/packages/realm/example/pubspec.yaml @@ -11,7 +11,7 @@ environment: dependencies: flutter: sdk: flutter - realm: ^2.3.0 + realm: ^3.0.0 characters: ^1.1.0 dev_dependencies: diff --git a/packages/realm/ios/realm.podspec b/packages/realm/ios/realm.podspec index ea3217075..1d7500d5a 100644 --- a/packages/realm/ios/realm.podspec +++ b/packages/realm/ios/realm.podspec @@ -19,7 +19,7 @@ puts "bundleId is #{bundleId}" Pod::Spec.new do |s| s.name = 'realm' - s.version = '2.3.0' + s.version = '3.0.0' s.summary = 'The official Realm SDK for Flutter' s.description = <<-DESC Realm is a mobile database - an alternative to SQLite and key-value stores. diff --git a/packages/realm/macos/realm.podspec b/packages/realm/macos/realm.podspec index 53b968505..bfb61b9db 100644 --- a/packages/realm/macos/realm.podspec +++ b/packages/realm/macos/realm.podspec @@ -36,7 +36,7 @@ puts "bundleId is #{bundleId}" Pod::Spec.new do |s| s.name = 'realm' - s.version = '2.3.0' + s.version = '3.0.0' s.summary = 'The official Realm SDK for Flutter' s.description = <<-DESC Realm is a mobile database - an alternative to SQLite and key-value stores. diff --git a/packages/realm/pubspec.yaml b/packages/realm/pubspec.yaml index ca476a6e2..0b2264332 100644 --- a/packages/realm/pubspec.yaml +++ b/packages/realm/pubspec.yaml @@ -1,6 +1,6 @@ name: realm description: The official Realm SDK for Flutter. Realm is a mobile database - an alternative to SQLite and key-value stores. -version: 2.3.0 +version: 3.0.0 homepage: https://www.realm.io repository: https://github.com/realm/realm-dart @@ -13,7 +13,7 @@ environment: dependencies: flutter: sdk: flutter - realm_dart: ^2.3.0 + realm_dart: ^3.0.0 flutter: plugin: diff --git a/packages/realm_common/pubspec.yaml b/packages/realm_common/pubspec.yaml index b9d7cfcca..e97f238ed 100644 --- a/packages/realm_common/pubspec.yaml +++ b/packages/realm_common/pubspec.yaml @@ -3,7 +3,7 @@ description: >- Hosts the common code shared between realm, realm_dart and realm_generator packages. This package is part of the official Realm Flutter and Realm Dart SDKs. -version: 2.3.0 +version: 3.0.0 homepage: https://www.realm.io repository: https://github.com/realm/realm-dart diff --git a/packages/realm_dart/lib/src/cli/metrics/metrics_command.dart b/packages/realm_dart/lib/src/cli/metrics/metrics_command.dart index 2fb0f1f75..b18b582fc 100644 --- a/packages/realm_dart/lib/src/cli/metrics/metrics_command.dart +++ b/packages/realm_dart/lib/src/cli/metrics/metrics_command.dart @@ -18,7 +18,7 @@ import 'options.dart'; import '../common/utils.dart'; // stamped into the library by the build system (see prepare-release.yml) -const realmCoreVersion = '14.7.0'; +const realmCoreVersion = '14.9.0'; class MetricsCommand extends Command { @override diff --git a/packages/realm_dart/lib/src/handles/native/realm_library.dart b/packages/realm_dart/lib/src/handles/native/realm_library.dart index a574bc298..bfc585d62 100644 --- a/packages/realm_dart/lib/src/handles/native/realm_library.dart +++ b/packages/realm_dart/lib/src/handles/native/realm_library.dart @@ -10,7 +10,7 @@ import 'realm_bindings.dart'; const bugInTheSdkMessage = "This is likely a bug in the Realm SDK - please file an issue at https://github.com/realm/realm-dart/issues"; // stamped into the library by the build system (see prepare-release.yml) -const libraryVersion = '2.3.0'; +const libraryVersion = '3.0.0'; final realmLib = () { final result = RealmLibrary(initRealm()); diff --git a/packages/realm_dart/pubspec.yaml b/packages/realm_dart/pubspec.yaml index 86e38dff5..7099fdcd4 100644 --- a/packages/realm_dart/pubspec.yaml +++ b/packages/realm_dart/pubspec.yaml @@ -1,6 +1,6 @@ name: realm_dart description: The official Realm SDK for Dart. Realm is a mobile database - an alternative to SQLite and key-value stores. -version: 2.3.0 +version: 3.0.0 homepage: https://www.realm.io repository: https://github.com/realm/realm-dart @@ -23,8 +23,8 @@ dependencies: path: ^1.0.0 pubspec_parse: ^1.0.0 pub_semver: ^2.1.0 - realm_common: ^2.3.0 - realm_generator: ^2.3.0 + realm_common: ^3.0.0 + realm_generator: ^3.0.0 tar: ^1.0.1 build_runner: ^2.1.0 http: ^1.0.0 diff --git a/packages/realm_dart/src/realm_dart.cpp b/packages/realm_dart/src/realm_dart.cpp index 5bab1f20c..c3a2599f1 100644 --- a/packages/realm_dart/src/realm_dart.cpp +++ b/packages/realm_dart/src/realm_dart.cpp @@ -80,7 +80,7 @@ RLM_API void realm_dart_invoke_unlock_callback(realm_userdata_t error, void* unl // Stamped into the library by the build system (see prepare-release.yml) // Keep this method as it is written and do not format it. // We have a github workflow that looks for and replaces this string as it is written here. -RLM_API const char* realm_dart_library_version() { return "2.3.0"; } +RLM_API const char* realm_dart_library_version() { return "3.0.0"; } //for debugging only // RLM_API void realm_dart_gc() { diff --git a/packages/realm_generator/pubspec.yaml b/packages/realm_generator/pubspec.yaml index 55c312122..01e367f66 100644 --- a/packages/realm_generator/pubspec.yaml +++ b/packages/realm_generator/pubspec.yaml @@ -3,7 +3,7 @@ description: >- Generates RealmObject classes from Realm data model classes. This package is part of the official Realm Flutter and Realm Dart SDKs. -version: 2.3.0 +version: 3.0.0 homepage: https://www.realm.io repository: https://github.com/realm/realm-dart @@ -17,7 +17,7 @@ dependencies: build_resolvers: ^2.0.9 build: ^2.0.0 dart_style: ^2.2.0 - realm_common: ^2.3.0 + realm_common: ^3.0.0 source_gen: ^1.1.0 source_span: ^1.8.0 collection: ^1.18.0 From 510a4faa739a8d9b1ce5c13bca70143f6e1f3247 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 7 Jun 2024 15:39:03 +0000 Subject: [PATCH 16/16] Add vNext Changelog header (#1717) Co-authored-by: nielsenko --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab6831cc0..c3d2a41f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ +## vNext (TBD) + +### Enhancements +* None + +### Fixed +* None + +### Compatibility +* Realm Studio: 15.0.0 or later. + +### Internal +* Using Core x.y.z. + ## 3.0.0 (2024-06-07) ### Breaking Changes