├── .bundle └── config ├── .changeset ├── README.md ├── config.json ├── funny-crabs-smoke.md └── six-moose-buy.md ├── .envrc ├── .gitattributes ├── .github ├── FUNDING.yml └── workflows │ ├── build-packages.yaml │ ├── dev-packages.yaml │ ├── release.yml │ └── test.yaml ├── .gitignore ├── .mtslconfig.json ├── .nvmrc ├── .prettierignore ├── .prettierrc ├── .ruby-version ├── .tool-versions ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── android ├── .project ├── .settings │ └── org.eclipse.buildship.core.prefs ├── CMakeLists.txt ├── build.gradle ├── cpp-adapter.cpp └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── reactnativequicksqlite │ ├── QuickSQLiteBridge.java │ ├── SequelModule.java │ └── SequelPackage.java ├── app.plugin.js ├── babel.config.js ├── cpp ├── ConnectionPool.cpp ├── ConnectionPool.h ├── ConnectionState.cpp ├── ConnectionState.h ├── JSIHelper.cpp ├── JSIHelper.h ├── bindings.cpp ├── bindings.h ├── fileUtils.cpp ├── fileUtils.h ├── logs.h ├── macros.h ├── sqlbatchexecutor.cpp ├── sqlbatchexecutor.h ├── sqlite3.c ├── sqlite3.h ├── sqliteBridge.cpp ├── sqliteBridge.h ├── sqliteExecute.cpp └── sqliteExecute.h ├── header2.png ├── ios ├── QuickSQLite.h ├── QuickSQLite.mm └── QuickSQLite.xcodeproj │ └── project.pbxproj ├── package.json ├── react-native-quick-sqlite.podspec ├── scripts ├── bootstrap.js └── dev-package-pre-version.js ├── sponsors └── stream.png ├── src ├── DBListenerManager.ts ├── index.ts ├── lock-hooks.ts ├── setup-open.ts ├── table-updates.ts ├── type-orm.ts ├── types.ts ├── utils.ts ├── utils │ └── BaseObserver.ts └── withUseFrameworks.ts ├── tests ├── .gitignore ├── App.tsx ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ ├── debug.keystore │ │ ├── proguard-rules.pro │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ └── com │ │ │ │ └── reactnativequicksqlite │ │ │ │ └── tests │ │ │ │ ├── MainActivity.kt │ │ │ │ └── MainApplication.kt │ │ │ └── res │ │ │ ├── drawable-hdpi │ │ │ └── splashscreen_logo.png │ │ │ ├── drawable-mdpi │ │ │ └── splashscreen_logo.png │ │ │ ├── drawable-xhdpi │ │ │ └── splashscreen_logo.png │ │ │ ├── drawable-xxhdpi │ │ │ └── splashscreen_logo.png │ │ │ ├── drawable-xxxhdpi │ │ │ └── splashscreen_logo.png │ │ │ ├── drawable │ │ │ ├── ic_launcher_background.xml │ │ │ └── rn_edit_text_material.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.webp │ │ │ ├── ic_launcher_foreground.webp │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.webp │ │ │ ├── ic_launcher_foreground.webp │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.webp │ │ │ ├── ic_launcher_foreground.webp │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.webp │ │ │ ├── ic_launcher_foreground.webp │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.webp │ │ │ ├── ic_launcher_foreground.webp │ │ │ └── ic_launcher_round.webp │ │ │ ├── values-night │ │ │ └── colors.xml │ │ │ └── values │ │ │ ├── colors.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ └── settings.gradle ├── app.json ├── assets │ ├── adaptive-icon.png │ ├── favicon.png │ ├── icon.png │ └── splash.png ├── babel.config.js ├── index.js ├── ios │ ├── .gitignore │ ├── .xcode.env │ ├── Podfile │ ├── Podfile.lock │ ├── Podfile.properties.json │ ├── tests.xcodeproj │ │ ├── project.pbxproj │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── tests.xcscheme │ ├── tests.xcworkspace │ │ └── contents.xcworkspacedata │ └── tests │ │ ├── AppDelegate.h │ │ ├── AppDelegate.mm │ │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── App-Icon-1024x1024@1x.png │ │ │ └── Contents.json │ │ ├── Contents.json │ │ ├── SplashScreenBackground.colorset │ │ │ └── Contents.json │ │ └── SplashScreenLogo.imageset │ │ │ ├── Contents.json │ │ │ ├── image.png │ │ │ ├── image@2x.png │ │ │ └── image@3x.png │ │ ├── Info.plist │ │ ├── PrivacyInfo.xcprivacy │ │ ├── SplashScreen.storyboard │ │ ├── Supporting │ │ └── Expo.plist │ │ ├── main.m │ │ ├── noop-file.swift │ │ ├── tests-Bridging-Header.h │ │ └── tests.entitlements ├── metro.config.js ├── package.json ├── scripts │ └── test.js ├── tests │ ├── index.ts │ ├── mocha │ │ ├── MochaRNAdapter.ts │ │ └── MochaSetup.ts │ ├── sqlite │ │ ├── rawQueries.spec.ts │ │ └── utils.ts │ └── type-orm │ │ ├── Database.ts │ │ ├── models │ │ ├── Book.ts │ │ └── User.ts │ │ └── typeorm.spec.ts ├── tsconfig.json └── yarn.lock ├── tsconfig.json └── yarn.lock /.bundle/config: -------------------------------------------------------------------------------- 1 | BUNDLE_PATH: "vendor/bundle" 2 | BUNDLE_FORCE_RUBY_PLATFORM: 1 -------------------------------------------------------------------------------- /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.3.1/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": false, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "restricted", 8 | "baseBranch": "main", 9 | "updateInternalDependencies": "patch", 10 | "ignore": [] 11 | } 12 | -------------------------------------------------------------------------------- /.changeset/funny-crabs-smoke.md: -------------------------------------------------------------------------------- 1 | --- 2 | "@journeyapps/react-native-quick-sqlite": patch 3 | --- 4 | 5 | Added expo config plugin, that automatically applies podfile changes needed for use_frameworks when `staticLibrary` option is specified for the plugin. 6 | -------------------------------------------------------------------------------- /.changeset/six-moose-buy.md: -------------------------------------------------------------------------------- 1 | --- 2 | "@journeyapps/react-native-quick-sqlite": patch 3 | --- 4 | 5 | Fix crash when binding or reading blobs. 6 | -------------------------------------------------------------------------------- /.envrc: -------------------------------------------------------------------------------- 1 | layout node 2 | use node 3 | [ -f .env ] && dotenv -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj -text 2 | # specific for windows script files 3 | *.bat text eol=crlf -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [mrousavy] 2 | -------------------------------------------------------------------------------- /.github/workflows/build-packages.yaml: -------------------------------------------------------------------------------- 1 | # Ensures packages build correctly 2 | name: Build Packages 3 | 4 | on: 5 | push: 6 | 7 | jobs: 8 | build: 9 | name: Build Packages 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | with: 14 | persist-credentials: false 15 | 16 | - name: Setup NodeJS 17 | uses: actions/setup-node@v4 18 | with: 19 | node-version: 18 20 | cache: 'yarn' 21 | 22 | - name: Setup Yarn 23 | run: | 24 | npm install -g yarn 25 | echo "Yarn version: $(yarn -v)" 26 | 27 | # Build is triggered in prepare script 28 | - name: Install Dependencies 29 | run: yarn install --frozen-lockfile 30 | -------------------------------------------------------------------------------- /.github/workflows/dev-packages.yaml: -------------------------------------------------------------------------------- 1 | # Action to publish packages under the `next` tag for testing 2 | # Packages are versioned as `0.0.0-{tag}-DATETIMESTAMP` 3 | name: Packages Deploy 4 | 5 | on: workflow_dispatch 6 | 7 | jobs: 8 | publish: 9 | name: Publish Dev Packages 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | with: 14 | persist-credentials: false 15 | 16 | - name: Setup NodeJS 17 | uses: actions/setup-node@v4 18 | with: 19 | node-version: 18 20 | 21 | - name: Setup Yarn 22 | run: | 23 | npm install -g yarn 24 | echo "Yarn version: $(yarn -v)" 25 | echo "//registry.npmjs.org/:_authToken=${{secrets.NPM_TOKEN}}" >> ~/.npmrc 26 | 27 | # This also builds the package as part of the post install 28 | - name: Install Dependencies 29 | run: yarn install --frozen-lockfile 30 | 31 | - name: Development Version 32 | run: | 33 | node ./scripts/dev-package-pre-version.js 34 | yarn changeset version --no-git-tag --snapshot dev 35 | yarn changeset publish --tag dev 36 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | concurrency: ${{ github.workflow }}-${{ github.ref }} 9 | 10 | jobs: 11 | release: 12 | name: Release 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout Repo 16 | uses: actions/checkout@v4 17 | 18 | - name: Setup Node.js 18 19 | uses: actions/setup-node@v4 20 | with: 21 | node-version: 18 22 | 23 | # This also builds the package as part of the post install 24 | - name: Install Dependencies 25 | run: yarn 26 | 27 | - name: Create Release Pull Request or Publish to npm 28 | id: changesets 29 | uses: changesets/action@v1 30 | with: 31 | # This expects you to have a script called release which does a build for your packages and calls changeset publish 32 | publish: yarn release 33 | env: 34 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 35 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 36 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | 6 | jobs: 7 | test: 8 | name: Test Android 9 | runs-on: ubuntu-xl 10 | env: 11 | AVD_NAME: ubuntu-avd-x86_64-31 12 | steps: 13 | - uses: actions/checkout@v4 14 | with: 15 | persist-credentials: false 16 | 17 | - name: Enable KVM group perms 18 | run: | 19 | echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules 20 | sudo udevadm control --reload-rules 21 | sudo udevadm trigger --name-match=kvm 22 | 23 | - name: Gradle Cache 24 | uses: gradle/gradle-build-action@v2 25 | 26 | - name: AVD Cache 27 | uses: actions/cache@v3 28 | id: avd-cache 29 | with: 30 | path: | 31 | ~/.android/avd/* 32 | ~/.android/adb* 33 | key: avd-31 34 | 35 | - name: Setup NodeJS 36 | uses: actions/setup-node@v4 37 | with: 38 | node-version: 18 39 | cache: 'yarn' 40 | 41 | - name: Set up JDK 17 42 | uses: actions/setup-java@v3 43 | with: 44 | java-version: 17 45 | distribution: 'adopt' 46 | cache: 'gradle' 47 | 48 | - name: Setup Yarn 49 | run: | 50 | npm install -g yarn 51 | echo "Yarn version: $(yarn -v)" 52 | 53 | - name: Install Dependencies 54 | run: yarn install --frozen-lockfile 55 | 56 | - name: Configure Test App 57 | run: | 58 | cd tests 59 | yarn install --frozen-lockfile 60 | 61 | - name: Initialize Android Folder 62 | run: mkdir -p ~/.android/avd 63 | 64 | - name: create AVD and generate snapshot for caching 65 | if: steps.avd-cache.outputs.cache-hit != 'true' 66 | uses: reactivecircus/android-emulator-runner@v2.28.0 67 | with: 68 | api-level: 31 69 | force-avd-creation: false 70 | target: google_apis 71 | arch: x86_64 72 | disable-animations: false 73 | avd-name: $AVD_NAME 74 | emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none 75 | script: echo "Generated AVD snapshot for caching." 76 | 77 | - name: Run connected Android tests 78 | uses: ReactiveCircus/android-emulator-runner@v2.28.0 79 | with: 80 | api-level: 31 81 | target: google_apis 82 | arch: x86_64 83 | avd-name: $AVD_NAME 84 | script: cd tests && yarn test-android --avdName $AVD_NAME 85 | force-avd-creation: false 86 | emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none 87 | disable-animations: true 88 | 89 | test-ios: 90 | name: Test iOS (Only build) 91 | runs-on: macOS-13 92 | steps: 93 | - uses: actions/checkout@v4 94 | with: 95 | persist-credentials: false 96 | 97 | - uses: irgaly/xcode-cache@v1 98 | with: 99 | key: xcode-cache-deriveddata-${{ github.workflow }}-${{ github.sha }} 100 | restore-keys: xcode-cache-deriveddata-${{ github.workflow }}- 101 | 102 | - name: CocoaPods Cache 103 | uses: actions/cache@v3 104 | id: cocoapods-cache 105 | with: 106 | path: | 107 | tests/ios/Pods/* 108 | key: ${{ runner.os }}-${{ hashFiles('tests/ios/Podfile.lock') }} 109 | 110 | - name: Setup NodeJS 111 | uses: actions/setup-node@v4 112 | with: 113 | node-version: 18 114 | cache: 'yarn' 115 | 116 | - name: Setup Yarn 117 | run: | 118 | npm install -g yarn 119 | echo "Yarn version: $(yarn -v)" 120 | 121 | - name: Install Dependencies 122 | run: yarn install --frozen-lockfile 123 | 124 | - name: Configure Test App 125 | run: | 126 | cd tests 127 | yarn install --frozen-lockfile 128 | 129 | - name: Install Cocoapods 130 | run: | 131 | cd tests/ios && pod install 132 | 133 | - name: Build iOS 134 | run: | 135 | cd tests && yarn build-ios 136 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # XDE 6 | .expo/ 7 | 8 | # VSCode 9 | .vscode/ 10 | jsconfig.json 11 | 12 | # Xcode 13 | # 14 | build/ 15 | *.pbxuser 16 | !default.pbxuser 17 | *.mode1v3 18 | !default.mode1v3 19 | *.mode2v3 20 | !default.mode2v3 21 | *.perspectivev3 22 | !default.perspectivev3 23 | xcuserdata 24 | *.xccheckout 25 | *.moved-aside 26 | DerivedData 27 | *.hmap 28 | *.ipa 29 | *.xcuserstate 30 | project.xcworkspace 31 | 32 | # Android/IJ 33 | # 34 | .idea 35 | .gradle 36 | local.properties 37 | *.iml 38 | *.hprof 39 | .cxx/ 40 | example/.cxx/ 41 | *.keystore 42 | !debug.keystore 43 | 44 | # Bundle 45 | # 46 | vendor/ 47 | 48 | # Cocoapods 49 | # 50 | example/ios/Pods 51 | example/vendor/bundle 52 | 53 | # Temporary files created by Metro to check the health of the file watcher 54 | 63 .metro-health-check* 55 | 56 | # node.js 57 | # 58 | node_modules/ 59 | npm-debug.log 60 | yarn-debug.log 61 | yarn-error.log 62 | tsconfig.tsbuildinfo 63 | 64 | # BUCK 65 | buck-out/ 66 | \.buckd/ 67 | android/app/libs 68 | android/keystores/debug.keystore 69 | android/.cxx 70 | 71 | # Expo 72 | .expo/* 73 | 74 | # generated by bob 75 | lib/ 76 | # Gradle 77 | android/gradle/ 78 | android/gradle* -------------------------------------------------------------------------------- /.mtslconfig.json: -------------------------------------------------------------------------------- 1 | { "ignore_dirs": [".git", "node_modules", "tests"] } 2 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v22.5.1 -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Ignore all node_modules 2 | **/node_modules/** 3 | 4 | # Autogenerated files 5 | **/.expo/** 6 | **/.next/** 7 | **/.idea/** 8 | **/.fleet/** 9 | 10 | # Other 11 | **/android/** 12 | **/assets/** 13 | **/bin/** 14 | **/ios/** -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "singleQuote": true, 4 | "tabWidth": 2, 5 | "jsxBracketSameLine": true, 6 | "useTabs": false, 7 | "printWidth": 120, 8 | "trailingComma": "none" 9 | } 10 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 3.3.0 2 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | nodejs 22.5.1 2 | ruby 2.7.6 3 | yarn 1.22.19 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @journeyapps/react-native-quick-sqlite 2 | 3 | ## 2.4.4 4 | 5 | ### Patch Changes 6 | 7 | - d743f0c: Update SQLite from 3.39.4 to 3.49.1 8 | 9 | ## 2.4.3 10 | 11 | ### Patch Changes 12 | 13 | - 9aec2e6: Update PowerSync core extension to version 0.3.14 14 | 15 | ## 2.4.2 16 | 17 | ### Patch Changes 18 | 19 | - 85b8906: Update core PowerSync extension to 0.3.12 20 | 21 | ## 2.4.1 22 | 23 | ### Patch Changes 24 | 25 | - 0466bb5: Update PowerSync core extension to 0.3.11 26 | 27 | ## 2.4.0 28 | 29 | ### Minor Changes 30 | 31 | - 43598db: Improved behaviour for closing a database connection. This should prevent some crash issues. 32 | 33 | ## 2.3.0 34 | 35 | ### Minor Changes 36 | 37 | - 73a0a68: powersync-sqlite-core 0.3.8: Increase column limit to 1999; improved handling of migrations on views. 38 | 39 | ## 2.2.1 40 | 41 | ### Patch Changes 42 | 43 | - 761d441: Enable FTS5 44 | 45 | ## 2.2.0 46 | 47 | ### Minor Changes 48 | 49 | - cbe61de: Upgrade minSdkVersion to 24 for Expo 52 50 | 51 | ## 2.1.2 52 | 53 | ### Patch Changes 54 | 55 | - a6e21ed: Use powersync-sqlite-core 0.3.6 to fix issue with dangling rows. 56 | 57 | ## 2.1.1 58 | 59 | ### Patch Changes 60 | 61 | - 9ac4ce7: Result object keys are no longer ordered alphabetically, but rather maintain insertion order. The behaviour now matches other libraries. 62 | 63 | ## 2.1.0 64 | 65 | ### Minor Changes 66 | 67 | - 23bcb1d: Added `refreshSchema()` to bindings. Will cause all connections to be aware of a schema change. 68 | 69 | ## 2.0.1 70 | 71 | ### Patch Changes 72 | 73 | - 63cc6b2: Fix issue where new databases would not always have journal_mode = WAL. 74 | 75 | ## 2.0.0 76 | 77 | ### Major Changes 78 | 79 | - ad95915: Use powersync-sqlite-core 0.3.0 80 | 81 | ## 1.4.0 82 | 83 | ### Minor Changes 84 | 85 | - 429361a: - Remove use of nativeCallSyncHook() for new architecture support. 86 | 87 | ## 1.3.1 88 | 89 | ### Patch Changes 90 | 91 | - b8e0524: Use install_modules_dependencies if available in newer react-native versions. 92 | 93 | ## 1.3.0 94 | 95 | ### Minor Changes 96 | 97 | - 11fc707: Rename back to @journeyapps/react-native-quick-sqlite for now 98 | - 5f70fd2: Use powersync-sqlite-core 0.2.1 99 | 100 | ## 1.2.0 101 | 102 | ### Minor Changes 103 | 104 | - f9d83cc: Move package to @powersync/react-native-quick-sqlite 105 | 106 | ## For the following previous versions refer to `@journeyapps/react-native-quick-sqlite` 107 | 108 | ## 1.1.8 109 | 110 | ### Patch Changes 111 | 112 | - f072d10: Silencing transactions that are reporting on failed rollback exceptions when they are safe to ignore. 113 | 114 | ## 1.1.7 115 | 116 | ### Patch Changes 117 | 118 | - 421bcbd: The default minimum SDK for Expo 51 is 23, so attempting to compile with our package using 21 would result in a build error. 119 | 120 | ## 1.1.6 121 | 122 | ### Patch Changes 123 | 124 | - 634c9c2: Removed the requirement for `lodash` and `uuid` packages. 125 | 126 | ## 1.1.5 127 | 128 | ### Patch Changes 129 | 130 | - 40c6dd0: Fix race condition where table change notications would trigger before COMMIT had completed. 131 | - 2165048: Use memory temp_store 132 | 133 | ## 1.1.4 134 | 135 | ### Patch Changes 136 | 137 | - a1a7dec: Updated UUID dependency. 138 | 139 | ## 1.1.3 140 | 141 | ### Patch Changes 142 | 143 | - b12ec4d: This pull request improves the performance of releasing lock operations. Executing multiple lock operations, such as individual calls to `.execute`, should see a significant performance improvement. 144 | 145 | ## 1.1.2 146 | 147 | ### Patch Changes 148 | 149 | - 4979882: Fixed incorrect imports of `sqlite3.h` to use local version. 150 | 151 | ## 1.1.1 152 | 153 | ### Patch Changes 154 | 155 | - b1324f1: Updated PowerSync SQLite Core to ~>0.1.6. https://github.com/powersync-ja/powersync-sqlite-core/pull/3 156 | 157 | ## 1.1.0 158 | 159 | ### Minor Changes 160 | 161 | - 3bb0212: Added `registerTablesChangedHook` to DB connections which reports batched table updates once `writeTransaction`s and `writeLock`s have been committed. Maintained API compatibility with `registerUpdateHook` which reports table change events as they occur. Added listeners for when write transactions have been committed or rolled back. 162 | 163 | ## 1.0.0 164 | 165 | ### Major Changes 166 | 167 | - 7c54e8a: Version 1.0.0 release out of beta 168 | 169 | ## 0.1.1 170 | 171 | ### Patch Changes 172 | 173 | - 2802916: Fixed: Missing dependency for `uuid` and race condition where ommitting `await` on some lock/transaction operations could deadlock the application's main thread. 174 | 175 | ## 0.1.0 176 | 177 | ### Minor Changes 178 | 179 | - 21cdcf1: Bump to beta version 180 | 181 | ## 0.0.2 182 | 183 | ### Patch Changes 184 | 185 | - c17f91c: Added concurrent read/write connection support. 186 | 187 | ## 0.0.1 188 | 189 | ### Patch Changes 190 | 191 | - 90affb4: Bump version to 0.0.1 for consistency 192 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | We want this community to be friendly and respectful to each other. Please follow it in all your interactions with the project. 4 | 5 | ## Development workflow 6 | 7 | To get started with the project, run `yarn` in the root directory to install the required dependencies for each package: 8 | 9 | ```sh 10 | yarn 11 | ``` 12 | 13 | While developing, you can run the [example app](/example/) to test your changes. 14 | 15 | To start the packager: 16 | 17 | ```sh 18 | yarn example start 19 | ``` 20 | 21 | To run the example app on Android: 22 | 23 | ```sh 24 | yarn example android 25 | ``` 26 | 27 | To run the example app on iOS: 28 | 29 | ```sh 30 | yarn example ios 31 | ``` 32 | 33 | Make sure your code passes TypeScript and ESLint. Run the following to verify: 34 | 35 | ```sh 36 | yarn typescript 37 | yarn lint 38 | ``` 39 | 40 | To fix formatting errors, run the following: 41 | 42 | ```sh 43 | yarn lint --fix 44 | ``` 45 | 46 | Remember to add tests for your change if possible. Run the unit tests by: 47 | 48 | ```sh 49 | yarn test 50 | ``` 51 | 52 | To edit the Objective-C files, open `example/ios/SequelExample.xcworkspace` in XCode and find the source files at `Pods > Development Pods > react-native-quick-sqlite`. 53 | 54 | To edit the Kotlin files, open `example/android` in Android studio and find the source files at `reactnativequicksqlite` under `Android`. 55 | 56 | ### Commit message convention 57 | 58 | We follow the [conventional commits specification](https://www.conventionalcommits.org/en) for our commit messages: 59 | 60 | - `fix`: bug fixes, e.g. fix crash due to deprecated method. 61 | - `feat`: new features, e.g. add new method to the module. 62 | - `refactor`: code refactor, e.g. migrate from class components to hooks. 63 | - `docs`: changes into documentation, e.g. add usage example for the module.. 64 | - `test`: adding or updating tests, e.g. add integration tests using detox. 65 | - `chore`: tooling changes, e.g. change CI config. 66 | 67 | Our pre-commit hooks verify that your commit message matches this format when committing. 68 | 69 | ### Linting and tests 70 | 71 | [ESLint](https://eslint.org/), [Prettier](https://prettier.io/), [TypeScript](https://www.typescriptlang.org/) 72 | 73 | We use [TypeScript](https://www.typescriptlang.org/) for type checking, [ESLint](https://eslint.org/) with [Prettier](https://prettier.io/) for linting and formatting the code, and [Jest](https://jestjs.io/) for testing. 74 | 75 | Our pre-commit hooks verify that the linter and tests pass when committing. 76 | 77 | ### Scripts 78 | 79 | The `package.json` file contains various scripts for common tasks: 80 | 81 | - `yarn bootstrap`: setup project by installing all dependencies and pods. 82 | - `yarn typescript`: type-check files with TypeScript. 83 | - `yarn lint`: lint files with ESLint. 84 | - `yarn test`: run unit tests with Jest. 85 | - `yarn example start`: start the Metro server for the example app. 86 | - `yarn example android`: run the example app on Android. 87 | - `yarn example ios`: run the example app on iOS. 88 | 89 | ### Sending a pull request 90 | 91 | > **Working on your first pull request?** You can learn how from this _free_ series: [How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github). 92 | 93 | When you're sending a pull request: 94 | 95 | - Prefer small pull requests focused on one change. 96 | - Verify that linters and tests are passing. 97 | - Review the documentation to make sure it looks good. 98 | - Follow the pull request template when opening a pull request. 99 | - For pull requests that change the API or implementation, discuss with maintainers first by opening an issue. 100 | 101 | ## Code of Conduct 102 | 103 | ### Our Pledge 104 | 105 | We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. 106 | 107 | We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. 108 | 109 | ### Our Standards 110 | 111 | Examples of behavior that contributes to a positive environment for our community include: 112 | 113 | - Demonstrating empathy and kindness toward other people 114 | - Being respectful of differing opinions, viewpoints, and experiences 115 | - Giving and gracefully accepting constructive feedback 116 | - Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience 117 | - Focusing on what is best not just for us as individuals, but for the overall community 118 | 119 | Examples of unacceptable behavior include: 120 | 121 | - The use of sexualized language or imagery, and sexual attention or 122 | advances of any kind 123 | - Trolling, insulting or derogatory comments, and personal or political attacks 124 | - Public or private harassment 125 | - Publishing others' private information, such as a physical or email 126 | address, without their explicit permission 127 | - Other conduct which could reasonably be considered inappropriate in a 128 | professional setting 129 | 130 | ### Enforcement Responsibilities 131 | 132 | Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. 133 | 134 | Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. 135 | 136 | ### Scope 137 | 138 | This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. 139 | 140 | ### Enforcement 141 | 142 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [INSERT CONTACT METHOD]. All complaints will be reviewed and investigated promptly and fairly. 143 | 144 | All community leaders are obligated to respect the privacy and security of the reporter of any incident. 145 | 146 | ### Enforcement Guidelines 147 | 148 | Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: 149 | 150 | #### 1. Correction 151 | 152 | **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. 153 | 154 | **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. 155 | 156 | #### 2. Warning 157 | 158 | **Community Impact**: A violation through a single incident or series of actions. 159 | 160 | **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. 161 | 162 | #### 3. Temporary Ban 163 | 164 | **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. 165 | 166 | **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. 167 | 168 | #### 4. Permanent Ban 169 | 170 | **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. 171 | 172 | **Consequence**: A permanent ban from any sort of public interaction within the community. 173 | 174 | ### Attribution 175 | 176 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, 177 | available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 178 | 179 | Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). 180 | 181 | [homepage]: https://www.contributor-covenant.org 182 | 183 | For answers to common questions about this code of conduct, see the FAQ at 184 | https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations. 185 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | # You may use http://rbenv.org/ or https://rvm.io/ to install and use this version 3 | ruby '3.3.0' 4 | gem 'cocoapods', '~> 1.11', '>= 1.11.3' -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (3.0.6) 5 | rexml 6 | activesupport (7.0.6) 7 | concurrent-ruby (~> 1.0, >= 1.0.2) 8 | i18n (>= 1.6, < 2) 9 | minitest (>= 5.1) 10 | tzinfo (~> 2.0) 11 | addressable (2.8.4) 12 | public_suffix (>= 2.0.2, < 6.0) 13 | algoliasearch (1.27.5) 14 | httpclient (~> 2.8, >= 2.8.3) 15 | json (>= 1.5.1) 16 | atomos (0.1.3) 17 | claide (1.1.0) 18 | cocoapods (1.12.1) 19 | addressable (~> 2.8) 20 | claide (>= 1.0.2, < 2.0) 21 | cocoapods-core (= 1.12.1) 22 | cocoapods-deintegrate (>= 1.0.3, < 2.0) 23 | cocoapods-downloader (>= 1.6.0, < 2.0) 24 | cocoapods-plugins (>= 1.0.0, < 2.0) 25 | cocoapods-search (>= 1.0.0, < 2.0) 26 | cocoapods-trunk (>= 1.6.0, < 2.0) 27 | cocoapods-try (>= 1.1.0, < 2.0) 28 | colored2 (~> 3.1) 29 | escape (~> 0.0.4) 30 | fourflusher (>= 2.3.0, < 3.0) 31 | gh_inspector (~> 1.0) 32 | molinillo (~> 0.8.0) 33 | nap (~> 1.0) 34 | ruby-macho (>= 2.3.0, < 3.0) 35 | xcodeproj (>= 1.21.0, < 2.0) 36 | cocoapods-core (1.12.1) 37 | activesupport (>= 5.0, < 8) 38 | addressable (~> 2.8) 39 | algoliasearch (~> 1.0) 40 | concurrent-ruby (~> 1.1) 41 | fuzzy_match (~> 2.0.4) 42 | nap (~> 1.0) 43 | netrc (~> 0.11) 44 | public_suffix (~> 4.0) 45 | typhoeus (~> 1.0) 46 | cocoapods-deintegrate (1.0.5) 47 | cocoapods-downloader (1.6.3) 48 | cocoapods-plugins (1.0.0) 49 | nap 50 | cocoapods-search (1.0.1) 51 | cocoapods-trunk (1.6.0) 52 | nap (>= 0.8, < 2.0) 53 | netrc (~> 0.11) 54 | cocoapods-try (1.2.0) 55 | colored2 (3.1.2) 56 | concurrent-ruby (1.2.2) 57 | escape (0.0.4) 58 | ethon (0.16.0) 59 | ffi (>= 1.15.0) 60 | ffi (1.15.5) 61 | fourflusher (2.3.1) 62 | fuzzy_match (2.0.4) 63 | gh_inspector (1.1.3) 64 | httpclient (2.8.3) 65 | i18n (1.14.1) 66 | concurrent-ruby (~> 1.0) 67 | json (2.6.3) 68 | minitest (5.18.1) 69 | molinillo (0.8.0) 70 | nanaimo (0.3.0) 71 | nap (1.1.0) 72 | netrc (0.11.0) 73 | public_suffix (4.0.7) 74 | rexml (3.2.5) 75 | ruby-macho (2.5.1) 76 | typhoeus (1.4.0) 77 | ethon (>= 0.9.0) 78 | tzinfo (2.0.6) 79 | concurrent-ruby (~> 1.0) 80 | xcodeproj (1.22.0) 81 | CFPropertyList (>= 2.3.3, < 4.0) 82 | atomos (~> 0.1.3) 83 | claide (>= 1.0.2, < 2.0) 84 | colored2 (~> 3.1) 85 | nanaimo (~> 0.3.0) 86 | rexml (~> 3.2.4) 87 | 88 | PLATFORMS 89 | ruby 90 | 91 | DEPENDENCIES 92 | cocoapods (~> 1.11, >= 1.11.3) 93 | 94 | RUBY VERSION 95 | ruby 2.7.6p219 96 | 97 | BUNDLED WITH 98 | 2.3.20 99 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2023 Oscar Franco, Journey Mobile, Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | This repository is a fork of [react-native-quick-sqlite](https://github.com/ospfranco/react-native-quick-sqlite?tab=readme-ov-file) that includes custom SQLite extensions built specifically for **PowerSync**. It has been modified to meet the needs of **PowerSync**, adding features or behaviors that are different from the original repository. 6 | 7 | It is **not** intended to be used independently, use [PowerSync React Native SDK](https://github.com/powersync-ja/powersync-js/tree/main/packages/react-native) and install this alongside it as a peer dependency. 8 | 9 | ### For Expo 10 | 11 | #### iOS with `use_frameworks!` 12 | 13 | If your iOS project uses `use_frameworks!`, add the `react-native-quick-sqlite` plugin to your **app.json** or **app.config.js** and configure the `staticLibrary` option: 14 | 15 | ```json 16 | { 17 | "expo": { 18 | "plugins": [ 19 | [ 20 | "@journeyapps/react-native-quick-sqlite", 21 | { 22 | "staticLibrary": true 23 | } 24 | ] 25 | ] 26 | } 27 | } 28 | ``` 29 | 30 | This plugin automatically configures the necessary build settings for `react-native-quick-sqlite` to work with `use_frameworks!`. 31 | -------------------------------------------------------------------------------- /android/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | journeyapps_react-native-quick-sqlite 4 | Project android_ created by Buildship. 5 | 6 | 7 | 8 | 9 | org.eclipse.buildship.core.gradleprojectbuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.buildship.core.gradleprojectnature 16 | 17 | 18 | 19 | 1695029328310 20 | 21 | 30 22 | 23 | org.eclipse.core.resources.regexFilterMatcher 24 | node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /android/.settings/org.eclipse.buildship.core.prefs: -------------------------------------------------------------------------------- 1 | arguments=--init-script /var/folders/jx/_56srfzj0s3d98w7v3tl8kvr0000gn/T/d146c9752a26f79b52047fb6dc6ed385d064e120494f96f08ca63a317c41f94c.gradle --init-script /var/folders/jx/_56srfzj0s3d98w7v3tl8kvr0000gn/T/52cde0cfcf3e28b8b7510e992210d9614505e0911af0c190bd590d7158574963.gradle 2 | auto.sync=false 3 | build.scans.enabled=false 4 | connection.gradle.distribution=GRADLE_DISTRIBUTION(VERSION(7.4.2)) 5 | connection.project.dir= 6 | eclipse.preferences.version=1 7 | gradle.user.home= 8 | java.home=/usr/local/Cellar/openjdk@11/11.0.14.1/libexec/openjdk.jdk/Contents/Home 9 | jvm.arguments= 10 | offline.mode=false 11 | override.workspace.settings=true 12 | show.console.view=true 13 | show.executions.view=true 14 | -------------------------------------------------------------------------------- /android/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(ReactNativeQuickSQLite) 2 | cmake_minimum_required(VERSION 3.9.0) 3 | 4 | set (PACKAGE_NAME "react-native-quick-sqlite") 5 | set (CMAKE_VERBOSE_MAKEFILE ON) 6 | set (CMAKE_CXX_STANDARD 17) 7 | set (BUILD_DIR ${CMAKE_SOURCE_DIR}/build) 8 | 9 | include_directories( 10 | ../cpp 11 | ) 12 | 13 | add_definitions( 14 | -DSQLITE_TEMP_STORE=2 15 | -DSQLITE_ENABLE_FTS5=1 16 | ${SQLITE_FLAGS} 17 | ) 18 | 19 | add_library( 20 | ${PACKAGE_NAME} 21 | SHARED 22 | ../cpp/sqliteBridge.cpp 23 | ../cpp/sqliteBridge.h 24 | ../cpp/bindings.cpp 25 | ../cpp/bindings.h 26 | ../cpp/sqlite3.h 27 | ../cpp/sqlite3.c 28 | ../cpp/JSIHelper.h 29 | ../cpp/JSIHelper.cpp 30 | ../cpp/fileUtils.h 31 | ../cpp/fileUtils.cpp 32 | ../cpp/sqliteExecute.h 33 | ../cpp/sqliteExecute.cpp 34 | ../cpp/sqlbatchexecutor.h 35 | ../cpp/sqlbatchexecutor.cpp 36 | ../cpp/macros.h 37 | ../cpp/ConnectionPool.cpp 38 | ../cpp/ConnectionPool.h 39 | ../cpp/ConnectionState.cpp 40 | ../cpp/ConnectionState.h 41 | cpp-adapter.cpp 42 | ) 43 | 44 | set_target_properties( 45 | ${PACKAGE_NAME} PROPERTIES 46 | CXX_STANDARD 17 47 | CXX_EXTENSIONS OFF 48 | POSITION_INDEPENDENT_CODE ON 49 | ) 50 | 51 | find_package(ReactAndroid REQUIRED CONFIG) 52 | find_package(fbjni REQUIRED CONFIG) 53 | find_package(powersync_sqlite_core REQUIRED CONFIG) 54 | 55 | find_library(LOG_LIB log) 56 | 57 | target_link_libraries( 58 | ${PACKAGE_NAME} 59 | ${LOG_LIB} 60 | fbjni::fbjni 61 | ReactAndroid::jsi 62 | android 63 | powersync_sqlite_core::powersync 64 | ) 65 | 66 | # This if-then-else can be removed once this library does not support react-native versions below 0.76 67 | # Ideally we would just depend on `REACTNATIVE_MERGED_SO` 68 | # See https://github.com/react-native-community/discussions-and-proposals/discussions/816 69 | if(REACTNATIVE_MERGED_SO OR ReactAndroid_VERSION_MINOR GREATER_EQUAL 76) 70 | target_link_libraries( 71 | ${PACKAGE_NAME} 72 | ReactAndroid::reactnative 73 | ) 74 | else() 75 | if(${USE_HERMES}) 76 | set(JSEXECUTOR_LIB ReactAndroid::hermes_executor) 77 | else() 78 | set(JSEXECUTOR_LIB ReactAndroid::jscexecutor) 79 | endif() 80 | 81 | target_link_libraries( 82 | ${PACKAGE_NAME} 83 | ReactAndroid::turbomodulejsijni 84 | ReactAndroid::react_nativemodule_core 85 | ${JSEXECUTOR_LIB} 86 | ) 87 | endif() 88 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | import java.nio.file.Paths 2 | 3 | buildscript { 4 | repositories { 5 | maven { 6 | url "https://plugins.gradle.org/m2/" 7 | } 8 | mavenCentral() 9 | google() 10 | } 11 | 12 | dependencies { 13 | classpath("com.android.tools.build:gradle:7.2.2") 14 | } 15 | } 16 | 17 | def resolveBuildType() { 18 | Gradle gradle = getGradle() 19 | String tskReqStr = gradle.getStartParameter().getTaskRequests()['args'].toString() 20 | 21 | return tskReqStr.contains('Release') ? 'release' : 'debug' 22 | } 23 | 24 | def isNewArchitectureEnabled() { 25 | // - Set `newArchEnabled` to true inside the `gradle.properties` file 26 | return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true" 27 | } 28 | 29 | def SQLITE_FLAGS = rootProject.properties['quickSqliteFlags'] 30 | 31 | apply plugin: 'com.android.library' 32 | 33 | def safeExtGet(prop, fallback) { 34 | rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback 35 | } 36 | 37 | def reactNativeArchitectures() { 38 | def value = project.getProperties().get("reactNativeArchitectures") 39 | return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"] 40 | } 41 | 42 | def USE_HERMES = rootProject.ext.hermesEnabled 43 | 44 | repositories { 45 | mavenCentral() 46 | } 47 | 48 | android { 49 | 50 | namespace "com.reactnativequicksqlite" 51 | compileSdkVersion safeExtGet("compileSdkVersion", 28) 52 | 53 | // Used to override the NDK path/version on internal CI or by allowing 54 | // users to customize the NDK path/version from their root project (e.g. for M1 support) 55 | if (rootProject.hasProperty("ndkPath")) { 56 | ndkPath rootProject.ext.ndkPath 57 | } 58 | if (rootProject.hasProperty("ndkVersion")) { 59 | ndkVersion rootProject.ext.ndkVersion 60 | } 61 | 62 | buildFeatures { 63 | prefab true 64 | } 65 | 66 | sourceSets.main { 67 | jniLibs.srcDirs = ['src/main/jniLibs'] 68 | } 69 | 70 | defaultConfig { 71 | minSdkVersion 24 72 | targetSdkVersion safeExtGet('targetSdkVersion', 28) 73 | versionCode 1 74 | versionName "1.0" 75 | 76 | externalNativeBuild { 77 | cmake { 78 | cppFlags "-O2", "-fexceptions", "-frtti", "-std=c++1y", "-DONANDROID" 79 | abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a' 80 | arguments '-DANDROID_STL=c++_shared', 81 | "-DSQLITE_FLAGS='${SQLITE_FLAGS ? SQLITE_FLAGS : ''}'", 82 | "-DUSE_HERMES=${USE_HERMES}" 83 | abiFilters (*reactNativeArchitectures()) 84 | } 85 | } 86 | 87 | packagingOptions { 88 | doNotStrip resolveBuildType() == 'debug' ? "**/**/*.so" : '' 89 | excludes = [ 90 | "META-INF", 91 | "META-INF/**", 92 | "**/libjsi.so", 93 | "**/libreact_nativemodule_core.so", 94 | "**/libturbomodulejsijni.so", 95 | "**/libreactnative.so", 96 | "**/libc++_shared.so", 97 | "**/libfbjni.so" 98 | ] 99 | } 100 | 101 | } 102 | 103 | compileOptions { 104 | sourceCompatibility JavaVersion.VERSION_1_8 105 | targetCompatibility JavaVersion.VERSION_1_8 106 | } 107 | 108 | externalNativeBuild { 109 | cmake { 110 | path "CMakeLists.txt" 111 | } 112 | } 113 | } 114 | 115 | dependencies { 116 | implementation 'co.powersync:powersync-sqlite-core:0.3.14' 117 | //noinspection GradleDynamicVersion 118 | implementation 'com.facebook.react:react-android:+' 119 | } 120 | 121 | // Resolves "LOCAL_SRC_FILES points to a missing file, Check that libfb.so exists or that its path is correct". 122 | tasks.whenTaskAdded { task -> 123 | if (task.name.contains("configureCMakeDebug")) { 124 | rootProject.getTasksByName("packageReactNdkDebugLibs", true).forEach { 125 | task.dependsOn(it) 126 | } 127 | } 128 | // We want to add a dependency for both configureCMakeRelease and configureCMakeRelWithDebInfo 129 | if (task.name.contains("configureCMakeRel")) { 130 | rootProject.getTasksByName("packageReactNdkReleaseLibs", true).forEach { 131 | task.dependsOn(it) 132 | } 133 | } 134 | } -------------------------------------------------------------------------------- /android/cpp-adapter.cpp: -------------------------------------------------------------------------------- 1 | #include "bindings.h" 2 | #include "logs.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | struct QuickSQLiteBridge : jni::JavaClass { 10 | static constexpr auto kJavaDescriptor = 11 | "Lcom/reactnativequicksqlite/QuickSQLiteBridge;"; 12 | 13 | static void registerNatives() { 14 | javaClassStatic()->registerNatives( 15 | {// initialization for JSI 16 | makeNativeMethod("installNativeJsi", 17 | QuickSQLiteBridge::installNativeJsi), 18 | {"clearStateNativeJsi", "()V", (void*)&QuickSQLiteBridge::clearStateNativeJsi} 19 | }); 20 | } 21 | 22 | private: 23 | static void installNativeJsi( 24 | jni::alias_ref thiz, jlong jsiRuntimePtr, 25 | jni::alias_ref jsCallInvokerHolder, 26 | jni::alias_ref docPath) { 27 | auto jsiRuntime = reinterpret_cast(jsiRuntimePtr); 28 | auto jsCallInvoker = jsCallInvokerHolder->cthis()->getCallInvoker(); 29 | std::string docPathString = docPath->toStdString(); 30 | 31 | osp::install(*jsiRuntime, jsCallInvoker, docPathString.c_str()); 32 | } 33 | 34 | static void clearStateNativeJsi() { 35 | osp::clearState(); 36 | } 37 | }; 38 | 39 | JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *) { 40 | return jni::initialize(vm, [] { QuickSQLiteBridge::registerNatives(); }); 41 | } 42 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /android/src/main/java/com/reactnativequicksqlite/QuickSQLiteBridge.java: -------------------------------------------------------------------------------- 1 | package com.reactnativequicksqlite; 2 | 3 | import android.util.Log; 4 | 5 | import com.facebook.react.bridge.ReactApplicationContext; 6 | import com.facebook.react.bridge.ReactContext; 7 | import com.facebook.react.turbomodule.core.CallInvokerHolderImpl; 8 | 9 | public class QuickSQLiteBridge { 10 | private native void installNativeJsi(long jsContextNativePointer, CallInvokerHolderImpl jsCallInvokerHolder, String docPath); 11 | private native void clearStateNativeJsi(); 12 | public static final QuickSQLiteBridge instance = new QuickSQLiteBridge(); 13 | 14 | public void install(ReactContext context) { 15 | long jsContextPointer = context.getJavaScriptContextHolder().get(); 16 | CallInvokerHolderImpl jsCallInvokerHolder = (CallInvokerHolderImpl)context.getCatalystInstance().getJSCallInvokerHolder(); 17 | final String path = context.getFilesDir().getAbsolutePath(); 18 | 19 | installNativeJsi( 20 | jsContextPointer, 21 | jsCallInvokerHolder, 22 | path 23 | ); 24 | } 25 | 26 | public void clearState() { 27 | clearStateNativeJsi(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /android/src/main/java/com/reactnativequicksqlite/SequelModule.java: -------------------------------------------------------------------------------- 1 | package com.reactnativequicksqlite; 2 | 3 | import androidx.annotation.NonNull; 4 | import android.util.Log; 5 | 6 | import com.facebook.jni.HybridData; 7 | import com.facebook.jni.annotations.DoNotStrip; 8 | import com.facebook.react.bridge.ReactApplicationContext; 9 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 10 | import com.facebook.react.bridge.ReactMethod; 11 | import com.facebook.react.turbomodule.core.CallInvokerHolderImpl; 12 | 13 | class SequelModule extends ReactContextBaseJavaModule { 14 | public static final String NAME = "QuickSQLite"; 15 | 16 | public SequelModule(ReactApplicationContext context) { 17 | super(context); 18 | } 19 | 20 | @NonNull 21 | @Override 22 | public String getName() { 23 | return NAME; 24 | } 25 | 26 | @ReactMethod(isBlockingSynchronousMethod = true) 27 | public boolean install() { 28 | try { 29 | System.loadLibrary("react-native-quick-sqlite"); 30 | QuickSQLiteBridge.instance.install(getReactApplicationContext()); 31 | return true; 32 | } catch (Exception exception) { 33 | Log.e(NAME, "Failed to install JSI Bindings!", exception); 34 | return false; 35 | } 36 | } 37 | 38 | @Override 39 | public void onCatalystInstanceDestroy() { 40 | try { 41 | QuickSQLiteBridge.instance.clearState(); 42 | } catch (Exception exception) { 43 | Log.e(NAME, "Failed to clear state!", exception); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /android/src/main/java/com/reactnativequicksqlite/SequelPackage.java: -------------------------------------------------------------------------------- 1 | package com.reactnativequicksqlite; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.facebook.react.ReactPackage; 6 | import com.facebook.react.bridge.NativeModule; 7 | import com.facebook.react.bridge.ReactApplicationContext; 8 | import com.facebook.react.uimanager.ViewManager; 9 | 10 | import java.util.Arrays; 11 | import java.util.Collections; 12 | import java.util.List; 13 | 14 | 15 | public class SequelPackage implements ReactPackage { 16 | @NonNull 17 | @Override 18 | public List createNativeModules(@NonNull ReactApplicationContext reactContext) { 19 | return Collections.singletonList(new SequelModule(reactContext)); 20 | } 21 | 22 | @NonNull 23 | @Override 24 | public List createViewManagers(@NonNull ReactApplicationContext reactContext) { 25 | return Collections.emptyList(); 26 | } 27 | } -------------------------------------------------------------------------------- /app.plugin.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/commonjs/withUseFrameworks'); 2 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['module:metro-react-native-babel-preset'], 3 | plugins: [ 4 | 'babel-plugin-transform-typescript-metadata', 5 | ['@babel/plugin-proposal-decorators', { legacy: true }], 6 | ['@babel/plugin-proposal-class-properties', { loose: true }] 7 | ] 8 | }; 9 | -------------------------------------------------------------------------------- /cpp/ConnectionPool.h: -------------------------------------------------------------------------------- 1 | #include "ConnectionState.h" 2 | #include "JSIHelper.h" 3 | #include "sqlite3.h" 4 | #include 5 | #include 6 | #include 7 | 8 | #ifndef ConnectionPool_h 9 | #define ConnectionPool_h 10 | 11 | enum TransactionEvent { COMMIT, ROLLBACK }; 12 | 13 | struct TransactionCallbackPayload { 14 | std::string *dbName; 15 | TransactionEvent event; 16 | }; 17 | 18 | // The number of concurrent read connections to the database. 19 | /** 20 | * Concurrent connection pool class. 21 | * 22 | * This allows for multiple (currently READ_CONNECTIONS) read connections and a 23 | * single write connection to operate concurrently. 24 | * 25 | * The SQLite database connections are opened in WAL mode, allowing for 26 | * concurrent reads and writes. 27 | * 28 | * This class acts as a simple state manager allowing requests for read and 29 | * write connections. The class will queue requests for connections and will 30 | * notify them they become available. 31 | * 32 | * Operations requesting locks here are synchronous and should be executed on a 33 | * single thread, however once a lock is active the connections can be used in a 34 | * thread pool for async statement executions. 35 | * 36 | * Synchronization, callback queueing and executions are managed by the 37 | * JavaScript portion of the library. 38 | * 39 | * The general flow is: 40 | * + JavaScript requests read/write lock on a connection via 41 | * readLock/writeLock. JS provides a unique context ID for the lock 42 | * ------> If a connection is available: The SQLite connection is locked to the 43 | * ....... connection lock context ID provided by the requestor. The JavaScript 44 | * ....... bridge is informed from the Connection pool that the requested 45 | * ....... context lock ID is now active and SQL requests can be made with the 46 | * ....... context ID (on the relevant connection). 47 | * ------> If no connections are available, the context ID is added to a FIFO 48 | * ....... queue. Once other requests are completed the JavaScript bridge is 49 | * ....... informed that the requested context lock ID is now active. 50 | * 51 | * + Any SQL requests are triggered (at the correct time) from the JavaScript 52 | * callback. Those requests are synchronized by returning JSI promises over the 53 | * bridge. 54 | * 55 | * + The JavaScript bridge makes a request to release the lock on the connection 56 | * pool once it's async callback operations are either resolved or rejected. 57 | */ 58 | class ConnectionPool { 59 | private: 60 | int maxReads; 61 | std::string dbName; 62 | ConnectionState **readConnections; 63 | ConnectionState writeConnection; 64 | 65 | std::vector readQueue; 66 | std::vector writeQueue; 67 | 68 | // Cached constant payloads for c style commit/rollback callbacks 69 | const TransactionCallbackPayload commitPayload; 70 | const TransactionCallbackPayload rollbackPayload; 71 | 72 | void (*onContextCallback)(std::string, ConnectionLockId); 73 | void (*onCommitCallback)(const TransactionCallbackPayload *); 74 | 75 | bool isConcurrencyEnabled; 76 | 77 | public: 78 | bool isClosed; 79 | 80 | ConnectionPool(std::string dbName, std::string docPath, 81 | unsigned int numReadConnections); 82 | ~ConnectionPool(); 83 | 84 | friend int onCommitIntermediate(ConnectionPool *pool); 85 | 86 | /** 87 | * Add a task to the read queue. If there are no available connections, 88 | * the task will be queued. 89 | */ 90 | void readLock(ConnectionLockId contextId); 91 | 92 | /** 93 | * Add a task to the write queue. 94 | */ 95 | void writeLock(ConnectionLockId contextId); 96 | 97 | /** 98 | * Queue in context 99 | */ 100 | SQLiteOPResult queueInContext(ConnectionLockId contextId, 101 | std::function task); 102 | 103 | /** 104 | * Callback function when a new context is available for use 105 | */ 106 | void setOnContextAvailable(void (*callback)(std::string, ConnectionLockId)); 107 | 108 | /** 109 | * Set a callback function for table updates 110 | */ 111 | void setTableUpdateHandler(void (*callback)(void *, int, const char *, 112 | const char *, sqlite3_int64)); 113 | 114 | /** 115 | * Set a callback function for transaction commits/rollbacks 116 | */ 117 | void setTransactionFinalizerHandler( 118 | void (*callback)(const TransactionCallbackPayload *)); 119 | 120 | /** 121 | * Close a context in order to progress queue 122 | */ 123 | void closeContext(ConnectionLockId contextId); 124 | 125 | // void closeContext(ConnectionLockContext *context); 126 | 127 | /** 128 | * Close all connections. 129 | */ 130 | void closeAll(); 131 | 132 | /** 133 | * Refreshes the schema for all connections. 134 | */ 135 | std::future refreshSchema(); 136 | 137 | /** 138 | * Attaches another database to all connections 139 | */ 140 | SQLiteOPResult attachDatabase(std::string const dbFileName, 141 | std::string const docPath, 142 | std::string const alias); 143 | 144 | SQLiteOPResult detachDatabase(std::string const alias); 145 | 146 | private: 147 | std::vector getAllConnections(); 148 | 149 | void activateContext(ConnectionState &state, ConnectionLockId contextId); 150 | 151 | SQLiteOPResult genericSqliteOpenDb(string const dbName, string const docPath, 152 | sqlite3 **db, int sqlOpenFlags); 153 | }; 154 | 155 | int onCommitIntermediate(ConnectionPool *pool); 156 | 157 | #endif -------------------------------------------------------------------------------- /cpp/ConnectionState.cpp: -------------------------------------------------------------------------------- 1 | #include "ConnectionState.h" 2 | #include "fileUtils.h" 3 | #include "sqlite3.h" 4 | 5 | const std::string EMPTY_LOCK_ID = ""; 6 | 7 | SQLiteOPResult genericSqliteOpenDb(string const dbName, string const docPath, 8 | sqlite3 **db, int sqlOpenFlags); 9 | 10 | ConnectionState::ConnectionState(const std::string dbName, 11 | const std::string docPath, int SQLFlags) { 12 | auto result = genericSqliteOpenDb(dbName, docPath, &connection, SQLFlags); 13 | if (result.type != SQLiteOk) { 14 | throw std::runtime_error("Failed to open SQLite database: " + result.errorMessage); 15 | } 16 | thread = std::thread(&ConnectionState::doWork, this); 17 | this->clearLock(); 18 | } 19 | 20 | ConnectionState::~ConnectionState() { 21 | if (!isClosed) { 22 | close(); 23 | } 24 | } 25 | 26 | void ConnectionState::clearLock() { 27 | waitFinished(); 28 | _currentLockId = EMPTY_LOCK_ID; 29 | } 30 | 31 | void ConnectionState::activateLock(const ConnectionLockId &lockId) { 32 | _currentLockId = lockId; 33 | } 34 | 35 | bool ConnectionState::matchesLock(const ConnectionLockId &lockId) { 36 | return _currentLockId == lockId; 37 | } 38 | 39 | bool ConnectionState::isEmptyLock() { return _currentLockId == EMPTY_LOCK_ID; } 40 | 41 | std::future ConnectionState::refreshSchema() { 42 | auto promise = std::make_shared>(); 43 | auto future = promise->get_future(); 44 | 45 | queueWork([promise](sqlite3* db) { 46 | try { 47 | int rc = sqlite3_exec(db, "PRAGMA table_info('sqlite_master')", nullptr, nullptr, nullptr); 48 | if (rc != SQLITE_OK) { 49 | throw std::runtime_error("Failed to refresh schema"); 50 | } 51 | promise->set_value(); 52 | } catch (...) { 53 | promise->set_exception(std::current_exception()); 54 | } 55 | }); 56 | 57 | return future; 58 | } 59 | 60 | void ConnectionState::close() { 61 | { 62 | std::unique_lock g(workQueueMutex); 63 | // prevent any new work from being queued 64 | isClosed = true; 65 | } 66 | 67 | // Wait for the work queue to empty 68 | waitFinished(); 69 | 70 | { 71 | // Now signal the thread to stop and notify it 72 | std::unique_lock g(workQueueMutex); 73 | threadDone = true; 74 | workQueueConditionVariable.notify_all(); 75 | } 76 | 77 | // Join the worker thread 78 | if (thread.joinable()) { 79 | thread.join(); 80 | } 81 | 82 | // Safely close the SQLite connection 83 | sqlite3_close_v2(connection); 84 | } 85 | 86 | void ConnectionState::queueWork(std::function task) { 87 | { 88 | std::unique_lock g(workQueueMutex); 89 | if (isClosed) { 90 | throw std::runtime_error("Connection is not open. Connection has been closed before queueing work."); 91 | } 92 | workQueue.push(task); 93 | } 94 | 95 | workQueueConditionVariable.notify_all(); 96 | } 97 | 98 | void ConnectionState::doWork() { 99 | // Loop while the queue is not destructing 100 | while (!threadDone) { 101 | std::function task; 102 | 103 | // Create a scope, so we don't lock the queue for longer than necessary 104 | { 105 | std::unique_lock g(workQueueMutex); 106 | workQueueConditionVariable.wait(g, [&] { 107 | // Only wake up if there are elements in the queue or the program is 108 | // shutting down 109 | return !workQueue.empty() || threadDone; 110 | }); 111 | 112 | // If we are shutting down exit without trying to process more work 113 | if (threadDone) { 114 | break; 115 | } 116 | 117 | task = workQueue.front(); 118 | workQueue.pop(); 119 | } 120 | 121 | threadBusy = true; 122 | task(connection); 123 | threadBusy = false; 124 | // Need to notify in order for waitFinished to be updated when 125 | // the queue is empty and not busy 126 | { 127 | std::unique_lock g(workQueueMutex); 128 | workQueueConditionVariable.notify_all(); 129 | } 130 | } 131 | } 132 | 133 | void ConnectionState::waitFinished() { 134 | std::unique_lock g(workQueueMutex); 135 | workQueueConditionVariable.wait( 136 | g, [&] { return workQueue.empty() && !threadBusy; }); 137 | } 138 | 139 | SQLiteOPResult genericSqliteOpenDb(string const dbName, string const docPath, 140 | sqlite3 **db, int sqlOpenFlags) { 141 | string dbPath = get_db_path(dbName, docPath); 142 | 143 | int exit = 0; 144 | exit = sqlite3_open_v2(dbPath.c_str(), db, sqlOpenFlags, nullptr); 145 | 146 | if (exit != SQLITE_OK) { 147 | return SQLiteOPResult{.type = SQLiteError, 148 | .errorMessage = sqlite3_errmsg(*db)}; 149 | } 150 | 151 | // Set journal mode directly when opening. 152 | // This may have some overhead on the main thread, 153 | // but prevents race conditions with multiple connections. 154 | if (sqlOpenFlags & SQLITE_OPEN_READONLY) { 155 | exit = sqlite3_exec(*db, "PRAGMA busy_timeout = 30000;" 156 | // Default to normal on all connections 157 | "PRAGMA synchronous = NORMAL;", 158 | nullptr, nullptr, nullptr 159 | ); 160 | } else { 161 | exit = sqlite3_exec(*db, "PRAGMA busy_timeout = 30000;" 162 | "PRAGMA journal_mode = WAL;" 163 | // 6Mb 1.5x default checkpoint size 164 | "PRAGMA journal_size_limit = 6291456;" 165 | // Default to normal on all connections 166 | "PRAGMA synchronous = NORMAL;", 167 | nullptr, nullptr, nullptr 168 | ); 169 | } 170 | if (exit != SQLITE_OK) { 171 | return SQLiteOPResult{.type = SQLiteError, 172 | .errorMessage = sqlite3_errmsg(*db)}; 173 | } 174 | 175 | return SQLiteOPResult{.type = SQLiteOk, .rowsAffected = 0}; 176 | } -------------------------------------------------------------------------------- /cpp/ConnectionState.h: -------------------------------------------------------------------------------- 1 | #include "JSIHelper.h" 2 | #include "sqlite3.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #ifndef ConnectionState_h 12 | #define ConnectionState_h 13 | 14 | typedef std::string ConnectionLockId; 15 | 16 | class ConnectionState { 17 | public: 18 | // Only to be used by connection pool under some circumstances 19 | sqlite3 *connection; 20 | 21 | private: 22 | ConnectionLockId _currentLockId; 23 | // Queue of requests waiting to be processed 24 | std::queue> workQueue; 25 | // Mutex to protect workQueue 26 | std::mutex workQueueMutex; 27 | // Store thread in order to stop it gracefully 28 | std::thread thread; 29 | // This condition variable is used for the threads to wait until there is work 30 | // to do 31 | std::condition_variable_any workQueueConditionVariable; 32 | std::atomic threadBusy{false}; 33 | std::atomic threadDone{false}; 34 | 35 | public: 36 | std::atomic isClosed{false}; 37 | 38 | ConnectionState(const std::string dbName, const std::string docPath, 39 | int SQLFlags); 40 | ~ConnectionState(); 41 | 42 | void clearLock(); 43 | void activateLock(const ConnectionLockId &lockId); 44 | bool matchesLock(const ConnectionLockId &lockId); 45 | bool isEmptyLock(); 46 | 47 | std::future refreshSchema(); 48 | void close(); 49 | void queueWork(std::function task); 50 | 51 | private: 52 | void doWork(); 53 | void waitFinished(); 54 | }; 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /cpp/JSIHelper.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // JSIHelper.cpp 3 | // react-native-quick-sqlite 4 | // 5 | // Created by Oscar on 13.03.22. 6 | // 7 | 8 | #include "JSIHelper.h" 9 | 10 | #include 11 | 12 | using namespace std; 13 | using namespace facebook; 14 | 15 | QuickValue createNullQuickValue() 16 | { 17 | return QuickValue{ 18 | .dataType = NULL_VALUE}; 19 | } 20 | 21 | QuickValue createBooleanQuickValue(bool value) 22 | { 23 | return QuickValue{ 24 | .dataType = BOOLEAN, 25 | .booleanValue = int(value)}; 26 | } 27 | 28 | QuickValue createTextQuickValue(string value) 29 | { 30 | return QuickValue{ 31 | .dataType = TEXT, 32 | .textValue = std::move(value)}; 33 | } 34 | 35 | QuickValue createIntegerQuickValue(int value) 36 | { 37 | return QuickValue{ 38 | .dataType = INTEGER, 39 | .doubleOrIntValue = static_cast(value)}; 40 | } 41 | 42 | QuickValue createIntegerQuickValue(double value) 43 | { 44 | return QuickValue{ 45 | .dataType = INTEGER, 46 | .doubleOrIntValue = value}; 47 | } 48 | 49 | QuickValue createInt64QuickValue(long long value) 50 | { 51 | return QuickValue{ 52 | .dataType = INT64, 53 | .int64Value = value}; 54 | } 55 | 56 | QuickValue createDoubleQuickValue(double value) 57 | { 58 | return QuickValue{ 59 | .dataType = DOUBLE, 60 | .doubleOrIntValue = value}; 61 | } 62 | 63 | QuickValue createArrayBufferQuickValueByCopying(const uint8_t *arrayBufferValue, size_t arrayBufferSize) 64 | { 65 | vector copy(arrayBufferValue, arrayBufferValue + arrayBufferSize); 66 | 67 | return QuickValue{ 68 | .dataType = ARRAY_BUFFER, 69 | .arrayBuffer = copy}; 70 | } 71 | 72 | void jsiQueryArgumentsToSequelParam(jsi::Runtime &rt, jsi::Value const ¶ms, vector *target) 73 | { 74 | if (params.isNull() || params.isUndefined()) 75 | { 76 | return; 77 | } 78 | 79 | jsi::Array values = params.asObject(rt).asArray(rt); 80 | 81 | for (int ii = 0; ii < values.length(rt); ii++) 82 | { 83 | 84 | jsi::Value value = values.getValueAtIndex(rt, ii); 85 | if (value.isNull() || value.isUndefined()) 86 | { 87 | target->push_back(createNullQuickValue()); 88 | } 89 | else if (value.isBool()) 90 | { 91 | target->push_back(createBooleanQuickValue(value.getBool())); 92 | } 93 | else if (value.isNumber()) 94 | { 95 | double doubleVal = value.asNumber(); 96 | int intVal = (int)doubleVal; 97 | long long longVal = (long)doubleVal; 98 | if (intVal == doubleVal) 99 | { 100 | target->push_back(createIntegerQuickValue(intVal)); 101 | } 102 | else if (longVal == doubleVal) 103 | { 104 | target->push_back(createInt64QuickValue(longVal)); 105 | } 106 | else 107 | { 108 | target->push_back(createDoubleQuickValue(doubleVal)); 109 | } 110 | } 111 | else if (value.isString()) 112 | { 113 | string strVal = value.asString(rt).utf8(rt); 114 | target->push_back(createTextQuickValue(strVal)); 115 | } 116 | else if (value.isObject()) 117 | { 118 | auto obj = value.asObject(rt); 119 | if (obj.isArrayBuffer(rt)) 120 | { 121 | auto buf = obj.getArrayBuffer(rt); 122 | target->push_back(createArrayBufferQuickValueByCopying(buf.data(rt), buf.size(rt))); 123 | } 124 | } 125 | else 126 | { 127 | target->push_back(createNullQuickValue()); 128 | } 129 | } 130 | } 131 | 132 | jsi::Value createSequelQueryExecutionResult(jsi::Runtime &rt, SQLiteOPResult status, vector> *results, vector *metadata) 133 | { 134 | if(status.type == SQLiteError) { 135 | throw std::invalid_argument(status.errorMessage); 136 | } 137 | 138 | jsi::Object res = jsi::Object(rt); 139 | 140 | res.setProperty(rt, "rowsAffected", jsi::Value(status.rowsAffected)); 141 | if (status.rowsAffected > 0 && status.insertId != 0) 142 | { 143 | res.setProperty(rt, "insertId", jsi::Value(status.insertId)); 144 | } 145 | 146 | // Converting row results into objects 147 | size_t rowCount = results->size(); 148 | jsi::Object rows = jsi::Object(rt); 149 | if (rowCount > 0 && metadata != NULL) 150 | { 151 | auto array = jsi::Array(rt, rowCount); 152 | for (int i = 0; i < rowCount; i++) 153 | { 154 | jsi::Object rowObject = jsi::Object(rt); 155 | auto row = results -> at(i); 156 | // Iterate over metadata to maintain column order 157 | for (const auto &column : *metadata) 158 | { 159 | std::string columnName = column.columnName; 160 | auto it = row.find(columnName); 161 | if (it != row.end()) 162 | { 163 | QuickValue value = it -> second; 164 | if (value.dataType == TEXT) 165 | { 166 | // using value.textValue (std::string) directly allows jsi::String to use length property of std::string (allowing strings with NULLs in them like SQLite does) 167 | rowObject.setProperty(rt, columnName.c_str(), jsi::String::createFromUtf8(rt, value.textValue)); 168 | } else if (value.dataType == INTEGER) 169 | { 170 | rowObject.setProperty(rt, columnName.c_str(), jsi::Value(value.doubleOrIntValue)); 171 | } else if (value.dataType == DOUBLE) 172 | { 173 | rowObject.setProperty(rt, columnName.c_str(), jsi::Value(value.doubleOrIntValue)); 174 | } else if (value.dataType == ARRAY_BUFFER) 175 | { 176 | jsi::Function array_buffer_ctor = rt.global().getPropertyAsFunction(rt, "ArrayBuffer"); 177 | jsi::Object o = array_buffer_ctor.callAsConstructor(rt, (int) value.arrayBuffer.size()).getObject(rt); 178 | jsi::ArrayBuffer buf = o.getArrayBuffer(rt); 179 | // It's a shame we have to copy here: see https://github.com/facebook/hermes/pull/419 and https://github.com/facebook/hermes/issues/564. 180 | memcpy(buf.data(rt), value.arrayBuffer.data(), value.arrayBuffer.size()); 181 | rowObject.setProperty(rt, columnName.c_str(), o); 182 | } else 183 | { 184 | rowObject.setProperty(rt, columnName.c_str(), jsi::Value(nullptr)); 185 | } 186 | } else 187 | { 188 | rowObject.setProperty(rt, columnName.c_str(), jsi::Value(nullptr)); 189 | } 190 | } 191 | array.setValueAtIndex(rt, i, move(rowObject)); 192 | } 193 | rows.setProperty(rt, "_array", move(array)); 194 | res.setProperty(rt, "rows", move(rows)); 195 | } 196 | 197 | if(metadata != NULL) 198 | { 199 | size_t column_count = metadata->size(); 200 | auto column_array = jsi::Array(rt, column_count); 201 | for (int i = 0; i < column_count; i++) { 202 | auto column = metadata->at(i); 203 | jsi::Object column_object = jsi::Object(rt); 204 | column_object.setProperty(rt, "columnName", jsi::String::createFromUtf8(rt, column.columnName.c_str())); 205 | column_object.setProperty(rt, "columnDeclaredType", jsi::String::createFromUtf8(rt, column.columnDeclaredType.c_str())); 206 | column_object.setProperty(rt, "columnIndex", jsi::Value(column.columnIndex)); 207 | column_array.setValueAtIndex(rt, i, move(column_object)); 208 | } 209 | res.setProperty(rt, "metadata", move(column_array)); 210 | } 211 | rows.setProperty(rt, "length", jsi::Value((int)rowCount)); 212 | 213 | return move(res); 214 | } 215 | -------------------------------------------------------------------------------- /cpp/JSIHelper.h: -------------------------------------------------------------------------------- 1 | // 2 | // JSIHelper.hpp 3 | // react-native-quick-sqlite 4 | // 5 | // Created by Oscar on 13.03.22. 6 | // 7 | 8 | #ifndef JSIHelper_h 9 | #define JSIHelper_h 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | using namespace std; 18 | using namespace facebook; 19 | 20 | /** 21 | * Enum for QuickValue to store/determine correct type for dynamic JSI values 22 | */ 23 | enum QuickDataType 24 | { 25 | NULL_VALUE, 26 | TEXT, 27 | INTEGER, 28 | INT64, 29 | DOUBLE, 30 | BOOLEAN, 31 | ARRAY_BUFFER, 32 | }; 33 | 34 | /** 35 | * Wrapper struct to allocate dynamic JSI values to static C++ primitives 36 | */ 37 | struct QuickValue 38 | { 39 | QuickDataType dataType; 40 | int booleanValue; 41 | double doubleOrIntValue; 42 | long long int64Value; 43 | string textValue; 44 | vector arrayBuffer; 45 | }; 46 | 47 | /** 48 | * Helper struct to carry SQLite results between entities 49 | */ 50 | struct QuickColumnValue 51 | { 52 | QuickValue value; 53 | string columnName; 54 | }; 55 | 56 | /** 57 | * Various structs to help with the results of the SQLite operations 58 | */ 59 | enum ResultType 60 | { 61 | SQLiteOk, 62 | SQLiteError 63 | }; 64 | 65 | struct SQLiteOPResult 66 | { 67 | ResultType type; 68 | string errorMessage; 69 | int rowsAffected; 70 | double insertId; 71 | }; 72 | 73 | struct SequelLiteralUpdateResult 74 | { 75 | ResultType type; 76 | string message; 77 | int affectedRows; 78 | }; 79 | 80 | struct SequelBatchOperationResult 81 | { 82 | ResultType type; 83 | string message; 84 | int affectedRows; 85 | int commands; 86 | }; 87 | 88 | /** 89 | * Describe column information of a resultset 90 | */ 91 | struct QuickColumnMetadata 92 | { 93 | string columnName; 94 | int columnIndex; 95 | string columnDeclaredType; 96 | }; 97 | 98 | /** 99 | * Fill the target vector with parsed parameters 100 | * */ 101 | void jsiQueryArgumentsToSequelParam(jsi::Runtime &rt, jsi::Value const &args, vector *target); 102 | 103 | QuickValue createNullQuickValue(); 104 | QuickValue createBooleanQuickValue(bool value); 105 | QuickValue createTextQuickValue(string value); 106 | QuickValue createIntegerQuickValue(int value); 107 | QuickValue createIntegerQuickValue(double value); 108 | QuickValue createInt64QuickValue(long long value); 109 | QuickValue createDoubleQuickValue(double value); 110 | QuickValue createArrayBufferQuickValueByCopying(const uint8_t *arrayBufferValue, size_t arrayBufferSize); 111 | jsi::Value createSequelQueryExecutionResult(jsi::Runtime &rt, SQLiteOPResult status, vector> *results, vector *metadata); 112 | 113 | #endif /* JSIHelper_h */ 114 | -------------------------------------------------------------------------------- /cpp/bindings.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace facebook; 6 | 7 | namespace osp { 8 | void install(jsi::Runtime &rt, 9 | std::shared_ptr jsCallInvoker, 10 | const char *docPath); 11 | void clearState(); 12 | } // namespace osp 13 | -------------------------------------------------------------------------------- /cpp/fileUtils.cpp: -------------------------------------------------------------------------------- 1 | #include "fileUtils.h" 2 | #include "logs.h" 3 | #include "sqlite3.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | bool folder_exists(const std::string &foldername) { 12 | struct stat buffer; 13 | return (stat(foldername.c_str(), &buffer) == 0); 14 | } 15 | 16 | /** 17 | * Portable wrapper for mkdir. Internally used by mkdir() 18 | * @param[in] path the full path of the directory to create. 19 | * @return zero on success, otherwise -1. 20 | */ 21 | int _mkdir(const char *path) { 22 | #if _POSIX_C_SOURCE 23 | return mkdir(path); 24 | #else 25 | return mkdir(path, 0755); // not sure if this works on mac 26 | #endif 27 | } 28 | 29 | /** 30 | * Recursive, portable wrapper for mkdir. 31 | * @param[in] path the full path of the directory to create. 32 | * @return zero on success, otherwise -1. 33 | */ 34 | int mkdir(const char *path) { 35 | std::string current_level = "/"; 36 | std::string level; 37 | std::stringstream ss(path); 38 | // First line is empty because it starts with /User 39 | getline(ss, level, '/'); 40 | // split path using slash as a separator 41 | while (getline(ss, level, '/')) { 42 | current_level += level; // append folder to the current level 43 | // create current level 44 | if (!folder_exists(current_level) && _mkdir(current_level.c_str()) != 0) 45 | return -1; 46 | 47 | current_level += "/"; // don't forget to append a slash 48 | } 49 | 50 | return 0; 51 | } 52 | 53 | bool file_exists(const std::string &path) { 54 | struct stat buffer; 55 | return (stat(path.c_str(), &buffer) == 0); 56 | } 57 | 58 | std::string get_db_path(std::string const dbName, std::string const docPath) { 59 | mkdir(docPath.c_str()); 60 | return docPath + "/" + dbName; 61 | } 62 | -------------------------------------------------------------------------------- /cpp/fileUtils.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | bool folder_exists(const std::string &foldername); 4 | 5 | /** 6 | * Portable wrapper for mkdir. Internally used by mkdir() 7 | * @param[in] path the full path of the directory to create. 8 | * @return zero on success, otherwise -1. 9 | */ 10 | int _mkdir(const char *path); 11 | 12 | /** 13 | * Recursive, portable wrapper for mkdir. 14 | * @param[in] path the full path of the directory to create. 15 | * @return zero on success, otherwise -1. 16 | */ 17 | int mkdir(const char *path); 18 | 19 | bool file_exists(const std::string &path); 20 | 21 | std::string get_db_path(std::string const dbName, std::string const docPath); -------------------------------------------------------------------------------- /cpp/logs.h: -------------------------------------------------------------------------------- 1 | #ifdef ANDROID 2 | // LOGS ANDROID 3 | #include 4 | #define LOG_TAG "react-native-quick-sqlite" 5 | #define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__) 6 | #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) 7 | #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) 8 | #define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__) 9 | #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) 10 | #define LOGSIMPLE(...) 11 | #else 12 | // LOGS NO ANDROID 13 | #include 14 | #define LOG_TAG "react-native-quick-sqlite" 15 | #define LOGV(...) \ 16 | printf(" "); \ 17 | printf(__VA_ARGS__); \ 18 | printf("\t - <%s> \n", LOG_TAG); 19 | #define LOGD(...) \ 20 | printf(" "); \ 21 | printf(__VA_ARGS__); \ 22 | printf("\t - <%s> \n", LOG_TAG); 23 | #define LOGI(...) \ 24 | printf(" "); \ 25 | printf(__VA_ARGS__); \ 26 | printf("\t - <%s> \n", LOG_TAG); 27 | #define LOGW(...) \ 28 | printf(" * Warning: "); \ 29 | printf(__VA_ARGS__); \ 30 | printf("\t - <%s> \n", LOG_TAG); 31 | #define LOGE(...) \ 32 | printf(" *** Error: "); \ 33 | printf(__VA_ARGS__); \ 34 | printf("\t - <%s> \n", LOG_TAG); 35 | #define LOGSIMPLE(...) \ 36 | printf(" "); \ 37 | printf(__VA_ARGS__); 38 | #endif // ANDROID 39 | -------------------------------------------------------------------------------- /cpp/macros.h: -------------------------------------------------------------------------------- 1 | #ifndef macros_h 2 | #define macros_h 3 | 4 | #define HOSTFN(name, basecount) \ 5 | jsi::Function::createFromHostFunction( \ 6 | rt, \ 7 | jsi::PropNameID::forAscii(rt, name), \ 8 | basecount, \ 9 | [=](jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, size_t count) -> jsi::Value 10 | 11 | #define JSIFN(capture) \ 12 | capture(jsi::Runtime &runtime, const jsi::Value &thisValue, \ 13 | const jsi::Value *arguments, size_t count) \ 14 | ->jsi::Value 15 | 16 | #endif /* macros_h */ 17 | -------------------------------------------------------------------------------- /cpp/sqlbatchexecutor.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Batch execution implementation 3 | */ 4 | #include "sqlbatchexecutor.h" 5 | #include "fileUtils.h" 6 | #include "sqliteExecute.h" 7 | #include 8 | #include 9 | 10 | void jsiBatchParametersToQuickArguments(jsi::Runtime &rt, 11 | jsi::Array const &batchParams, 12 | vector *commands) { 13 | for (int i = 0; i < batchParams.length(rt); i++) { 14 | const jsi::Array &command = 15 | batchParams.getValueAtIndex(rt, i).asObject(rt).asArray(rt); 16 | if (command.length(rt) == 0) { 17 | continue; 18 | } 19 | 20 | const string query = command.getValueAtIndex(rt, 0).asString(rt).utf8(rt); 21 | const jsi::Value &commandParams = command.length(rt) > 1 22 | ? command.getValueAtIndex(rt, 1) 23 | : jsi::Value::undefined(); 24 | if (!commandParams.isUndefined() && 25 | commandParams.asObject(rt).isArray(rt) && 26 | commandParams.asObject(rt).asArray(rt).length(rt) > 0 && 27 | commandParams.asObject(rt) 28 | .asArray(rt) 29 | .getValueAtIndex(rt, 0) 30 | .isObject()) { 31 | // This arguments is an array of arrays, like a batch update of a single 32 | // sql command. 33 | const jsi::Array &batchUpdateParams = 34 | commandParams.asObject(rt).asArray(rt); 35 | for (int x = 0; x < batchUpdateParams.length(rt); x++) { 36 | const jsi::Value &p = batchUpdateParams.getValueAtIndex(rt, x); 37 | vector params; 38 | jsiQueryArgumentsToSequelParam(rt, p, ¶ms); 39 | commands->push_back(QuickQueryArguments{ 40 | query, make_shared>(params)}); 41 | } 42 | } else { 43 | vector params; 44 | jsiQueryArgumentsToSequelParam(rt, commandParams, ¶ms); 45 | commands->push_back( 46 | QuickQueryArguments{query, make_shared>(params)}); 47 | } 48 | } 49 | } 50 | 51 | SequelBatchOperationResult 52 | sqliteExecuteBatch(sqlite3 *db, vector *commands) { 53 | size_t commandCount = commands->size(); 54 | if (commandCount <= 0) { 55 | return SequelBatchOperationResult{ 56 | .type = SQLiteError, 57 | .message = "No SQL commands provided", 58 | }; 59 | } 60 | 61 | try { 62 | int affectedRows = 0; 63 | 64 | sqliteExecuteLiteralWithDB(db, "BEGIN EXCLUSIVE TRANSACTION"); 65 | 66 | for (int i = 0; i < commandCount; i++) { 67 | auto command = commands->at(i); 68 | // We do not provide a datastructure to receive query data because we 69 | // don't need/want to handle this results in a batch execution 70 | auto result = sqliteExecuteWithDB(db, command.sql, command.params.get(), 71 | NULL, NULL); 72 | if (result.type == SQLiteError) { 73 | sqliteExecuteLiteralWithDB(db, "ROLLBACK"); 74 | return SequelBatchOperationResult{ 75 | .type = SQLiteError, 76 | .message = result.errorMessage, 77 | }; 78 | } else { 79 | affectedRows += result.rowsAffected; 80 | } 81 | } 82 | sqliteExecuteLiteralWithDB(db, "COMMIT"); 83 | return SequelBatchOperationResult{ 84 | .type = SQLiteOk, 85 | .affectedRows = affectedRows, 86 | .commands = (int)commandCount, 87 | }; 88 | } catch (std::exception &exc) { 89 | sqliteExecuteLiteralWithDB(db, "ROLLBACK"); 90 | return SequelBatchOperationResult{ 91 | .type = SQLiteError, 92 | .message = exc.what(), 93 | }; 94 | } 95 | } 96 | 97 | SequelBatchOperationResult sqliteImportFile(sqlite3 *db, 98 | const std::string fileLocation) { 99 | std::string line; 100 | std::ifstream sqFile(fileLocation); 101 | 102 | if (sqFile.is_open()) { 103 | try { 104 | int affectedRows = 0; 105 | int commands = 0; 106 | sqliteExecuteLiteralWithDB(db, "BEGIN EXCLUSIVE TRANSACTION"); 107 | while (std::getline(sqFile, line, '\n')) { 108 | if (!line.empty()) { 109 | SequelLiteralUpdateResult result = 110 | sqliteExecuteLiteralWithDB(db, line); 111 | if (result.type == SQLiteError) { 112 | sqliteExecuteLiteralWithDB(db, "ROLLBACK"); 113 | sqFile.close(); 114 | return {SQLiteError, result.message, 0, commands}; 115 | } else { 116 | affectedRows += result.affectedRows; 117 | commands++; 118 | } 119 | } 120 | } 121 | sqFile.close(); 122 | sqliteExecuteLiteralWithDB(db, "COMMIT"); 123 | return {SQLiteOk, "", affectedRows, commands}; 124 | } catch (...) { 125 | sqFile.close(); 126 | sqliteExecuteLiteralWithDB(db, "ROLLBACK"); 127 | return {SQLiteError, 128 | "[react-native-quick-sqlite][loadSQLFile] Unexpected error, " 129 | "transaction was rolledback", 130 | 0, 0}; 131 | } 132 | } else { 133 | return {SQLiteError, 134 | "[react-native-quick-sqlite][loadSQLFile] Could not open file", 0, 135 | 0}; 136 | } 137 | } -------------------------------------------------------------------------------- /cpp/sqlbatchexecutor.h: -------------------------------------------------------------------------------- 1 | /** 2 | * SQL Batch execution implementation using default sqliteBridge implementation 3 | */ 4 | #include "ConnectionPool.h" 5 | #include "JSIHelper.h" 6 | #include "sqliteBridge.h" 7 | 8 | using namespace std; 9 | using namespace facebook; 10 | 11 | struct QuickQueryArguments { 12 | string sql; 13 | shared_ptr> params; 14 | }; 15 | 16 | /** 17 | * Local Helper method to translate JSI objects QuickQueryArguments 18 | * datastructure MUST be called in the JavaScript Thread 19 | */ 20 | void jsiBatchParametersToQuickArguments(jsi::Runtime &rt, 21 | jsi::Array const &batchParams, 22 | vector *commands); 23 | 24 | /** 25 | * Execute a batch of commands in a exclusive transaction 26 | */ 27 | SequelBatchOperationResult 28 | sqliteExecuteBatch(sqlite3 *db, vector *commands); 29 | 30 | SequelBatchOperationResult sqliteImportFile(sqlite3 *db, 31 | std::string const file); -------------------------------------------------------------------------------- /cpp/sqliteBridge.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * sequel.cpp 3 | * 4 | * Created by Oscar Franco on 2021/03/07 5 | * Copyright (c) 2021 Oscar Franco 6 | * 7 | * This code is licensed under the MIT license 8 | */ 9 | 10 | #include "sqliteBridge.h" 11 | #include "ConnectionPool.h" 12 | #include "fileUtils.h" 13 | #include "logs.h" 14 | #include "sqlite3.h" 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | using namespace std; 23 | using namespace facebook; 24 | 25 | std::map dbMap = 26 | std::map(); 27 | 28 | SQLiteOPResult generateNotOpenResult(std::string const &dbName) { 29 | return SQLiteOPResult{ 30 | .type = SQLiteError, 31 | .errorMessage = dbName + " is not open", 32 | }; 33 | } 34 | 35 | ConnectionPool *getConnection(std::string const dbName) { 36 | if (dbMap.count(dbName) == 0) { 37 | // Connection is already closed 38 | return nullptr; 39 | } 40 | 41 | return dbMap[dbName]; 42 | } 43 | 44 | 45 | /** 46 | * Opens SQL database with default settings 47 | */ 48 | SQLiteOPResult 49 | sqliteOpenDb(string const dbName, string const docPath, 50 | void (*contextAvailableCallback)(std::string, ConnectionLockId), 51 | void (*updateTableCallback)(void *, int, const char *, 52 | const char *, sqlite3_int64), 53 | void (*onTransactionFinalizedCallback)( 54 | const TransactionCallbackPayload *event), 55 | uint32_t numReadConnections) { 56 | if (dbMap.count(dbName) == 1) { 57 | return SQLiteOPResult{ 58 | .type = SQLiteError, 59 | .errorMessage = dbName + " is already open", 60 | }; 61 | } 62 | 63 | try { 64 | // Open the database 65 | dbMap[dbName] = new ConnectionPool(dbName, docPath, numReadConnections); 66 | dbMap[dbName]->setOnContextAvailable(contextAvailableCallback); 67 | dbMap[dbName]->setTableUpdateHandler(updateTableCallback); 68 | dbMap[dbName]->setTransactionFinalizerHandler(onTransactionFinalizedCallback); 69 | } catch (const std::exception &e) { 70 | return SQLiteOPResult{ 71 | .type = SQLiteError, 72 | .errorMessage = e.what(), 73 | }; 74 | } 75 | 76 | return SQLiteOPResult{ 77 | .type = SQLiteOk, 78 | }; 79 | } 80 | 81 | std::future sqliteRefreshSchema(const std::string& dbName) { 82 | if (dbMap.count(dbName) == 0) { 83 | std::promise promise; 84 | promise.set_value(); 85 | return promise.get_future(); 86 | } 87 | 88 | ConnectionPool* connection = dbMap[dbName]; 89 | return connection->refreshSchema(); 90 | } 91 | 92 | SQLiteOPResult sqliteCloseDb(string const dbName) { 93 | if (dbMap.count(dbName) == 0) { 94 | return generateNotOpenResult(dbName); 95 | } 96 | 97 | ConnectionPool *connection = dbMap[dbName]; 98 | 99 | connection->closeAll(); 100 | dbMap.erase(dbName); 101 | delete connection; 102 | 103 | return SQLiteOPResult{ 104 | .type = SQLiteOk, 105 | }; 106 | } 107 | 108 | void sqliteCloseAll() { 109 | for (auto const &x : dbMap) { 110 | x.second->closeAll(); 111 | delete x.second; 112 | } 113 | dbMap.clear(); 114 | } 115 | 116 | SQLiteOPResult sqliteQueueInContext(std::string dbName, 117 | ConnectionLockId const contextId, 118 | std::function task) { 119 | if (dbMap.count(dbName) == 0) { 120 | return generateNotOpenResult(dbName); 121 | } 122 | 123 | ConnectionPool *connection = dbMap[dbName]; 124 | return connection->queueInContext(contextId, task); 125 | } 126 | 127 | void sqliteReleaseLock(std::string const dbName, 128 | ConnectionLockId const contextId) { 129 | if (dbMap.count(dbName) == 0) { 130 | // Do nothing if the lock does not actually exist 131 | return; 132 | } 133 | 134 | ConnectionPool *connection = dbMap[dbName]; 135 | connection->closeContext(contextId); 136 | } 137 | 138 | SQLiteOPResult sqliteRequestLock(std::string const dbName, 139 | ConnectionLockId const contextId, 140 | ConcurrentLockType lockType) { 141 | if (dbMap.count(dbName) == 0) { 142 | return generateNotOpenResult(dbName); 143 | } 144 | 145 | ConnectionPool *connection = dbMap[dbName]; 146 | 147 | switch (lockType) { 148 | case ConcurrentLockType::ReadLock: 149 | connection->readLock(contextId); 150 | break; 151 | case ConcurrentLockType::WriteLock: 152 | connection->writeLock(contextId); 153 | break; 154 | 155 | default: 156 | break; 157 | } 158 | 159 | return SQLiteOPResult{ 160 | .type = SQLiteOk, 161 | 162 | }; 163 | } 164 | 165 | SQLiteOPResult sqliteAttachDb(string const mainDBName, string const docPath, 166 | string const databaseToAttach, 167 | string const alias) { 168 | if (dbMap.count(mainDBName) == 0) { 169 | return generateNotOpenResult(mainDBName); 170 | } 171 | 172 | ConnectionPool *connection = dbMap[mainDBName]; 173 | return connection->attachDatabase(databaseToAttach, docPath, alias); 174 | } 175 | 176 | SQLiteOPResult sqliteDetachDb(string const mainDBName, string const alias) { 177 | if (dbMap.count(mainDBName) == 0) { 178 | return generateNotOpenResult(mainDBName); 179 | } 180 | 181 | ConnectionPool *connection = dbMap[mainDBName]; 182 | return connection->detachDatabase(alias); 183 | } 184 | 185 | SQLiteOPResult sqliteRemoveDb(string const dbName, string const docPath) { 186 | if (dbMap.count(dbName) == 1) { 187 | SQLiteOPResult closeResult = sqliteCloseDb(dbName); 188 | if (closeResult.type == SQLiteError) { 189 | return closeResult; 190 | } 191 | } 192 | 193 | string dbPath = get_db_path(dbName, docPath); 194 | 195 | if (!file_exists(dbPath)) { 196 | return SQLiteOPResult{ 197 | .type = SQLiteOk, 198 | .errorMessage = 199 | "[react-native-quick-sqlite]: Database file not found" + dbPath}; 200 | } 201 | 202 | remove(dbPath.c_str()); 203 | 204 | return SQLiteOPResult{ 205 | .type = SQLiteOk, 206 | }; 207 | } 208 | -------------------------------------------------------------------------------- /cpp/sqliteBridge.h: -------------------------------------------------------------------------------- 1 | /* 2 | * sequel.h 3 | * 4 | * Created by Oscar Franco on 2021/03/07 5 | * Copyright (c) 2021 Oscar Franco 6 | * 7 | * This code is licensed under the MIT license 8 | */ 9 | 10 | #include "ConnectionPool.h" 11 | #include "JSIHelper.h" 12 | #include "sqlite3.h" 13 | #include 14 | #include 15 | 16 | #ifndef SQLiteBridge_h 17 | #define SQLiteBridge_h 18 | 19 | #define CONCURRENT_READ_CONNECTIONS 4 20 | 21 | using namespace facebook; 22 | 23 | enum ConcurrentLockType { 24 | ReadLock, 25 | WriteLock, 26 | }; 27 | 28 | SQLiteOPResult 29 | sqliteOpenDb(std::string const dbName, std::string const docPath, 30 | void (*contextAvailableCallback)(std::string, ConnectionLockId), 31 | void (*updateTableCallback)(void *, int, const char *, 32 | const char *, sqlite3_int64), 33 | void (*onTransactionFinalizedCallback)( 34 | const TransactionCallbackPayload *event), 35 | uint32_t numReadConnections); 36 | 37 | std::future sqliteRefreshSchema(const std::string& dbName); 38 | 39 | SQLiteOPResult sqliteCloseDb(string const dbName); 40 | 41 | void sqliteCloseAll(); 42 | 43 | ConnectionPool *getConnection(std::string const dbName); 44 | 45 | SQLiteOPResult sqliteRemoveDb(string const dbName, string const docPath); 46 | 47 | /** 48 | * Requests a lock context to be queued for a read or write lock 49 | */ 50 | SQLiteOPResult sqliteRequestLock(std::string const dbName, 51 | ConnectionLockId const contextId, 52 | ConcurrentLockType lockType); 53 | 54 | SQLiteOPResult sqliteQueueInContext(std::string dbName, 55 | ConnectionLockId const contextId, 56 | std::function); 57 | 58 | void sqliteReleaseLock(std::string const dbName, 59 | ConnectionLockId const contextId); 60 | 61 | SQLiteOPResult sqliteAttachDb(string const mainDBName, string const docPath, 62 | string const databaseToAttach, 63 | string const alias); 64 | 65 | SQLiteOPResult sqliteDetachDb(string const mainDBName, string const alias); 66 | 67 | #endif -------------------------------------------------------------------------------- /cpp/sqliteExecute.cpp: -------------------------------------------------------------------------------- 1 | #include "sqliteExecute.h" 2 | 3 | void bindStatement(sqlite3_stmt *statement, vector *values) { 4 | size_t size = values->size(); 5 | if (size <= 0) { 6 | return; 7 | } 8 | 9 | for (int ii = 0; ii < size; ii++) { 10 | int sqIndex = ii + 1; 11 | const QuickValue& value = values->at(ii); 12 | QuickDataType dataType = value.dataType; 13 | if (dataType == NULL_VALUE) { 14 | sqlite3_bind_null(statement, sqIndex); 15 | } else if (dataType == BOOLEAN) { 16 | sqlite3_bind_int(statement, sqIndex, value.booleanValue); 17 | } else if (dataType == INTEGER) { 18 | sqlite3_bind_int(statement, sqIndex, (int)value.doubleOrIntValue); 19 | } else if (dataType == DOUBLE) { 20 | sqlite3_bind_double(statement, sqIndex, value.doubleOrIntValue); 21 | } else if (dataType == INT64) { 22 | sqlite3_bind_int64(statement, sqIndex, value.int64Value); 23 | } else if (dataType == TEXT) { 24 | sqlite3_bind_text(statement, sqIndex, value.textValue.c_str(), 25 | value.textValue.length(), SQLITE_TRANSIENT); 26 | } else if (dataType == ARRAY_BUFFER) { 27 | sqlite3_bind_blob(statement, sqIndex, value.arrayBuffer.data(), 28 | value.arrayBuffer.size(), SQLITE_STATIC); 29 | } 30 | } 31 | } 32 | 33 | SQLiteOPResult 34 | sqliteExecuteWithDB(sqlite3 *db, std::string const &query, 35 | std::vector *params, 36 | std::vector> *results, 37 | std::vector *metadata) { 38 | sqlite3_stmt *statement; 39 | 40 | int statementStatus = 41 | sqlite3_prepare_v2(db, query.c_str(), -1, &statement, NULL); 42 | 43 | if (statementStatus == 44 | SQLITE_OK) // statemnet is correct, bind the passed parameters 45 | { 46 | bindStatement(statement, params); 47 | } else { 48 | const char *message = sqlite3_errmsg(db); 49 | return SQLiteOPResult{ 50 | .type = SQLiteError, 51 | .errorMessage = "[react-native-quick-sqlite] SQL execution error: " + 52 | string(message), 53 | .rowsAffected = 0}; 54 | } 55 | 56 | bool isConsuming = true; 57 | bool isFailed = false; 58 | 59 | int result, i, count, column_type; 60 | std::string column_name, column_declared_type; 61 | std::map row; 62 | 63 | while (isConsuming) { 64 | result = sqlite3_step(statement); 65 | 66 | switch (result) { 67 | case SQLITE_ROW: 68 | if (results == NULL) { 69 | break; 70 | } 71 | 72 | i = 0; 73 | row = std::map(); 74 | count = sqlite3_column_count(statement); 75 | 76 | while (i < count) { 77 | column_type = sqlite3_column_type(statement, i); 78 | column_name = sqlite3_column_name(statement, i); 79 | 80 | switch (column_type) { 81 | 82 | case SQLITE_INTEGER: { 83 | /** 84 | * It's not possible to send a int64_t in a jsi::Value because JS 85 | * cannot represent the whole number range. Instead, we're sending a 86 | * double, which can represent all integers up to 53 bits long, which 87 | * is more than what was there before (a 32-bit int). 88 | * 89 | * See https://github.com/margelo/react-native-quick-sqlite/issues/16 90 | * for more context. 91 | */ 92 | double column_value = sqlite3_column_double(statement, i); 93 | row[column_name] = createIntegerQuickValue(column_value); 94 | break; 95 | } 96 | 97 | case SQLITE_FLOAT: { 98 | double column_value = sqlite3_column_double(statement, i); 99 | row[column_name] = createDoubleQuickValue(column_value); 100 | break; 101 | } 102 | 103 | case SQLITE_TEXT: { 104 | const char *column_value = 105 | reinterpret_cast(sqlite3_column_text(statement, i)); 106 | int byteLen = sqlite3_column_bytes(statement, i); 107 | // Specify length too; in case string contains NULL in the middle 108 | // (which SQLite supports!) 109 | row[column_name] = 110 | createTextQuickValue(std::string(column_value, byteLen)); 111 | break; 112 | } 113 | 114 | case SQLITE_BLOB: { 115 | int blob_size = sqlite3_column_bytes(statement, i); 116 | const void *blob = sqlite3_column_blob(statement, i); 117 | row[column_name] = createArrayBufferQuickValueByCopying(static_cast(blob), blob_size); 118 | break; 119 | } 120 | 121 | case SQLITE_NULL: 122 | // Intentionally left blank to switch to default case 123 | default: 124 | row[column_name] = createNullQuickValue(); 125 | break; 126 | } 127 | i++; 128 | } 129 | results->push_back(move(row)); 130 | break; 131 | case SQLITE_DONE: 132 | if (metadata != NULL) { 133 | i = 0; 134 | count = sqlite3_column_count(statement); 135 | while (i < count) { 136 | column_name = sqlite3_column_name(statement, i); 137 | const char *tp = sqlite3_column_decltype(statement, i); 138 | column_declared_type = tp != NULL ? tp : "UNKNOWN"; 139 | QuickColumnMetadata meta = { 140 | .columnName = column_name, 141 | .columnIndex = i, 142 | .columnDeclaredType = column_declared_type, 143 | }; 144 | metadata->push_back(meta); 145 | i++; 146 | } 147 | } 148 | isConsuming = false; 149 | break; 150 | 151 | default: 152 | isFailed = true; 153 | isConsuming = false; 154 | } 155 | } 156 | 157 | sqlite3_finalize(statement); 158 | 159 | if (isFailed) { 160 | const char *message = sqlite3_errmsg(db); 161 | return SQLiteOPResult{ 162 | .type = SQLiteError, 163 | .errorMessage = "[react-native-quick-sqlite] SQL execution error: " + 164 | std::string(message), 165 | .rowsAffected = 0, 166 | .insertId = 0}; 167 | } 168 | 169 | int changedRowCount = sqlite3_changes(db); 170 | long long latestInsertRowId = sqlite3_last_insert_rowid(db); 171 | return SQLiteOPResult{.type = SQLiteOk, 172 | .rowsAffected = changedRowCount, 173 | .insertId = static_cast(latestInsertRowId)}; 174 | } 175 | 176 | SequelLiteralUpdateResult sqliteExecuteLiteralWithDB(sqlite3 *db, 177 | string const &query) { 178 | // SQLite statements need to be compiled before executed 179 | sqlite3_stmt *statement; 180 | 181 | // Compile and move result into statement memory spot 182 | int statementStatus = 183 | sqlite3_prepare_v2(db, query.c_str(), -1, &statement, NULL); 184 | 185 | if (statementStatus != 186 | SQLITE_OK) // statemnet is correct, bind the passed parameters 187 | { 188 | const char *message = sqlite3_errmsg(db); 189 | return {SQLiteError, 190 | "[react-native-quick-sqlite] SQL execution error: " + 191 | string(message), 192 | 0}; 193 | } 194 | 195 | bool isConsuming = true; 196 | bool isFailed = false; 197 | 198 | int result, i, count, column_type; 199 | string column_name; 200 | 201 | while (isConsuming) { 202 | result = sqlite3_step(statement); 203 | 204 | switch (result) { 205 | case SQLITE_ROW: 206 | isConsuming = true; 207 | break; 208 | 209 | case SQLITE_DONE: 210 | isConsuming = false; 211 | break; 212 | 213 | default: 214 | isFailed = true; 215 | isConsuming = false; 216 | } 217 | } 218 | 219 | sqlite3_finalize(statement); 220 | 221 | if (isFailed) { 222 | const char *message = sqlite3_errmsg(db); 223 | return {SQLiteError, 224 | "[react-native-quick-sqlite] SQL execution error: " + 225 | string(message), 226 | 0}; 227 | } 228 | 229 | int changedRowCount = sqlite3_changes(db); 230 | return {SQLiteOk, "", changedRowCount}; 231 | } 232 | -------------------------------------------------------------------------------- /cpp/sqliteExecute.h: -------------------------------------------------------------------------------- 1 | #include "JSIHelper.h" 2 | #include "sqlite3.h" 3 | #include 4 | #include 5 | #include 6 | 7 | SQLiteOPResult 8 | sqliteExecuteWithDB(sqlite3 *db, std::string const &query, 9 | std::vector *params, 10 | std::vector> *results, 11 | std::vector *metadata); 12 | 13 | SequelLiteralUpdateResult sqliteExecuteLiteralWithDB(sqlite3 *db, 14 | std::string const &query); 15 | 16 | void bindStatement(sqlite3_stmt *statement, std::vector *values); -------------------------------------------------------------------------------- /header2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powersync-ja/react-native-quick-sqlite/e3f5c79741c23e1f34ba1cdc40e2d56415d4dc45/header2.png -------------------------------------------------------------------------------- /ios/QuickSQLite.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface QuickSQLite : NSObject 5 | 6 | @property(nonatomic, assign) BOOL setBridgeOnMainQueue; 7 | 8 | @end 9 | -------------------------------------------------------------------------------- /ios/QuickSQLite.mm: -------------------------------------------------------------------------------- 1 | #import "QuickSQLite.h" 2 | 3 | #import 4 | 5 | #import 6 | #import 7 | #import 8 | 9 | #import "../cpp/bindings.h" 10 | 11 | @implementation QuickSQLite 12 | 13 | RCT_EXPORT_MODULE(QuickSQLite) 14 | 15 | 16 | RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(install) { 17 | NSLog(@"Installing QuickSQLite module..."); 18 | 19 | RCTBridge *bridge = [RCTBridge currentBridge]; 20 | RCTCxxBridge *cxxBridge = (RCTCxxBridge *)bridge; 21 | if (cxxBridge == nil) { 22 | return @false; 23 | } 24 | 25 | using namespace facebook; 26 | 27 | auto jsiRuntime = (jsi::Runtime *)cxxBridge.runtime; 28 | if (jsiRuntime == nil) { 29 | return @false; 30 | } 31 | auto &runtime = *jsiRuntime; 32 | auto callInvoker = bridge.jsCallInvoker; 33 | 34 | // Get appGroupID value from Info.plist using key "AppGroup" 35 | NSString *appGroupID = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"ReactNativeQuickSQLite_AppGroup"]; 36 | NSString *documentPath; 37 | 38 | if (appGroupID != nil) { 39 | // Get the app groups container storage url 40 | NSFileManager *fileManager = [NSFileManager defaultManager]; 41 | NSURL *storeUrl = [fileManager containerURLForSecurityApplicationGroupIdentifier:appGroupID]; 42 | 43 | if (storeUrl == nil) { 44 | NSLog(@"Invalid AppGroup ID provided (%@). Check the value of \"AppGroup\" in your Info.plist file", appGroupID); 45 | return @false; 46 | } 47 | NSLog(@"Configured with AppGroup ID: %@", appGroupID); 48 | 49 | documentPath = [storeUrl path]; 50 | } else { 51 | // Get iOS app's document directory (to safely store database .sqlite3 file) 52 | NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true); 53 | documentPath = [paths objectAtIndex:0]; 54 | } 55 | 56 | osp::install(runtime, callInvoker, [documentPath UTF8String]); 57 | return @true; 58 | } 59 | 60 | - (void)invalidate { 61 | osp::clearState(); 62 | } 63 | 64 | 65 | @end 66 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@journeyapps/react-native-quick-sqlite", 3 | "publishConfig": { 4 | "registry": "https://registry.npmjs.org/", 5 | "access": "public" 6 | }, 7 | "version": "2.4.4", 8 | "description": "Fast SQLite for react-native", 9 | "main": "lib/commonjs/index", 10 | "module": "lib/module/index", 11 | "types": "lib/typescript/src/index.d.ts", 12 | "react-native": "src/index", 13 | "source": "src/index", 14 | "files": [ 15 | "src", 16 | "lib", 17 | "android", 18 | "ios", 19 | "cpp", 20 | "meta.json", 21 | "app.plugin.js", 22 | "react-native-quick-sqlite.podspec", 23 | "!android/build", 24 | "!android/.cxx", 25 | "!ios/build", 26 | "!**/__tests__", 27 | "!**/__fixtures__", 28 | "!**/__mocks__" 29 | ], 30 | "scripts": { 31 | "build": "bob build", 32 | "clean": "rm -rf lib tsconfig.tsbuildinfo", 33 | "format": "prettier --write .", 34 | "typescript": "tsc --noEmit", 35 | "prepare": "bob build", 36 | "example": "yarn --cwd example", 37 | "release": "yarn changeset publish" 38 | }, 39 | "keywords": [ 40 | "react-native", 41 | "ios", 42 | "android" 43 | ], 44 | "repository": "https://github.com/powersync-ja/react-native-quick-sqlite", 45 | "author": "JOURNEYAPPS", 46 | "license": "MIT", 47 | "bugs": { 48 | "url": "https://github.com/powersync-ja/react-native-quick-sqlite/issues" 49 | }, 50 | "homepage": "https://github.com/powersync-ja/react-native-quick-sqlite#readme", 51 | "devDependencies": { 52 | "@changesets/cli": "^2.26.2", 53 | "@expo/config-plugins": "^9.0.17", 54 | "prettier": "^3.3.3", 55 | "react": "18.3.1", 56 | "react-native": "0.76.2", 57 | "react-native-builder-bob": "^0.30.1", 58 | "typescript": "^5.3.3" 59 | }, 60 | "peerDependencies": { 61 | "react": "*", 62 | "react-native": "*" 63 | }, 64 | "react-native-builder-bob": { 65 | "source": "src", 66 | "output": "lib", 67 | "targets": [ 68 | "commonjs", 69 | "module", 70 | [ 71 | "typescript", 72 | { 73 | "project": "tsconfig.json" 74 | } 75 | ] 76 | ] 77 | }, 78 | "dependencies": {}, 79 | "packageManager": "yarn@1.22.19+sha512.ff4579ab459bb25aa7c0ff75b62acebe576f6084b36aa842971cf250a5d8c6cd3bc9420b22ce63c7f93a0857bc6ef29291db39c3e7a23aab5adfd5a4dd6c5d71" 80 | } 81 | -------------------------------------------------------------------------------- /react-native-quick-sqlite.podspec: -------------------------------------------------------------------------------- 1 | require "json" 2 | 3 | package = JSON.parse(File.read(File.join(__dir__, "package.json"))) 4 | if (File.exist?(File.join(__dir__, "meta.json"))) 5 | meta = JSON.parse(File.read(File.join(__dir__, "meta.json"))) 6 | package["version"] = meta["version"] 7 | end 8 | 9 | Pod::Spec.new do |s| 10 | s.name = "react-native-quick-sqlite" 11 | s.version = package["version"] 12 | s.summary = package["description"] 13 | s.homepage = package["homepage"] 14 | s.license = package["license"] 15 | s.authors = package["author"] 16 | 17 | s.platforms = { :ios => "10.0" } 18 | s.source = { :git => "https://github.com/margelo/react-native-quick-sqlite.git", :tag => "#{s.version}" } 19 | 20 | s.pod_target_xcconfig = { 21 | :GCC_PREPROCESSOR_DEFINITIONS => "HAVE_FULLFSYNC=1 SQLITE_ENABLE_FTS5=1", 22 | :WARNING_CFLAGS => "-Wno-shorten-64-to-32 -Wno-comma -Wno-unreachable-code -Wno-conditional-uninitialized -Wno-deprecated-declarations", 23 | :USE_HEADERMAP => "No" 24 | } 25 | 26 | s.header_mappings_dir = "cpp" 27 | s.source_files = "ios/**/*.{h,hpp,m,mm}", "cpp/**/*.{h,cpp,c}" 28 | 29 | s.dependency "React-callinvoker" 30 | s.dependency "React" 31 | s.dependency "powersync-sqlite-core", "~> 0.3.14" 32 | if defined?(install_modules_dependencies()) 33 | install_modules_dependencies(s) 34 | else 35 | s.dependency "React-Core" 36 | end 37 | 38 | if ENV['QUICK_SQLITE_USE_PHONE_VERSION'] == '1' then 39 | s.exclude_files = "cpp/sqlite3.c", "cpp/sqlite3.h" 40 | s.library = "sqlite3" 41 | end 42 | 43 | end 44 | -------------------------------------------------------------------------------- /scripts/bootstrap.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const child_process = require('child_process'); 3 | 4 | const root = path.resolve(__dirname, '..'); 5 | const args = process.argv.slice(2); 6 | const options = { 7 | cwd: process.cwd(), 8 | env: process.env, 9 | stdio: 'inherit', 10 | encoding: 'utf-8' 11 | }; 12 | 13 | let result; 14 | 15 | if (process.cwd() !== root || args.length) { 16 | // We're not in the root of the project, or additional arguments were passed 17 | // In this case, forward the command to `yarn` 18 | result = child_process.spawnSync('yarn', args, options); 19 | } else { 20 | // If `yarn` is run without arguments, perform bootstrap 21 | result = child_process.spawnSync('yarn', ['bootstrap'], options); 22 | } 23 | 24 | process.exitCode = result.status; 25 | -------------------------------------------------------------------------------- /scripts/dev-package-pre-version.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Dev package versions like 0.0.0-dev-[timestamp] seem to cause issues with the podspec 3 | * This script will remove the -dev-[timestamp] from the version. 4 | * Side Note: Versions above 0.0.0 e.g. 0.0.1-dev-[timestamp] do work with pod install. 5 | */ 6 | const fs = require('fs'); 7 | const path = require('path'); 8 | 9 | console.log(`Storing current version for dev version`); 10 | const currentPackage = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../package.json'), 'utf-8')); 11 | // Backup the current version 12 | fs.writeFileSync(path.join(__dirname, '../meta.json'), JSON.stringify({ version: currentPackage.version }, null, '\t')); 13 | -------------------------------------------------------------------------------- /sponsors/stream.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powersync-ja/react-native-quick-sqlite/e3f5c79741c23e1f34ba1cdc40e2d56415d4dc45/sponsors/stream.png -------------------------------------------------------------------------------- /src/DBListenerManager.ts: -------------------------------------------------------------------------------- 1 | import { registerTransactionHook, registerUpdateHook } from './table-updates'; 2 | import { 3 | BatchedUpdateCallback, 4 | BatchedUpdateNotification, 5 | TransactionEvent, 6 | UpdateCallback, 7 | UpdateNotification 8 | } from './types'; 9 | import { BaseListener, BaseObserver } from './utils/BaseObserver'; 10 | 11 | export interface DBListenerManagerOptions { 12 | dbName: string; 13 | } 14 | 15 | export interface WriteTransactionEvent { 16 | type: TransactionEvent; 17 | } 18 | 19 | export interface DBListener extends BaseListener { 20 | /** 21 | * Register a listener to be fired for any table change. 22 | * Changes inside write locks and transactions are reported immediately. 23 | */ 24 | rawTableChange: UpdateCallback; 25 | 26 | /** 27 | * Register a listener for when table changes are persisted 28 | * into the DB. Changes during write transactions which are 29 | * rolled back are not reported. 30 | * Any changes during write locks are buffered and reported 31 | * after transaction commit and lock release. 32 | * Table changes are reported individually for now in order to maintain 33 | * API compatibility. These can be batched in future. 34 | */ 35 | tablesUpdated: BatchedUpdateCallback; 36 | 37 | /** 38 | * Listener event triggered whenever a write transaction 39 | * is started, committed or rolled back. 40 | */ 41 | writeTransaction: (event: WriteTransactionEvent) => void; 42 | 43 | closed: () => void; 44 | } 45 | 46 | export class DBListenerManager extends BaseObserver {} 47 | 48 | export class DBListenerManagerInternal extends DBListenerManager { 49 | private updateBuffer: UpdateNotification[]; 50 | 51 | constructor(protected options: DBListenerManagerOptions) { 52 | super(); 53 | this.updateBuffer = []; 54 | registerUpdateHook(this.options.dbName, (update) => this.handleTableUpdates(update)); 55 | registerTransactionHook(this.options.dbName, (eventType) => { 56 | switch (eventType) { 57 | /** 58 | * COMMIT hooks occur before the commit is completed. This leads to race conditions. 59 | * Only use the rollback event to clear updates. 60 | */ 61 | case TransactionEvent.ROLLBACK: 62 | this.transactionReverted(); 63 | break; 64 | } 65 | 66 | this.iterateListeners((l) => 67 | l.writeTransaction?.({ 68 | type: eventType 69 | }) 70 | ); 71 | }); 72 | } 73 | 74 | flushUpdates() { 75 | if (!this.updateBuffer.length) { 76 | return; 77 | } 78 | 79 | const groupedUpdates = this.updateBuffer.reduce((grouping: Record, update) => { 80 | const { table } = update; 81 | const updateGroup = grouping[table] || (grouping[table] = []); 82 | updateGroup.push(update); 83 | return grouping; 84 | }, {}); 85 | 86 | const batchedUpdate: BatchedUpdateNotification = { 87 | groupedUpdates, 88 | rawUpdates: this.updateBuffer, 89 | tables: Object.keys(groupedUpdates) 90 | }; 91 | this.updateBuffer = []; 92 | this.iterateListeners((l) => l.tablesUpdated?.(batchedUpdate)); 93 | } 94 | 95 | protected transactionReverted() { 96 | // clear updates 97 | this.updateBuffer = []; 98 | } 99 | 100 | handleTableUpdates(notification: UpdateNotification) { 101 | // Fire updates for any change 102 | this.iterateListeners((l) => l.rawTableChange?.({ ...notification })); 103 | 104 | // Queue changes until they are flushed 105 | this.updateBuffer.push(notification); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { NativeModules } from 'react-native'; 2 | import { ISQLite } from './types'; 3 | import { setupOpen } from './setup-open'; 4 | import { setupTypeORMDriver } from './type-orm'; 5 | 6 | export * from './types'; 7 | 8 | declare global { 9 | function nativeCallSyncHook(): unknown; 10 | var __QuickSQLiteProxy: object | undefined; 11 | } 12 | 13 | if (global.__QuickSQLiteProxy == null) { 14 | const QuickSQLiteModule = NativeModules.QuickSQLite; 15 | 16 | if (QuickSQLiteModule == null) { 17 | throw new Error('Base quick-sqlite module not found. Maybe try rebuilding the app.'); 18 | } 19 | 20 | // Check if we are running on-device (JSI) 21 | if (QuickSQLiteModule.install == null) { 22 | throw new Error( 23 | 'Failed to install react-native-quick-sqlite: React Native is not running on-device. QuickSQLite can only be used when synchronous method invocations (JSI) are possible. If you are using a remote debugger (e.g. Chrome), switch to an on-device debugger (e.g. Flipper) instead.' 24 | ); 25 | } 26 | 27 | // Call the synchronous blocking install() function 28 | const result = QuickSQLiteModule.install(); 29 | if (result !== true) { 30 | throw new Error( 31 | `Failed to install react-native-quick-sqlite: The native QuickSQLite Module could not be installed! Looks like something went wrong when installing JSI bindings: ${result}` 32 | ); 33 | } 34 | 35 | // Check again if the constructor now exists. If not, throw an error. 36 | if (global.__QuickSQLiteProxy == null) { 37 | throw new Error( 38 | 'Failed to install react-native-quick-sqlite, the native initializer function does not exist. Are you trying to use QuickSQLite from different JS Runtimes?' 39 | ); 40 | } 41 | } 42 | 43 | const proxy = global.__QuickSQLiteProxy; 44 | export const QuickSQLite = proxy as ISQLite; 45 | 46 | export const { open } = setupOpen(QuickSQLite); 47 | 48 | export const typeORMDriver = setupTypeORMDriver(open); 49 | -------------------------------------------------------------------------------- /src/lock-hooks.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Hooks which can be triggered during the execution of read/write locks 3 | */ 4 | export interface LockHooks { 5 | lockAcquired?: () => Promise; 6 | lockReleased?: () => Promise; 7 | } 8 | -------------------------------------------------------------------------------- /src/table-updates.ts: -------------------------------------------------------------------------------- 1 | import { RowUpdateType, TransactionCallback, TransactionEvent, UpdateCallback } from './types'; 2 | 3 | const updateCallbacks: Record = {}; 4 | const transactionCallbacks: Record = {}; 5 | 6 | /** 7 | * Entry point for update callbacks. This is triggered from C++ with params. 8 | */ 9 | global.triggerUpdateHook = function (dbName: string, table: string, opType: RowUpdateType, rowId: number) { 10 | const callback = updateCallbacks[dbName]; 11 | if (!callback) { 12 | return; 13 | } 14 | 15 | callback({ 16 | opType, 17 | table, 18 | rowId 19 | }); 20 | return null; 21 | }; 22 | 23 | export const registerUpdateHook = (dbName: string, callback: UpdateCallback) => { 24 | updateCallbacks[dbName] = callback; 25 | }; 26 | 27 | /** 28 | * Entry point for transaction callbacks. This is triggered from C++ with params. 29 | */ 30 | global.triggerTransactionFinalizerHook = function (dbName: string, eventType: TransactionEvent) { 31 | const callback = transactionCallbacks[dbName]; 32 | if (!callback) { 33 | return; 34 | } 35 | 36 | callback(eventType); 37 | return null; 38 | }; 39 | 40 | export const registerTransactionHook = (dbName: string, callback: TransactionCallback) => { 41 | transactionCallbacks[dbName] = callback; 42 | }; 43 | -------------------------------------------------------------------------------- /src/type-orm.ts: -------------------------------------------------------------------------------- 1 | import { QueryResult, TransactionContext, Open } from './types'; 2 | 3 | /** 4 | * DO NOT USE THIS! THIS IS MEANT FOR TYPEORM 5 | * If you are looking for a convenience wrapper use `connect` 6 | */ 7 | export const setupTypeORMDriver = (open: Open) => ({ 8 | openDatabase: ( 9 | options: { 10 | name: string; 11 | location?: string; 12 | }, 13 | ok: (db: any) => void, 14 | fail: (msg: string) => void 15 | ): any => { 16 | try { 17 | const _con = open(options.name, { location: options.location }); 18 | 19 | const connection = { 20 | executeSql: async ( 21 | sql: string, 22 | params: any[] | undefined, 23 | ok: (res: QueryResult) => void, 24 | fail: (msg: string) => void 25 | ) => { 26 | try { 27 | let response = await _con.execute(sql, params); 28 | ok(response); 29 | } catch (e) { 30 | fail(e); 31 | } 32 | }, 33 | transaction: (fn: (tx: TransactionContext) => Promise): Promise => { 34 | return _con.writeTransaction(fn); 35 | }, 36 | close: (ok: any, fail: any) => { 37 | try { 38 | _con.close(); 39 | ok(); 40 | } catch (e) { 41 | fail(e); 42 | } 43 | }, 44 | attach: (dbNameToAttach: string, alias: string, location: string | undefined, callback: () => void) => { 45 | _con.attach(dbNameToAttach, alias, location); 46 | callback(); 47 | }, 48 | detach: (alias, callback: () => void) => { 49 | _con.detach(alias); 50 | callback(); 51 | } 52 | }; 53 | 54 | ok(connection); 55 | 56 | return connection; 57 | } catch (e) { 58 | fail(e); 59 | } 60 | } 61 | }); 62 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import { DBListenerManager } from './DBListenerManager'; 2 | 3 | /** 4 | * Object returned by SQL Query executions { 5 | * insertId: Represent the auto-generated row id if applicable 6 | * rowsAffected: Number of affected rows if result of a update query 7 | * message: if status === 1, here you will find error description 8 | * rows: if status is undefined or 0 this object will contain the query results 9 | * } 10 | * 11 | * @interface QueryResult 12 | */ 13 | export type QueryResult = { 14 | insertId?: number; 15 | rowsAffected: number; 16 | rows?: { 17 | /** Raw array with all dataset */ 18 | _array: any[]; 19 | /** The lengh of the dataset */ 20 | length: number; 21 | /** A convenience function to acess the index based the row object 22 | * @param idx the row index 23 | * @returns the row structure identified by column names 24 | */ 25 | item: (idx: number) => any; 26 | }; 27 | /** 28 | * Query metadata, avaliable only for select query results 29 | */ 30 | metadata?: ColumnMetadata[]; 31 | }; 32 | 33 | /** 34 | * Column metadata 35 | * Describes some information about columns fetched by the query 36 | */ 37 | export type ColumnMetadata = { 38 | /** The name used for this column for this resultset */ 39 | columnName: string; 40 | /** The declared column type for this column, when fetched directly from a table or a View resulting from a table column. "UNKNOWN" for dynamic values, like function returned ones. */ 41 | columnDeclaredType: string; 42 | /** 43 | * The index for this column for this resultset*/ 44 | columnIndex: number; 45 | }; 46 | 47 | /** 48 | * Allows the execution of bulk of sql commands 49 | * inside a transaction 50 | * If a single query must be executed many times with different arguments, its preferred 51 | * to declare it a single time, and use an array of array parameters. 52 | */ 53 | export type SQLBatchTuple = [string] | [string, Array | Array>]; 54 | 55 | /** 56 | * status: 0 or undefined for correct execution, 1 for error 57 | * message: if status === 1, here you will find error description 58 | * rowsAffected: Number of affected rows if status == 0 59 | */ 60 | export type BatchQueryResult = { 61 | rowsAffected?: number; 62 | }; 63 | 64 | /** 65 | * Result of loading a file and executing every line as a SQL command 66 | * Similar to BatchQueryResult 67 | */ 68 | export interface FileLoadResult extends BatchQueryResult { 69 | commands?: number; 70 | } 71 | 72 | export enum RowUpdateType { 73 | SQLITE_INSERT = 18, 74 | SQLITE_DELETE = 9, 75 | SQLITE_UPDATE = 23 76 | } 77 | 78 | export interface TableUpdateOperation { 79 | opType: RowUpdateType; 80 | rowId: number; 81 | } 82 | export interface UpdateNotification extends TableUpdateOperation { 83 | table: string; 84 | } 85 | 86 | export interface BatchedUpdateNotification { 87 | rawUpdates: UpdateNotification[]; 88 | tables: string[]; 89 | groupedUpdates: Record; 90 | } 91 | 92 | export type UpdateCallback = (update: UpdateNotification) => void; 93 | export type BatchedUpdateCallback = (update: BatchedUpdateNotification) => void; 94 | 95 | export enum TransactionEvent { 96 | COMMIT, 97 | ROLLBACK 98 | } 99 | 100 | export type TransactionCallback = (eventType: TransactionEvent) => void; 101 | 102 | export type ContextLockID = string; 103 | 104 | export enum ConcurrentLockType { 105 | READ, 106 | WRITE 107 | } 108 | 109 | export type OpenOptions = { 110 | location?: string; 111 | /** 112 | * The number of concurrent read connections to use. 113 | * Setting this value to zero will only open a single write connection. 114 | * Setting this value > 0 will open the DB in WAL mode with [numReadConnections] 115 | * read connections and a single write connection. This allows for concurrent 116 | * read operations during a write operation. 117 | */ 118 | numReadConnections?: number; 119 | }; 120 | 121 | export type Open = (dbName: string, options?: OpenOptions) => QuickSQLiteConnection; 122 | 123 | export interface ISQLite { 124 | open: Open; 125 | close: (dbName: string) => void; 126 | delete: (dbName: string, location?: string) => void; 127 | refreshSchema: (dbName: string) => Promise; 128 | 129 | requestLock: (dbName: string, id: ContextLockID, type: ConcurrentLockType) => QueryResult; 130 | releaseLock(dbName: string, id: ContextLockID): void; 131 | executeInContext: (dbName: string, id: ContextLockID, query: string, params: any[]) => Promise; 132 | 133 | attach: (mainDbName: string, dbNameToAttach: string, alias: string, location?: string) => void; 134 | detach: (mainDbName: string, alias: string) => void; 135 | 136 | executeBatch: (dbName: string, commands: SQLBatchTuple[], id: ContextLockID) => Promise; 137 | loadFile: (dbName: string, location: string, id: ContextLockID) => Promise; 138 | } 139 | 140 | export interface LockOptions { 141 | timeoutMs?: number; 142 | } 143 | 144 | export interface LockContext { 145 | execute: (sql: string, args?: any[]) => Promise; 146 | } 147 | 148 | export interface TransactionContext extends LockContext { 149 | commit: () => Promise; 150 | rollback: () => Promise; 151 | } 152 | 153 | export type QuickSQLiteConnection = { 154 | close: () => void; 155 | refreshSchema: () => Promise; 156 | execute: (sql: string, args?: any[]) => Promise; 157 | readLock: (callback: (context: LockContext) => Promise, options?: LockOptions) => Promise; 158 | readTransaction: (callback: (context: TransactionContext) => Promise, options?: LockOptions) => Promise; 159 | writeLock: (callback: (context: LockContext) => Promise, options?: LockOptions) => Promise; 160 | writeTransaction: (callback: (context: TransactionContext) => Promise, options?: LockOptions) => Promise; 161 | delete: () => void; 162 | /** 163 | * The attach method should only be called if there are no active locks/transactions. 164 | */ 165 | attach: (dbNameToAttach: string, alias: string, location?: string) => void; 166 | /** 167 | * The detach method should only be called if there are no active locks/transactions. 168 | */ 169 | detach: (alias: string) => void; 170 | executeBatch: (commands: SQLBatchTuple[]) => Promise; 171 | loadFile: (location: string) => Promise; 172 | /** 173 | * Register a callback which will be fired for each ROWID table change event. 174 | * Table changes are reported immediately. 175 | * Changes might not yet be committed if using a transaction. 176 | * - Listen to transaction events in listenerManager if extra logic is required 177 | * For most use cases use `registerTablesChangedHook` instead. 178 | * @returns a function which will deregister the callback 179 | */ 180 | registerUpdateHook(callback: UpdateCallback): () => void; 181 | /** 182 | * Register a callback which will be fired whenever a update to a ROWID table 183 | * has been committed. 184 | * Changes inside write locks will be buffered until the lock is released or 185 | * if a transaction inside the lock has been committed. 186 | * Reverting a transaction inside a write lock will not fire table updates. 187 | * @returns a function which will deregister the callback 188 | */ 189 | registerTablesChangedHook(callback: BatchedUpdateCallback): () => void; 190 | listenerManager: DBListenerManager; 191 | }; 192 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { QueryResult } from './types'; 2 | 3 | // Add 'item' function to result object to allow the sqlite-storage typeorm driver to work 4 | export const enhanceQueryResult = (result: QueryResult): void => { 5 | // Add 'item' function to result object to allow the sqlite-storage typeorm driver to work 6 | if (result.rows == null) { 7 | result.rows = { 8 | _array: [], 9 | length: 0, 10 | item: (idx: number) => result.rows._array[idx] 11 | }; 12 | } else { 13 | result.rows.item = (idx: number) => result.rows._array[idx]; 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /src/utils/BaseObserver.ts: -------------------------------------------------------------------------------- 1 | export interface BaseObserverInterface { 2 | registerListener(listener: Partial): () => void; 3 | } 4 | 5 | export type BaseListener = { 6 | [key: string]: (...event: any) => any; 7 | }; 8 | 9 | export class BaseObserver implements BaseObserverInterface { 10 | protected listeners: Set>; 11 | 12 | constructor() { 13 | this.listeners = new Set(); 14 | } 15 | 16 | registerListener(listener: Partial): () => void { 17 | this.listeners.add(listener); 18 | return () => { 19 | this.listeners.delete(listener); 20 | }; 21 | } 22 | 23 | iterateListeners(cb: (listener: Partial) => any) { 24 | for (const listener of this.listeners) { 25 | cb(listener); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/withUseFrameworks.ts: -------------------------------------------------------------------------------- 1 | const { ConfigPlugin, withDangerousMod, createRunOncePlugin } = require('@expo/config-plugins'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | 5 | // Define package metadata 6 | const pkg = { name: '@journeyapps/react-native-quick-sqlite', version: 'UNVERSIONED' }; 7 | 8 | // Function to modify the Podfile 9 | function modifyPodfile(podfilePath: string) { 10 | let podfile = fs.readFileSync(podfilePath, 'utf8'); 11 | const preinstallScript = ` 12 | pre_install do |installer| 13 | installer.pod_targets.each do |pod| 14 | if pod.name.eql?('react-native-quick-sqlite') 15 | def pod.build_type 16 | Pod::BuildType.static_library 17 | end 18 | end 19 | end 20 | end 21 | `; 22 | // Ensure script is added only once 23 | if (!podfile.includes('react-native-quick-sqlite')) { 24 | podfile = podfile.replace(/target\s+'[^']+'\s+do/, `$&\n${preinstallScript}`); 25 | fs.writeFileSync(podfilePath, podfile, 'utf8'); 26 | console.log(`Added pre_install script for react-native-quick-sqlite to Podfile`); 27 | } 28 | } 29 | 30 | // Config Plugin 31 | const withUseFrameworks = (config, options = { staticLibrary: false }) => { 32 | const { staticLibrary } = options; 33 | 34 | return withDangerousMod(config, [ 35 | 'ios', 36 | (config) => { 37 | if (!staticLibrary) { 38 | return config; 39 | } 40 | 41 | const podfilePath = path.join(config.modRequest.platformProjectRoot, 'Podfile'); 42 | if (fs.existsSync(podfilePath)) { 43 | modifyPodfile(podfilePath); 44 | } else { 45 | console.warn(`Podfile not found at ${podfilePath}`); 46 | } 47 | return config; 48 | } 49 | ]); 50 | }; 51 | 52 | const pluginWithOptions = (config, options) => { 53 | return withUseFrameworks(config, options); 54 | }; 55 | 56 | // Export the plugin with Expo's createRunOncePlugin 57 | module.exports = createRunOncePlugin(pluginWithOptions, pkg.name, pkg.version); 58 | -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | # Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files 2 | 3 | # dependencies 4 | node_modules/ 5 | 6 | # Expo 7 | .expo/ 8 | dist/ 9 | web-build/ 10 | 11 | # Native 12 | *.orig.* 13 | *.jks 14 | *.p8 15 | *.p12 16 | *.key 17 | *.mobileprovision 18 | 19 | # Metro 20 | .metro-health-check* 21 | 22 | # debug 23 | npm-debug.* 24 | yarn-debug.* 25 | yarn-error.* 26 | 27 | # macOS 28 | .DS_Store 29 | *.pem 30 | 31 | # local env files 32 | .env*.local 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | 37 | # Expo 38 | .expo 39 | dist/ 40 | web-build/ -------------------------------------------------------------------------------- /tests/App.tsx: -------------------------------------------------------------------------------- 1 | /// 2 | import React, { useEffect, useState } from 'react'; 3 | import { SafeAreaView, ScrollView, Text } from 'react-native'; 4 | import 'reflect-metadata'; 5 | 6 | import { registerBaseTests, runTests } from './tests/index'; 7 | const TEST_SERVER_URL = 'http://localhost:4243/results'; 8 | 9 | export default function App() { 10 | const [results, setResults] = useState([]); 11 | 12 | const executeTests = React.useCallback(async () => { 13 | setResults([]); 14 | 15 | try { 16 | const results = await runTests(registerBaseTests); 17 | console.log(JSON.stringify(results, null, '\t')); 18 | setResults(results); 19 | // Send results to host server 20 | await fetch(TEST_SERVER_URL, { 21 | method: 'POST', 22 | headers: { 'Content-Type': 'application/json' }, 23 | body: JSON.stringify(results) 24 | }); 25 | } catch (ex) { 26 | console.error(ex); 27 | // Send results to host server 28 | fetch(TEST_SERVER_URL, { 29 | method: 'POST', 30 | headers: { 'Content-Type': 'application/json' }, 31 | body: JSON.stringify([ 32 | { 33 | description: `Caught exception: ${ex}`, 34 | type: 'incorrect' 35 | } 36 | ]) 37 | }); 38 | } 39 | }, []); 40 | 41 | useEffect(() => { 42 | console.log('Running Tests:'); 43 | executeTests(); 44 | }, []); 45 | 46 | return ( 47 | 48 | 49 | RN Quick SQLite Test Suite 50 | {results.map((r: any, i: number) => { 51 | if (r.type === 'grouping') { 52 | return ( 53 | 54 | {r.description} 55 | 56 | ); 57 | } 58 | 59 | if (r.type === 'incorrect') { 60 | return ( 61 | 62 | 🔴 {r.description}: {r.errorMsg} 63 | 64 | ); 65 | } 66 | 67 | return ( 68 | 69 | 🟢 {r.description} 70 | 71 | ); 72 | })} 73 | 74 | 75 | ); 76 | } 77 | -------------------------------------------------------------------------------- /tests/android/.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Android/IntelliJ 6 | # 7 | build/ 8 | .idea 9 | .gradle 10 | local.properties 11 | *.iml 12 | *.hprof 13 | .cxx/ 14 | 15 | # Bundle artifacts 16 | *.jsbundle 17 | -------------------------------------------------------------------------------- /tests/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "com.android.application" 2 | apply plugin: "org.jetbrains.kotlin.android" 3 | apply plugin: "com.facebook.react" 4 | 5 | def projectRoot = rootDir.getAbsoluteFile().getParentFile().getAbsolutePath() 6 | 7 | /** 8 | * This is the configuration block to customize your React Native Android app. 9 | * By default you don't need to apply any configuration, just uncomment the lines you need. 10 | */ 11 | react { 12 | entryFile = file(["node", "-e", "require('expo/scripts/resolveAppEntry')", projectRoot, "android", "absolute"].execute(null, rootDir).text.trim()) 13 | reactNativeDir = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsoluteFile() 14 | hermesCommand = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsolutePath() + "/sdks/hermesc/%OS-BIN%/hermesc" 15 | codegenDir = new File(["node", "--print", "require.resolve('@react-native/codegen/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim()).getParentFile().getAbsoluteFile() 16 | 17 | // Use Expo CLI to bundle the app, this ensures the Metro config 18 | // works correctly with Expo projects. 19 | cliFile = new File(["node", "--print", "require.resolve('@expo/cli', { paths: [require.resolve('expo/package.json')] })"].execute(null, rootDir).text.trim()) 20 | bundleCommand = "export:embed" 21 | 22 | /* Folders */ 23 | // The root of your project, i.e. where "package.json" lives. Default is '../..' 24 | // root = file("../../") 25 | // The folder where the react-native NPM package is. Default is ../../node_modules/react-native 26 | // reactNativeDir = file("../../node_modules/react-native") 27 | // The folder where the react-native Codegen package is. Default is ../../node_modules/@react-native/codegen 28 | // codegenDir = file("../../node_modules/@react-native/codegen") 29 | 30 | /* Variants */ 31 | // The list of variants to that are debuggable. For those we're going to 32 | // skip the bundling of the JS bundle and the assets. By default is just 'debug'. 33 | // If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants. 34 | // debuggableVariants = ["liteDebug", "prodDebug"] 35 | 36 | /* Bundling */ 37 | // A list containing the node command and its flags. Default is just 'node'. 38 | // nodeExecutableAndArgs = ["node"] 39 | 40 | // 41 | // The path to the CLI configuration file. Default is empty. 42 | // bundleConfig = file(../rn-cli.config.js) 43 | // 44 | // The name of the generated asset file containing your JS bundle 45 | // bundleAssetName = "MyApplication.android.bundle" 46 | // 47 | // The entry file for bundle generation. Default is 'index.android.js' or 'index.js' 48 | // entryFile = file("../js/MyApplication.android.js") 49 | // 50 | // A list of extra flags to pass to the 'bundle' commands. 51 | // See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle 52 | // extraPackagerArgs = [] 53 | 54 | /* Hermes Commands */ 55 | // The hermes compiler command to run. By default it is 'hermesc' 56 | // hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc" 57 | // 58 | // The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map" 59 | // hermesFlags = ["-O", "-output-source-map"] 60 | 61 | /* Autolinking */ 62 | autolinkLibrariesWithApp() 63 | } 64 | 65 | /** 66 | * Set this to true to Run Proguard on Release builds to minify the Java bytecode. 67 | */ 68 | def enableProguardInReleaseBuilds = (findProperty('android.enableProguardInReleaseBuilds') ?: false).toBoolean() 69 | 70 | /** 71 | * The preferred build flavor of JavaScriptCore (JSC) 72 | * 73 | * For example, to use the international variant, you can use: 74 | * `def jscFlavor = 'org.webkit:android-jsc-intl:+'` 75 | * 76 | * The international variant includes ICU i18n library and necessary data 77 | * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that 78 | * give correct results when using with locales other than en-US. Note that 79 | * this variant is about 6MiB larger per architecture than default. 80 | */ 81 | def jscFlavor = 'org.webkit:android-jsc:+' 82 | 83 | android { 84 | ndkVersion rootProject.ext.ndkVersion 85 | 86 | buildToolsVersion rootProject.ext.buildToolsVersion 87 | compileSdk rootProject.ext.compileSdkVersion 88 | 89 | namespace 'com.reactnativequicksqlite.tests' 90 | defaultConfig { 91 | applicationId 'com.reactnativequicksqlite.tests' 92 | minSdkVersion rootProject.ext.minSdkVersion 93 | targetSdkVersion rootProject.ext.targetSdkVersion 94 | versionCode 1 95 | versionName "1.0.0" 96 | } 97 | signingConfigs { 98 | debug { 99 | storeFile file('debug.keystore') 100 | storePassword 'android' 101 | keyAlias 'androiddebugkey' 102 | keyPassword 'android' 103 | } 104 | } 105 | buildTypes { 106 | debug { 107 | signingConfig signingConfigs.debug 108 | } 109 | release { 110 | // Caution! In production, you need to generate your own keystore file. 111 | // see https://reactnative.dev/docs/signed-apk-android. 112 | signingConfig signingConfigs.debug 113 | shrinkResources (findProperty('android.enableShrinkResourcesInReleaseBuilds')?.toBoolean() ?: false) 114 | minifyEnabled enableProguardInReleaseBuilds 115 | proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" 116 | crunchPngs (findProperty('android.enablePngCrunchInReleaseBuilds')?.toBoolean() ?: true) 117 | } 118 | } 119 | packagingOptions { 120 | jniLibs { 121 | useLegacyPackaging (findProperty('expo.useLegacyPackaging')?.toBoolean() ?: false) 122 | } 123 | } 124 | androidResources { 125 | ignoreAssetsPattern '!.svn:!.git:!.ds_store:!*.scc:!CVS:!thumbs.db:!picasa.ini:!*~' 126 | } 127 | } 128 | 129 | // Apply static values from `gradle.properties` to the `android.packagingOptions` 130 | // Accepts values in comma delimited lists, example: 131 | // android.packagingOptions.pickFirsts=/LICENSE,**/picasa.ini 132 | ["pickFirsts", "excludes", "merges", "doNotStrip"].each { prop -> 133 | // Split option: 'foo,bar' -> ['foo', 'bar'] 134 | def options = (findProperty("android.packagingOptions.$prop") ?: "").split(","); 135 | // Trim all elements in place. 136 | for (i in 0.. 0) { 141 | println "android.packagingOptions.$prop += $options ($options.length)" 142 | // Ex: android.packagingOptions.pickFirsts += '**/SCCS/**' 143 | options.each { 144 | android.packagingOptions[prop] += it 145 | } 146 | } 147 | } 148 | 149 | dependencies { 150 | // The version of react-native is set by the React Native Gradle Plugin 151 | implementation("com.facebook.react:react-android") 152 | 153 | def isGifEnabled = (findProperty('expo.gif.enabled') ?: "") == "true"; 154 | def isWebpEnabled = (findProperty('expo.webp.enabled') ?: "") == "true"; 155 | def isWebpAnimatedEnabled = (findProperty('expo.webp.animated') ?: "") == "true"; 156 | 157 | if (isGifEnabled) { 158 | // For animated gif support 159 | implementation("com.facebook.fresco:animated-gif:${reactAndroidLibs.versions.fresco.get()}") 160 | } 161 | 162 | if (isWebpEnabled) { 163 | // For webp support 164 | implementation("com.facebook.fresco:webpsupport:${reactAndroidLibs.versions.fresco.get()}") 165 | if (isWebpAnimatedEnabled) { 166 | // Animated webp support 167 | implementation("com.facebook.fresco:animated-webp:${reactAndroidLibs.versions.fresco.get()}") 168 | } 169 | } 170 | 171 | if (hermesEnabled.toBoolean()) { 172 | implementation("com.facebook.react:hermes-android") 173 | } else { 174 | implementation jscFlavor 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /tests/android/app/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powersync-ja/react-native-quick-sqlite/e3f5c79741c23e1f34ba1cdc40e2d56415d4dc45/tests/android/app/debug.keystore -------------------------------------------------------------------------------- /tests/android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # react-native-reanimated 11 | -keep class com.swmansion.reanimated.** { *; } 12 | -keep class com.facebook.react.turbomodule.** { *; } 13 | 14 | # Add any project specific keep options here: 15 | -------------------------------------------------------------------------------- /tests/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /tests/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /tests/android/app/src/main/java/com/reactnativequicksqlite/tests/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.reactnativequicksqlite.tests 2 | import expo.modules.splashscreen.SplashScreenManager 3 | 4 | import android.os.Build 5 | import android.os.Bundle 6 | 7 | import com.facebook.react.ReactActivity 8 | import com.facebook.react.ReactActivityDelegate 9 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled 10 | import com.facebook.react.defaults.DefaultReactActivityDelegate 11 | 12 | import expo.modules.ReactActivityDelegateWrapper 13 | 14 | class MainActivity : ReactActivity() { 15 | override fun onCreate(savedInstanceState: Bundle?) { 16 | // Set the theme to AppTheme BEFORE onCreate to support 17 | // coloring the background, status bar, and navigation bar. 18 | // This is required for expo-splash-screen. 19 | // setTheme(R.style.AppTheme); 20 | // @generated begin expo-splashscreen - expo prebuild (DO NOT MODIFY) sync-f3ff59a738c56c9a6119210cb55f0b613eb8b6af 21 | SplashScreenManager.registerOnActivity(this) 22 | // @generated end expo-splashscreen 23 | super.onCreate(null) 24 | } 25 | 26 | /** 27 | * Returns the name of the main component registered from JavaScript. This is used to schedule 28 | * rendering of the component. 29 | */ 30 | override fun getMainComponentName(): String = "main" 31 | 32 | /** 33 | * Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate] 34 | * which allows you to enable New Architecture with a single boolean flags [fabricEnabled] 35 | */ 36 | override fun createReactActivityDelegate(): ReactActivityDelegate { 37 | return ReactActivityDelegateWrapper( 38 | this, 39 | BuildConfig.IS_NEW_ARCHITECTURE_ENABLED, 40 | object : DefaultReactActivityDelegate( 41 | this, 42 | mainComponentName, 43 | fabricEnabled 44 | ){}) 45 | } 46 | 47 | /** 48 | * Align the back button behavior with Android S 49 | * where moving root activities to background instead of finishing activities. 50 | * @see onBackPressed 51 | */ 52 | override fun invokeDefaultOnBackPressed() { 53 | if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) { 54 | if (!moveTaskToBack(false)) { 55 | // For non-root activities, use the default implementation to finish them. 56 | super.invokeDefaultOnBackPressed() 57 | } 58 | return 59 | } 60 | 61 | // Use the default back button implementation on Android S 62 | // because it's doing more than [Activity.moveTaskToBack] in fact. 63 | super.invokeDefaultOnBackPressed() 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tests/android/app/src/main/java/com/reactnativequicksqlite/tests/MainApplication.kt: -------------------------------------------------------------------------------- 1 | package com.reactnativequicksqlite.tests 2 | 3 | import android.app.Application 4 | import android.content.res.Configuration 5 | 6 | import com.facebook.react.PackageList 7 | import com.facebook.react.ReactApplication 8 | import com.facebook.react.ReactNativeHost 9 | import com.facebook.react.ReactPackage 10 | import com.facebook.react.ReactHost 11 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load 12 | import com.facebook.react.defaults.DefaultReactNativeHost 13 | import com.facebook.react.soloader.OpenSourceMergedSoMapping 14 | import com.facebook.soloader.SoLoader 15 | 16 | import expo.modules.ApplicationLifecycleDispatcher 17 | import expo.modules.ReactNativeHostWrapper 18 | 19 | class MainApplication : Application(), ReactApplication { 20 | 21 | override val reactNativeHost: ReactNativeHost = ReactNativeHostWrapper( 22 | this, 23 | object : DefaultReactNativeHost(this) { 24 | override fun getPackages(): List { 25 | val packages = PackageList(this).packages 26 | // Packages that cannot be autolinked yet can be added manually here, for example: 27 | // packages.add(new MyReactNativePackage()); 28 | return packages 29 | } 30 | 31 | override fun getJSMainModuleName(): String = ".expo/.virtual-metro-entry" 32 | 33 | override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG 34 | 35 | override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED 36 | override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED 37 | } 38 | ) 39 | 40 | override val reactHost: ReactHost 41 | get() = ReactNativeHostWrapper.createReactHost(applicationContext, reactNativeHost) 42 | 43 | override fun onCreate() { 44 | super.onCreate() 45 | SoLoader.init(this, OpenSourceMergedSoMapping) 46 | if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { 47 | // If you opted-in for the New Architecture, we load the native entry point for this app. 48 | load() 49 | } 50 | ApplicationLifecycleDispatcher.onApplicationCreate(this) 51 | } 52 | 53 | override fun onConfigurationChanged(newConfig: Configuration) { 54 | super.onConfigurationChanged(newConfig) 55 | ApplicationLifecycleDispatcher.onConfigurationChanged(this, newConfig) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /tests/android/app/src/main/res/drawable-hdpi/splashscreen_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powersync-ja/react-native-quick-sqlite/e3f5c79741c23e1f34ba1cdc40e2d56415d4dc45/tests/android/app/src/main/res/drawable-hdpi/splashscreen_logo.png -------------------------------------------------------------------------------- /tests/android/app/src/main/res/drawable-mdpi/splashscreen_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powersync-ja/react-native-quick-sqlite/e3f5c79741c23e1f34ba1cdc40e2d56415d4dc45/tests/android/app/src/main/res/drawable-mdpi/splashscreen_logo.png -------------------------------------------------------------------------------- /tests/android/app/src/main/res/drawable-xhdpi/splashscreen_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powersync-ja/react-native-quick-sqlite/e3f5c79741c23e1f34ba1cdc40e2d56415d4dc45/tests/android/app/src/main/res/drawable-xhdpi/splashscreen_logo.png -------------------------------------------------------------------------------- /tests/android/app/src/main/res/drawable-xxhdpi/splashscreen_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powersync-ja/react-native-quick-sqlite/e3f5c79741c23e1f34ba1cdc40e2d56415d4dc45/tests/android/app/src/main/res/drawable-xxhdpi/splashscreen_logo.png -------------------------------------------------------------------------------- /tests/android/app/src/main/res/drawable-xxxhdpi/splashscreen_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powersync-ja/react-native-quick-sqlite/e3f5c79741c23e1f34ba1cdc40e2d56415d4dc45/tests/android/app/src/main/res/drawable-xxxhdpi/splashscreen_logo.png -------------------------------------------------------------------------------- /tests/android/app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /tests/android/app/src/main/res/drawable/rn_edit_text_material.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 22 | 23 | 24 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /tests/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /tests/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /tests/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powersync-ja/react-native-quick-sqlite/e3f5c79741c23e1f34ba1cdc40e2d56415d4dc45/tests/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /tests/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powersync-ja/react-native-quick-sqlite/e3f5c79741c23e1f34ba1cdc40e2d56415d4dc45/tests/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /tests/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powersync-ja/react-native-quick-sqlite/e3f5c79741c23e1f34ba1cdc40e2d56415d4dc45/tests/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /tests/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powersync-ja/react-native-quick-sqlite/e3f5c79741c23e1f34ba1cdc40e2d56415d4dc45/tests/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /tests/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powersync-ja/react-native-quick-sqlite/e3f5c79741c23e1f34ba1cdc40e2d56415d4dc45/tests/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /tests/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powersync-ja/react-native-quick-sqlite/e3f5c79741c23e1f34ba1cdc40e2d56415d4dc45/tests/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /tests/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powersync-ja/react-native-quick-sqlite/e3f5c79741c23e1f34ba1cdc40e2d56415d4dc45/tests/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /tests/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powersync-ja/react-native-quick-sqlite/e3f5c79741c23e1f34ba1cdc40e2d56415d4dc45/tests/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /tests/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powersync-ja/react-native-quick-sqlite/e3f5c79741c23e1f34ba1cdc40e2d56415d4dc45/tests/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /tests/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powersync-ja/react-native-quick-sqlite/e3f5c79741c23e1f34ba1cdc40e2d56415d4dc45/tests/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /tests/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powersync-ja/react-native-quick-sqlite/e3f5c79741c23e1f34ba1cdc40e2d56415d4dc45/tests/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /tests/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powersync-ja/react-native-quick-sqlite/e3f5c79741c23e1f34ba1cdc40e2d56415d4dc45/tests/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /tests/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powersync-ja/react-native-quick-sqlite/e3f5c79741c23e1f34ba1cdc40e2d56415d4dc45/tests/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /tests/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powersync-ja/react-native-quick-sqlite/e3f5c79741c23e1f34ba1cdc40e2d56415d4dc45/tests/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /tests/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powersync-ja/react-native-quick-sqlite/e3f5c79741c23e1f34ba1cdc40e2d56415d4dc45/tests/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /tests/android/app/src/main/res/values-night/colors.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/android/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | #ffffff 3 | #ffffff 4 | #023c69 5 | #ffffff 6 | -------------------------------------------------------------------------------- /tests/android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | tests 3 | contain 4 | false 5 | -------------------------------------------------------------------------------- /tests/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 14 | 19 | -------------------------------------------------------------------------------- /tests/android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext { 5 | buildToolsVersion = findProperty('android.buildToolsVersion') ?: '35.0.0' 6 | minSdkVersion = Integer.parseInt(findProperty('android.minSdkVersion') ?: '24') 7 | compileSdkVersion = Integer.parseInt(findProperty('android.compileSdkVersion') ?: '35') 8 | targetSdkVersion = Integer.parseInt(findProperty('android.targetSdkVersion') ?: '34') 9 | kotlinVersion = findProperty('android.kotlinVersion') ?: '1.9.24' 10 | 11 | ndkVersion = "26.1.10909125" 12 | } 13 | repositories { 14 | google() 15 | mavenCentral() 16 | } 17 | dependencies { 18 | classpath('com.android.tools.build:gradle') 19 | classpath('com.facebook.react:react-native-gradle-plugin') 20 | classpath('org.jetbrains.kotlin:kotlin-gradle-plugin') 21 | } 22 | } 23 | 24 | apply plugin: "com.facebook.react.rootproject" 25 | 26 | allprojects { 27 | repositories { 28 | maven { 29 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm 30 | url(new File(['node', '--print', "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim(), '../android')) 31 | } 32 | maven { 33 | // Android JSC is installed from npm 34 | url(new File(['node', '--print', "require.resolve('jsc-android/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim(), '../dist')) 35 | } 36 | 37 | google() 38 | mavenCentral() 39 | maven { url 'https://www.jitpack.io' } 40 | } 41 | } 42 | 43 | -------------------------------------------------------------------------------- /tests/android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx512m -XX:MaxMetaspaceSize=256m 13 | org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | # AndroidX package structure to make it clearer which packages are bundled with the 21 | # Android operating system, and which are packaged with your app's APK 22 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 23 | android.useAndroidX=true 24 | 25 | # Enable AAPT2 PNG crunching 26 | android.enablePngCrunchInReleaseBuilds=true 27 | 28 | # Use this property to specify which architecture you want to build. 29 | # You can also override it from the CLI using 30 | # ./gradlew -PreactNativeArchitectures=x86_64 31 | reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64 32 | 33 | # Use this property to enable support to the new architecture. 34 | # This will allow you to use TurboModules and the Fabric render in 35 | # your application. You should enable this flag either if you want 36 | # to write custom TurboModules/Fabric components OR use libraries that 37 | # are providing them. 38 | newArchEnabled=false 39 | 40 | # Use this property to enable or disable the Hermes JS engine. 41 | # If set to false, you will be using JSC instead. 42 | hermesEnabled=true 43 | 44 | # Enable GIF support in React Native images (~200 B increase) 45 | expo.gif.enabled=true 46 | # Enable webp support in React Native images (~85 KB increase) 47 | expo.webp.enabled=true 48 | # Enable animated webp support (~3.4 MB increase) 49 | # Disabled by default because iOS doesn't support animated webp 50 | expo.webp.animated=false 51 | 52 | # Enable network inspector 53 | EX_DEV_CLIENT_NETWORK_INSPECTOR=true 54 | 55 | # Use legacy packaging to compress native libraries in the resulting APK. 56 | expo.useLegacyPackaging=false 57 | 58 | android.extraMavenRepos=[] -------------------------------------------------------------------------------- /tests/android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powersync-ja/react-native-quick-sqlite/e3f5c79741c23e1f34ba1cdc40e2d56415d4dc45/tests/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /tests/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /tests/android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /tests/android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | includeBuild(new File(["node", "--print", "require.resolve('@react-native/gradle-plugin/package.json')"].execute(null, rootDir).text.trim()).getParentFile().toString()) 3 | } 4 | plugins { id("com.facebook.react.settings") } 5 | 6 | extensions.configure(com.facebook.react.ReactSettingsExtension) { ex -> 7 | if (System.getenv('EXPO_USE_COMMUNITY_AUTOLINKING') == '1') { 8 | ex.autolinkLibrariesFromCommand() 9 | } else { 10 | def command = [ 11 | 'node', 12 | '--no-warnings', 13 | '--eval', 14 | 'require(require.resolve(\'expo-modules-autolinking\', { paths: [require.resolve(\'expo/package.json\')] }))(process.argv.slice(1))', 15 | 'react-native-config', 16 | '--json', 17 | '--platform', 18 | 'android' 19 | ].toList() 20 | ex.autolinkLibrariesFromCommand(command) 21 | } 22 | } 23 | 24 | rootProject.name = 'tests' 25 | 26 | dependencyResolutionManagement { 27 | versionCatalogs { 28 | reactAndroidLibs { 29 | from(files(new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim(), "../gradle/libs.versions.toml"))) 30 | } 31 | } 32 | } 33 | 34 | apply from: new File(["node", "--print", "require.resolve('expo/package.json')"].execute(null, rootDir).text.trim(), "../scripts/autolinking.gradle"); 35 | useExpoModules() 36 | 37 | include ':app' 38 | includeBuild(new File(["node", "--print", "require.resolve('@react-native/gradle-plugin/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim()).getParentFile()) 39 | -------------------------------------------------------------------------------- /tests/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "tests", 4 | "slug": "tests", 5 | "version": "1.0.0", 6 | "orientation": "portrait", 7 | "icon": "./assets/icon.png", 8 | "userInterfaceStyle": "light", 9 | "newArchEnabled": false, 10 | "splash": { 11 | "image": "./assets/splash.png", 12 | "resizeMode": "contain", 13 | "backgroundColor": "#ffffff" 14 | }, 15 | "ios": { 16 | "supportsTablet": true, 17 | "bundleIdentifier": "com.reactnativequicksqlite.tests" 18 | }, 19 | "android": { 20 | "adaptiveIcon": { 21 | "foregroundImage": "./assets/adaptive-icon.png", 22 | "backgroundColor": "#ffffff" 23 | }, 24 | "package": "com.reactnativequicksqlite.tests" 25 | }, 26 | "web": { 27 | "favicon": "./assets/favicon.png" 28 | }, 29 | "plugins": [ 30 | "expo-build-properties" 31 | ] 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/assets/adaptive-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powersync-ja/react-native-quick-sqlite/e3f5c79741c23e1f34ba1cdc40e2d56415d4dc45/tests/assets/adaptive-icon.png -------------------------------------------------------------------------------- /tests/assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powersync-ja/react-native-quick-sqlite/e3f5c79741c23e1f34ba1cdc40e2d56415d4dc45/tests/assets/favicon.png -------------------------------------------------------------------------------- /tests/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powersync-ja/react-native-quick-sqlite/e3f5c79741c23e1f34ba1cdc40e2d56415d4dc45/tests/assets/icon.png -------------------------------------------------------------------------------- /tests/assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powersync-ja/react-native-quick-sqlite/e3f5c79741c23e1f34ba1cdc40e2d56415d4dc45/tests/assets/splash.png -------------------------------------------------------------------------------- /tests/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.cache(true); 3 | return { 4 | presets: ['babel-preset-expo'], 5 | plugins: [ 6 | '@babel/plugin-transform-class-static-block', 7 | [ 8 | 'module-resolver', 9 | { 10 | alias: { 11 | stream: 'stream-browserify', 12 | 'react-native-sqlite-storage': 'react-native-quick-sqlite' 13 | } 14 | } 15 | ] 16 | ] 17 | }; 18 | }; 19 | -------------------------------------------------------------------------------- /tests/index.js: -------------------------------------------------------------------------------- 1 | import { registerRootComponent } from 'expo'; 2 | import App from './App'; 3 | import { Buffer } from '@craftzdog/react-native-buffer'; 4 | 5 | global.Buffer = Buffer; 6 | global.process.cwd = () => 'sxsx'; 7 | //The following statements cause an error on Expo 52/RN >0.76 8 | // global.process.env = { NODE_ENV: 'production' }; 9 | // global.location = {}; 10 | 11 | // registerRootComponent calls AppRegistry.registerComponent('main', () => App); 12 | // It also ensures that whether you load the app in Expo Go or in a native build, 13 | // the environment is set up appropriately 14 | registerRootComponent(App); 15 | -------------------------------------------------------------------------------- /tests/ios/.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | project.xcworkspace 24 | .xcode.env.local 25 | 26 | # Bundle artifacts 27 | *.jsbundle 28 | 29 | # CocoaPods 30 | /Pods/ 31 | -------------------------------------------------------------------------------- /tests/ios/.xcode.env: -------------------------------------------------------------------------------- 1 | # This `.xcode.env` file is versioned and is used to source the environment 2 | # used when running script phases inside Xcode. 3 | # To customize your local environment, you can create an `.xcode.env.local` 4 | # file that is not versioned. 5 | 6 | # NODE_BINARY variable contains the PATH to the node executable. 7 | # 8 | # Customize the NODE_BINARY variable here. 9 | # For example, to use nvm with brew, add the following line 10 | # . "$(brew --prefix nvm)/nvm.sh" --no-use 11 | export NODE_BINARY=$(command -v node) 12 | -------------------------------------------------------------------------------- /tests/ios/Podfile: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(`node --print "require.resolve('expo/package.json')"`), "scripts/autolinking") 2 | require File.join(File.dirname(`node --print "require.resolve('react-native/package.json')"`), "scripts/react_native_pods") 3 | 4 | require 'json' 5 | podfile_properties = JSON.parse(File.read(File.join(__dir__, 'Podfile.properties.json'))) rescue {} 6 | 7 | ENV['RCT_NEW_ARCH_ENABLED'] = podfile_properties['newArchEnabled'] == 'true' ? '1' : '0' 8 | ENV['EX_DEV_CLIENT_NETWORK_INSPECTOR'] = podfile_properties['EX_DEV_CLIENT_NETWORK_INSPECTOR'] 9 | 10 | platform :ios, podfile_properties['ios.deploymentTarget'] || '15.1' 11 | install! 'cocoapods', 12 | :deterministic_uuids => false 13 | 14 | prepare_react_native_project! 15 | 16 | target 'tests' do 17 | use_expo_modules! 18 | 19 | if ENV['EXPO_USE_COMMUNITY_AUTOLINKING'] == '1' 20 | config_command = ['node', '-e', "process.argv=['', '', 'config'];require('@react-native-community/cli').run()"]; 21 | else 22 | config_command = [ 23 | 'node', 24 | '--no-warnings', 25 | '--eval', 26 | 'require(require.resolve(\'expo-modules-autolinking\', { paths: [require.resolve(\'expo/package.json\')] }))(process.argv.slice(1))', 27 | 'react-native-config', 28 | '--json', 29 | '--platform', 30 | 'ios' 31 | ] 32 | end 33 | 34 | config = use_native_modules!(config_command) 35 | 36 | use_frameworks! :linkage => podfile_properties['ios.useFrameworks'].to_sym if podfile_properties['ios.useFrameworks'] 37 | use_frameworks! :linkage => ENV['USE_FRAMEWORKS'].to_sym if ENV['USE_FRAMEWORKS'] 38 | 39 | use_react_native!( 40 | :path => config[:reactNativePath], 41 | :hermes_enabled => podfile_properties['expo.jsEngine'] == nil || podfile_properties['expo.jsEngine'] == 'hermes', 42 | # An absolute path to your application root. 43 | :app_path => "#{Pod::Config.instance.installation_root}/..", 44 | :privacy_file_aggregation_enabled => podfile_properties['apple.privacyManifestAggregationEnabled'] != 'false', 45 | ) 46 | 47 | post_install do |installer| 48 | react_native_post_install( 49 | installer, 50 | config[:reactNativePath], 51 | :mac_catalyst_enabled => false, 52 | :ccache_enabled => podfile_properties['apple.ccacheEnabled'] == 'true', 53 | ) 54 | 55 | # This is necessary for Xcode 14, because it signs resource bundles by default 56 | # when building for devices. 57 | installer.target_installation_results.pod_target_installation_results 58 | .each do |pod_name, target_installation_result| 59 | target_installation_result.resource_bundle_targets.each do |resource_bundle_target| 60 | resource_bundle_target.build_configurations.each do |config| 61 | config.build_settings['CODE_SIGNING_ALLOWED'] = 'NO' 62 | end 63 | end 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /tests/ios/Podfile.properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo.jsEngine": "hermes", 3 | "EX_DEV_CLIENT_NETWORK_INSPECTOR": "true", 4 | "newArchEnabled": "false", 5 | "apple.extraPods": "[]", 6 | "apple.ccacheEnabled": "false", 7 | "apple.privacyManifestAggregationEnabled": "true" 8 | } 9 | -------------------------------------------------------------------------------- /tests/ios/tests.xcodeproj/xcshareddata/xcschemes/tests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 53 | 55 | 61 | 62 | 63 | 64 | 70 | 72 | 78 | 79 | 80 | 81 | 83 | 84 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /tests/ios/tests.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /tests/ios/tests/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import 4 | 5 | @interface AppDelegate : EXAppDelegateWrapper 6 | 7 | @end 8 | -------------------------------------------------------------------------------- /tests/ios/tests/AppDelegate.mm: -------------------------------------------------------------------------------- 1 | #import "AppDelegate.h" 2 | 3 | #import 4 | #import 5 | 6 | @implementation AppDelegate 7 | 8 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 9 | { 10 | self.moduleName = @"main"; 11 | 12 | // You can add your custom initial props in the dictionary below. 13 | // They will be passed down to the ViewController used by React Native. 14 | self.initialProps = @{}; 15 | 16 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 17 | } 18 | 19 | - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge 20 | { 21 | return [self bundleURL]; 22 | } 23 | 24 | - (NSURL *)bundleURL 25 | { 26 | #if DEBUG 27 | return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@".expo/.virtual-metro-entry"]; 28 | #else 29 | return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; 30 | #endif 31 | } 32 | 33 | // Linking API 34 | - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary *)options { 35 | return [super application:application openURL:url options:options] || [RCTLinkingManager application:application openURL:url options:options]; 36 | } 37 | 38 | // Universal Links 39 | - (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray> * _Nullable))restorationHandler { 40 | BOOL result = [RCTLinkingManager application:application continueUserActivity:userActivity restorationHandler:restorationHandler]; 41 | return [super application:application continueUserActivity:userActivity restorationHandler:restorationHandler] || result; 42 | } 43 | 44 | // Explicitly define remote notification delegates to ensure compatibility with some third-party libraries 45 | - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken 46 | { 47 | return [super application:application didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; 48 | } 49 | 50 | // Explicitly define remote notification delegates to ensure compatibility with some third-party libraries 51 | - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error 52 | { 53 | return [super application:application didFailToRegisterForRemoteNotificationsWithError:error]; 54 | } 55 | 56 | // Explicitly define remote notification delegates to ensure compatibility with some third-party libraries 57 | - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler 58 | { 59 | return [super application:application didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler]; 60 | } 61 | 62 | @end 63 | -------------------------------------------------------------------------------- /tests/ios/tests/Images.xcassets/AppIcon.appiconset/App-Icon-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powersync-ja/react-native-quick-sqlite/e3f5c79741c23e1f34ba1cdc40e2d56415d4dc45/tests/ios/tests/Images.xcassets/AppIcon.appiconset/App-Icon-1024x1024@1x.png -------------------------------------------------------------------------------- /tests/ios/tests/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "filename": "App-Icon-1024x1024@1x.png", 5 | "idiom": "universal", 6 | "platform": "ios", 7 | "size": "1024x1024" 8 | } 9 | ], 10 | "info": { 11 | "version": 1, 12 | "author": "expo" 13 | } 14 | } -------------------------------------------------------------------------------- /tests/ios/tests/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "expo" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /tests/ios/tests/Images.xcassets/SplashScreenBackground.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors": [ 3 | { 4 | "color": { 5 | "components": { 6 | "alpha": "1.000", 7 | "blue": "1.00000000000000", 8 | "green": "1.00000000000000", 9 | "red": "1.00000000000000" 10 | }, 11 | "color-space": "srgb" 12 | }, 13 | "idiom": "universal" 14 | } 15 | ], 16 | "info": { 17 | "version": 1, 18 | "author": "expo" 19 | } 20 | } -------------------------------------------------------------------------------- /tests/ios/tests/Images.xcassets/SplashScreenLogo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "idiom": "universal", 5 | "filename": "image.png", 6 | "scale": "1x" 7 | }, 8 | { 9 | "idiom": "universal", 10 | "filename": "image@2x.png", 11 | "scale": "2x" 12 | }, 13 | { 14 | "idiom": "universal", 15 | "filename": "image@3x.png", 16 | "scale": "3x" 17 | } 18 | ], 19 | "info": { 20 | "version": 1, 21 | "author": "expo" 22 | } 23 | } -------------------------------------------------------------------------------- /tests/ios/tests/Images.xcassets/SplashScreenLogo.imageset/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powersync-ja/react-native-quick-sqlite/e3f5c79741c23e1f34ba1cdc40e2d56415d4dc45/tests/ios/tests/Images.xcassets/SplashScreenLogo.imageset/image.png -------------------------------------------------------------------------------- /tests/ios/tests/Images.xcassets/SplashScreenLogo.imageset/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powersync-ja/react-native-quick-sqlite/e3f5c79741c23e1f34ba1cdc40e2d56415d4dc45/tests/ios/tests/Images.xcassets/SplashScreenLogo.imageset/image@2x.png -------------------------------------------------------------------------------- /tests/ios/tests/Images.xcassets/SplashScreenLogo.imageset/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/powersync-ja/react-native-quick-sqlite/e3f5c79741c23e1f34ba1cdc40e2d56415d4dc45/tests/ios/tests/Images.xcassets/SplashScreenLogo.imageset/image@3x.png -------------------------------------------------------------------------------- /tests/ios/tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CADisableMinimumFrameDurationOnPhone 6 | 7 | CFBundleDevelopmentRegion 8 | $(DEVELOPMENT_LANGUAGE) 9 | CFBundleDisplayName 10 | tests 11 | CFBundleExecutable 12 | $(EXECUTABLE_NAME) 13 | CFBundleIdentifier 14 | $(PRODUCT_BUNDLE_IDENTIFIER) 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | $(PRODUCT_NAME) 19 | CFBundlePackageType 20 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 21 | CFBundleShortVersionString 22 | 1.0.0 23 | CFBundleSignature 24 | ???? 25 | CFBundleURLTypes 26 | 27 | 28 | CFBundleURLSchemes 29 | 30 | com.reactnativequicksqlite.tests 31 | 32 | 33 | 34 | CFBundleVersion 35 | 1 36 | LSMinimumSystemVersion 37 | 12.0 38 | LSRequiresIPhoneOS 39 | 40 | NSAppTransportSecurity 41 | 42 | NSAllowsArbitraryLoads 43 | 44 | NSAllowsLocalNetworking 45 | 46 | 47 | UILaunchStoryboardName 48 | SplashScreen 49 | UIRequiredDeviceCapabilities 50 | 51 | arm64 52 | 53 | UIRequiresFullScreen 54 | 55 | UIStatusBarStyle 56 | UIStatusBarStyleDefault 57 | UISupportedInterfaceOrientations 58 | 59 | UIInterfaceOrientationPortrait 60 | UIInterfaceOrientationPortraitUpsideDown 61 | 62 | UISupportedInterfaceOrientations~ipad 63 | 64 | UIInterfaceOrientationPortrait 65 | UIInterfaceOrientationPortraitUpsideDown 66 | UIInterfaceOrientationLandscapeLeft 67 | UIInterfaceOrientationLandscapeRight 68 | 69 | UIUserInterfaceStyle 70 | Light 71 | UIViewControllerBasedStatusBarAppearance 72 | 73 | 74 | -------------------------------------------------------------------------------- /tests/ios/tests/PrivacyInfo.xcprivacy: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPrivacyAccessedAPITypes 6 | 7 | 8 | NSPrivacyAccessedAPIType 9 | NSPrivacyAccessedAPICategoryUserDefaults 10 | NSPrivacyAccessedAPITypeReasons 11 | 12 | CA92.1 13 | 14 | 15 | 16 | NSPrivacyAccessedAPIType 17 | NSPrivacyAccessedAPICategoryFileTimestamp 18 | NSPrivacyAccessedAPITypeReasons 19 | 20 | 0A2A.1 21 | 3B52.1 22 | C617.1 23 | 24 | 25 | 26 | NSPrivacyAccessedAPIType 27 | NSPrivacyAccessedAPICategoryDiskSpace 28 | NSPrivacyAccessedAPITypeReasons 29 | 30 | E174.1 31 | 85F4.1 32 | 33 | 34 | 35 | NSPrivacyAccessedAPIType 36 | NSPrivacyAccessedAPICategorySystemBootTime 37 | NSPrivacyAccessedAPITypeReasons 38 | 39 | 35F9.1 40 | 41 | 42 | 43 | NSPrivacyCollectedDataTypes 44 | 45 | NSPrivacyTracking 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /tests/ios/tests/SplashScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /tests/ios/tests/Supporting/Expo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | EXUpdatesCheckOnLaunch 6 | ALWAYS 7 | EXUpdatesEnabled 8 | 9 | EXUpdatesLaunchWaitMs 10 | 0 11 | 12 | -------------------------------------------------------------------------------- /tests/ios/tests/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char * argv[]) { 6 | @autoreleasepool { 7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 8 | } 9 | } 10 | 11 | -------------------------------------------------------------------------------- /tests/ios/tests/noop-file.swift: -------------------------------------------------------------------------------- 1 | // 2 | // @generated 3 | // A blank Swift file must be created for native modules with Swift files to work correctly. 4 | // 5 | -------------------------------------------------------------------------------- /tests/ios/tests/tests-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | -------------------------------------------------------------------------------- /tests/ios/tests/tests.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /tests/metro.config.js: -------------------------------------------------------------------------------- 1 | // // Learn more https://docs.expo.dev/guides/monorepos 2 | const { getDefaultConfig } = require('expo/metro-config'); 3 | const path = require('node:path'); 4 | 5 | // // Find the project and workspace directories 6 | const projectRoot = __dirname; 7 | const workspaceRoot = path.resolve(projectRoot, '..'); 8 | 9 | const config = getDefaultConfig(projectRoot); 10 | 11 | // 1. Watch all files within the monorepo 12 | config.watchFolders = [workspaceRoot]; 13 | // 2. Let Metro know where to resolve packages and in what order 14 | config.resolver.nodeModulesPaths = [ 15 | path.resolve(projectRoot, 'node_modules'), 16 | path.resolve(workspaceRoot, 'node_modules') 17 | ]; 18 | // // #3 - Force resolving nested modules to the folders below 19 | config.resolver.disableHierarchicalLookup = true; 20 | config.resolver.unstable_enableSymlinks = true; 21 | 22 | config.transformer.getTransformOptions = async () => ({ 23 | transform: { 24 | experimentalImportSupport: false, 25 | inlineRequires: true 26 | } 27 | }); 28 | 29 | module.exports = config; 30 | -------------------------------------------------------------------------------- /tests/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tests", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "scripts": { 6 | "android": "expo run:android", 7 | "ios": "expo run:ios", 8 | "test-android": "node scripts/test.js run-android", 9 | "test-ios": "node scripts/test.js run-ios", 10 | "build-ios": "react-native build-ios" 11 | }, 12 | "dependencies": { 13 | "@craftzdog/react-native-buffer": "^6.0.5", 14 | "base-64": "^1.0.0", 15 | "base64-arraybuffer": "^1.0.2", 16 | "chai": "^5.1.2", 17 | "chance": "^1.1.9", 18 | "events": "^3.3.0", 19 | "expo": "~52.0.26", 20 | "expo-build-properties": "~0.13.2", 21 | "expo-splash-screen": "~0.29.21", 22 | "expo-status-bar": "~2.0.1", 23 | "lodash": "^4.17.21", 24 | "mocha": "^10.1.0", 25 | "nativewind": "^2.0.11", 26 | "p-defer": "^4.0.1", 27 | "react": "18.3.1", 28 | "react-native": "0.76.6", 29 | "react-native-quick-sqlite": "link:..", 30 | "react-native-safe-area-context": "4.12.0", 31 | "reflect-metadata": "^0.1.13", 32 | "stream-browserify": "^3.0.0", 33 | "tailwindcss": "^3.2.4", 34 | "typeorm": "^0.3.11", 35 | "util": "^0.12.5" 36 | }, 37 | "devDependencies": { 38 | "@babel/core": "^7.20.0", 39 | "@babel/plugin-transform-class-static-block": "^7.26.0", 40 | "@react-native-community/cli": "^15.1.2", 41 | "@types/chai": "^5.0.1", 42 | "@types/chai-as-promised": "^8.0.1", 43 | "@types/chance": "^1.1.3", 44 | "@types/express": "^5.0.0", 45 | "@types/lodash": "^4.17.1", 46 | "@types/mocha": "^10.0.1", 47 | "@types/react": "~18.3.12", 48 | "babel-plugin-module-resolver": "^4.1.0", 49 | "body-parser": "^1.20.2", 50 | "chai-as-promised": "^8.0.1", 51 | "chalk": "4.1.2", 52 | "commander": "^11.1.0", 53 | "express": "^4.21.1", 54 | "typescript": "^5.3.3" 55 | }, 56 | "private": true 57 | } 58 | -------------------------------------------------------------------------------- /tests/scripts/test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This scripts acts as a simple server to get test results from 3 | * an Android simulator. 4 | */ 5 | 6 | const { spawn } = require('child_process'); 7 | const express = require('express'); 8 | const bodyParser = require('body-parser'); 9 | const _ = require('lodash'); 10 | const chalk = require('chalk'); 11 | const { program } = require('commander'); 12 | 13 | const DEFAULT_AVD_NAME = 'Pixel_3a_API_34_extension_level_7_arm64-v8a'; 14 | const DEFAULT_SIMULATOR_NAME = 'iPhone 15 Pro Max'; 15 | const DEFAULT_PORT = 4243; 16 | const TEST_TIMEOUT = 1_800_000; // 30 minutes 17 | 18 | program.name('Test Suite').description('Automates tests for React Native app based tests'); 19 | 20 | program 21 | .command('run-android') 22 | .option( 23 | '--avdName ', 24 | 'The virtual android device name (the adb device name will be fetched from this)', 25 | DEFAULT_AVD_NAME 26 | ) 27 | .option('--device', 'Use a physical device instead of emulator') 28 | .option('--port', 'Port to run Express HTTP server for getting results on.', DEFAULT_PORT) 29 | .action(async (str, options) => { 30 | const opts = options.opts(); 31 | const avdName = opts.avdName; 32 | const useDevice = opts.device; 33 | const deviceName = await getADBDeviceName(avdName, useDevice); 34 | if (!deviceName) { 35 | throw new Error(`Could not find adb device with AVD name ${avdName}`); 36 | } 37 | 38 | const port = opts.port; 39 | /** Expose the Express server on the Android device */ 40 | await spawnP('Reverse Port', `adb`, [`-s`, deviceName, `reverse`, `tcp:${port}`, `tcp:${port}`]); 41 | 42 | /** Build and run the Expo app, don't await this, we will await a response. */ 43 | if (!useDevice) { 44 | spawnP('Build Expo App', `yarn`, [`android`, `-d`, avdName]); 45 | } else { 46 | spawnP('Build Expo App', `yarn`, [`android`]); 47 | } 48 | 49 | const app = express(); 50 | app.use(bodyParser.json()); 51 | 52 | const resultsPromise = receiveResults(app); 53 | 54 | /** Listen for results */ 55 | const server = app.listen(port); 56 | await resultsPromise; 57 | server.close(); 58 | 59 | console.log('Done with tests'); 60 | process.exit(0); 61 | }); 62 | 63 | program 64 | .command('run-ios') 65 | .option('--simulatorName ', 'The iOS simulator name (e.g., "iPhone 11")', DEFAULT_SIMULATOR_NAME) 66 | .option('--port', 'Port to run Express HTTP server for getting results on.', DEFAULT_PORT) 67 | .action(async (str, options) => { 68 | const opts = options.opts(); 69 | const simulatorName = opts.simulatorName; 70 | const deviceName = await getSimulatorDeviceName(simulatorName); 71 | if (!deviceName) { 72 | throw new Error(`Could not find iOS simulator with name ${simulatorName}`); 73 | } 74 | 75 | const port = opts.port; 76 | const app = express(); 77 | app.use(bodyParser.json()); 78 | 79 | const resultsPromise = receiveResults(app); 80 | 81 | /** Listen for results */ 82 | const server = app.listen(port); 83 | 84 | /** Build and run the Expo app, don't await this, we will await a response. */ 85 | spawnP('Build Expo App', 'yarn', ['ios']); 86 | 87 | await resultsPromise; 88 | server.close(); 89 | 90 | console.log('Done with tests'); 91 | process.exit(0); 92 | }); 93 | 94 | program.parse(); 95 | 96 | async function spawnP(tag, cmd, args = []) { 97 | return new Promise((resolve, reject) => { 98 | console.log(`Executing command: ${cmd} ${args.join(' ')}`); 99 | const runner = spawn(cmd, args); 100 | let stdout = ''; 101 | runner.stdout.on('data', (data) => { 102 | console.log(`[${tag}]: ${data}`); 103 | stdout += data; 104 | }); 105 | runner.stderr.on('data', (data) => console.error(`[${tag}]: ${data}`)); 106 | 107 | runner.on('exit', (code) => { 108 | if (!code) { 109 | return resolve(stdout); 110 | } 111 | reject(new Error(`${cmd} failed with code ${code}`)); 112 | }); 113 | }); 114 | } 115 | 116 | async function getADBDeviceName(avdName, useDevice) { 117 | const tag = 'Get ADB Device'; 118 | const devicesOutput = await spawnP(tag, 'adb', ['devices']); 119 | const deviceNames = _.chain(devicesOutput.split('\n')) 120 | .slice(1) // Remove output header 121 | .map((line) => line.split('\t')[0]) // Get name column 122 | .map((line) => line.trim()) // Omit empty results 123 | .filter((line) => !!line) 124 | .value(); 125 | if (useDevice) { 126 | return deviceNames[0]; 127 | } 128 | // Need to check all devices for their AVD name 129 | for (let deviceName of deviceNames) { 130 | try { 131 | const deviceAVDNameResponse = await spawnP(tag, `adb`, [`-s`, deviceName, `emu`, `avd`, `name`]); 132 | const deviceAVDName = deviceAVDNameResponse.split('\n')[0].trim(); 133 | if (deviceAVDName == avdName) { 134 | return deviceName; // This device has the specified AVD name 135 | } 136 | } catch (ex) { 137 | console.warn(ex); 138 | } 139 | } 140 | } 141 | 142 | async function getSimulatorDeviceName(simulatorName) { 143 | try { 144 | const devicesOutput = await spawnP('List iOS Simulators', 'xcrun', ['simctl', 'list', 'devices', 'x']); 145 | const deviceNames = _.chain(devicesOutput.split('\n')) 146 | .map((line) => line.trim()) 147 | .filter((line) => line.startsWith(simulatorName)) 148 | .value(); 149 | 150 | if (deviceNames.length > 0) { 151 | return deviceNames[0]; 152 | } else { 153 | console.error(`No iOS simulator found with name ${simulatorName}`); 154 | return null; 155 | } 156 | } catch (ex) { 157 | console.error(ex); 158 | return null; 159 | } 160 | } 161 | 162 | async function receiveResults(app) { 163 | return new Promise((resolve, reject) => { 164 | /** 165 | * We can receive results from here 166 | */ 167 | const timeout = setTimeout(() => { 168 | reject(new Error('Test timed out')); 169 | }, TEST_TIMEOUT); 170 | 171 | app.post('/results', (req, res) => { 172 | clearTimeout(timeout); 173 | const results = req.body; 174 | displayResults(results); 175 | if (results.some((r) => r.type == 'incorrect')) { 176 | reject(new Error('Not all tests have passed')); 177 | } else { 178 | resolve(results); 179 | } 180 | 181 | return res.send('Done'); 182 | }); 183 | }); 184 | } 185 | 186 | function displayResults(results) { 187 | for (let result of results) { 188 | switch (result.type) { 189 | case 'grouping': 190 | console.log(chalk.blue(`Group Test Results for: ${result.description}`)); 191 | break; 192 | case 'correct': 193 | console.log(chalk.green(`✅ ${result.description}`)); 194 | break; 195 | case 'incorrect': 196 | console.log(chalk.red(`❌ ${result.description}`)); 197 | console.log(chalk.red(result.errorMsg)); 198 | break; 199 | } 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /tests/tests/index.ts: -------------------------------------------------------------------------------- 1 | export { runTests } from './mocha/MochaSetup'; 2 | export { registerBaseTests } from './sqlite/rawQueries.spec'; 3 | -------------------------------------------------------------------------------- /tests/tests/mocha/MochaRNAdapter.ts: -------------------------------------------------------------------------------- 1 | import 'mocha'; 2 | import type * as MochaTypes from 'mocha'; 3 | 4 | export const rootSuite = new Mocha.Suite('') as MochaTypes.Suite; 5 | rootSuite.timeout(60 * 1000); 6 | 7 | let mochaContext = rootSuite; 8 | 9 | let only = false; 10 | 11 | export const clearTests = () => { 12 | rootSuite.suites = []; 13 | rootSuite.tests = []; 14 | mochaContext = rootSuite; 15 | only = false; 16 | }; 17 | 18 | export const it = (name: string, f: MochaTypes.Func | MochaTypes.AsyncFunc): void => { 19 | if (!only) { 20 | const test = new Mocha.Test(name, async () => { 21 | console.log(`Running ${name}`); 22 | // @ts-ignore 23 | return f(); 24 | }); 25 | mochaContext.addTest(test); 26 | } 27 | }; 28 | 29 | export const itOnly = (name: string, f: MochaTypes.Func | MochaTypes.AsyncFunc): void => { 30 | clearTests(); 31 | const test = new Mocha.Test(name, f); 32 | mochaContext.addTest(test); 33 | only = true; 34 | }; 35 | 36 | export const describe = (name: string, f: () => void): void => { 37 | const prevMochaContext = mochaContext; 38 | mochaContext = new Mocha.Suite(name, prevMochaContext.ctx) as MochaTypes.Suite; 39 | prevMochaContext.addSuite(mochaContext); 40 | console.log(`Running ${name}`); 41 | f(); 42 | mochaContext = prevMochaContext; 43 | }; 44 | 45 | export const beforeEach = (f: () => void): void => { 46 | mochaContext.beforeEach(f); 47 | }; 48 | 49 | export const beforeAll = (f: any) => { 50 | mochaContext.beforeAll(f); 51 | }; 52 | -------------------------------------------------------------------------------- /tests/tests/mocha/MochaSetup.ts: -------------------------------------------------------------------------------- 1 | import 'mocha'; 2 | import type * as MochaTypes from 'mocha'; 3 | // import type { RowItemType } from '../navigators/children/TestingScreen/RowItemType'; 4 | import { clearTests, rootSuite } from './MochaRNAdapter'; 5 | 6 | export async function runTests(...registrators: Array<() => void>) { 7 | // testRegistrators: Array<() => void> = [] 8 | // console.log('setting up mocha'); 9 | 10 | const promise = new Promise((resolve) => { 11 | const { EVENT_RUN_BEGIN, EVENT_RUN_END, EVENT_TEST_FAIL, EVENT_TEST_PASS, EVENT_SUITE_BEGIN, EVENT_SUITE_END } = 12 | Mocha.Runner.constants; 13 | 14 | clearTests(); 15 | const results: any[] = []; 16 | var runner = new Mocha.Runner(rootSuite) as MochaTypes.Runner; 17 | 18 | runner 19 | .once(EVENT_RUN_BEGIN, () => {}) 20 | .on(EVENT_SUITE_BEGIN, (suite: MochaTypes.Suite) => { 21 | const name = suite.title; 22 | if (name !== '') { 23 | results.push({ 24 | description: name, 25 | key: Math.random().toString(), 26 | type: 'grouping' 27 | }); 28 | } 29 | }) 30 | .on(EVENT_TEST_PASS, (test: MochaTypes.Runnable) => { 31 | results.push({ 32 | description: test.title, 33 | key: Math.random().toString(), 34 | type: 'correct' 35 | }); 36 | // console.log(`${indent()}pass: ${test.fullTitle()}`); 37 | }) 38 | .on(EVENT_TEST_FAIL, (test: MochaTypes.Runnable, err: Error) => { 39 | results.push({ 40 | description: test.title, 41 | key: Math.random().toString(), 42 | type: 'incorrect', 43 | errorMsg: err.message 44 | }); 45 | // console.log( 46 | // `${indent()}fail: ${test.fullTitle()} - error: ${err.message}` 47 | // ); 48 | }) 49 | .once(EVENT_RUN_END, () => { 50 | resolve(results); 51 | }); 52 | 53 | registrators.forEach((register) => { 54 | register(); 55 | }); 56 | runner.run(); 57 | }); 58 | 59 | // return () => { 60 | // console.log('aborting'); 61 | // runner.abort(); 62 | // }; 63 | 64 | return promise; 65 | } 66 | -------------------------------------------------------------------------------- /tests/tests/sqlite/utils.ts: -------------------------------------------------------------------------------- 1 | export function randomIntFromInterval(min: number, max: number) { 2 | // min included and max excluded 3 | return Math.random() * (max - min) + min; 4 | } 5 | 6 | export function numberName(n: number) { 7 | if (n == 0) { 8 | return 'zero'; 9 | } 10 | 11 | let numberName: string[] = []; 12 | let d43 = Math.floor(n / 1000); 13 | if (d43 != 0) { 14 | numberName.push(names100[d43]); 15 | numberName.push('thousand'); 16 | n -= d43 * 1000; 17 | } 18 | 19 | let d2 = Math.floor(n / 100); 20 | if (d2 != 0) { 21 | numberName.push(names100[d2]); 22 | numberName.push('hundred'); 23 | n -= d2 * 100; 24 | } 25 | 26 | let d10 = n; 27 | if (d10 != 0) { 28 | numberName.push(names100[d10]); 29 | } 30 | 31 | return numberName.join(' '); 32 | } 33 | 34 | export function assertAlways(condition: boolean) { 35 | if (!condition) { 36 | throw Error('Assertion failed'); 37 | } 38 | } 39 | 40 | const digits = ['', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine']; 41 | const names100: string[] = [ 42 | ...digits, 43 | ...['ten', 'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', 'sixteen', 'seventeen', 'eighteen', 'nineteen'], 44 | ...digits.map((digit) => `twenty${digit != '' ? '-' + digit : ''}`), 45 | ...digits.map((digit) => `thirty${digit != '' ? '-' + digit : ''}`), 46 | ...digits.map((digit) => `forty${digit != '' ? '-' + digit : ''}`), 47 | ...digits.map((digit) => `fifty${digit != '' ? '-' + digit : ''}`), 48 | ...digits.map((digit) => `sixty${digit != '' ? '-' + digit : ''}`), 49 | ...digits.map((digit) => `seventy${digit != '' ? '-' + digit : ''}`), 50 | ...digits.map((digit) => `eighty${digit != '' ? '-' + digit : ''}`), 51 | ...digits.map((digit) => `ninety${digit != '' ? '-' + digit : ''}`) 52 | ]; 53 | -------------------------------------------------------------------------------- /tests/tests/type-orm/Database.ts: -------------------------------------------------------------------------------- 1 | import { DataSource } from 'typeorm'; 2 | import { typeORMDriver } from 'react-native-quick-sqlite'; 3 | import { Book } from './models/Book'; 4 | import { User } from './models/User'; 5 | let datasource: DataSource; 6 | 7 | export async function typeORMInit() { 8 | datasource = new DataSource({ 9 | type: 'react-native', 10 | database: 'typeormdb', 11 | location: '.', 12 | driver: typeORMDriver, 13 | entities: [Book, User], 14 | synchronize: true 15 | }); 16 | 17 | await datasource.initialize(); 18 | 19 | const bookRepository = datasource.getRepository(Book); 20 | const book1 = new Book(); 21 | book1.id = Math.random().toString(); 22 | book1.title = 'Lord of the rings'; 23 | 24 | await bookRepository.save(book1); 25 | } 26 | 27 | export async function typeORMGetBooks() { 28 | const bookRepository = datasource.getRepository(Book); 29 | return await bookRepository.find(); 30 | } 31 | 32 | export async function executeFailingTypeORMQuery() { 33 | const bookRepository = datasource.getRepository(Book); 34 | 35 | try { 36 | const manualQuery = await bookRepository.query(` 37 | SELECT * From UnexistingTable 38 | `); 39 | } catch (e) { 40 | console.warn('should have cached'); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/tests/type-orm/models/Book.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; 2 | import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm/browser'; 3 | import { BaseEntity } from 'typeorm'; 4 | 5 | @Entity() 6 | export class Book extends BaseEntity { 7 | @PrimaryGeneratedColumn('uuid') 8 | id!: string; 9 | 10 | @Column('text') 11 | title!: string; 12 | } 13 | -------------------------------------------------------------------------------- /tests/tests/type-orm/models/User.ts: -------------------------------------------------------------------------------- 1 | import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm/browser'; 2 | 3 | @Entity('user') 4 | export class User { 5 | @PrimaryGeneratedColumn('uuid') 6 | id!: string; 7 | 8 | @Column('text') 9 | name!: string; 10 | 11 | @Column('int') 12 | age!: number; 13 | 14 | @Column('float') 15 | networth!: number; 16 | 17 | @Column('simple-json') 18 | metadata: { nickname: string }; 19 | 20 | @Column('blob') 21 | avatar: ArrayBuffer; 22 | } 23 | -------------------------------------------------------------------------------- /tests/tests/type-orm/typeorm.spec.ts: -------------------------------------------------------------------------------- 1 | import { DataSource, Repository } from 'typeorm'; 2 | import { beforeAll, beforeEach, it, describe } from '../mocha/MochaRNAdapter'; 3 | import { typeORMDriver } from 'react-native-quick-sqlite'; 4 | import { User } from './models/User'; 5 | import { Book } from './models/Book'; 6 | import chai from 'chai'; 7 | import Chance from 'chance'; 8 | 9 | const chance = new Chance(); 10 | let expect = chai.expect; 11 | 12 | let dataSource: DataSource; 13 | let userRepository: Repository; 14 | let bookRepository: Repository; 15 | 16 | export function registerTypeORMTests() { 17 | describe('Typeorm tests', () => { 18 | beforeAll((done: any) => { 19 | dataSource = new DataSource({ 20 | type: 'react-native', 21 | database: 'typeormDb.sqlite', 22 | location: 'default', 23 | driver: typeORMDriver, 24 | entities: [User, Book], 25 | synchronize: true 26 | }); 27 | 28 | dataSource 29 | .initialize() 30 | .then(() => { 31 | userRepository = dataSource.getRepository(User); 32 | bookRepository = dataSource.getRepository(Book); 33 | done(); 34 | }) 35 | .catch((e) => { 36 | console.error('error initializing typeORM datasource', e); 37 | throw e; 38 | }); 39 | }); 40 | 41 | beforeEach(async () => { 42 | await userRepository.clear(); 43 | await bookRepository.clear(); 44 | }); 45 | 46 | it('basic test', async () => { 47 | const name = 'Steven'; 48 | const user = userRepository.create({ 49 | name, 50 | age: chance.integer(), 51 | networth: chance.integer(), 52 | metadata: { nickname: name }, 53 | avatar: 'Something' 54 | }); 55 | await userRepository.save(user); 56 | 57 | const found = await userRepository.findOne({ where: { name } }); 58 | 59 | expect(found.name).to.equal(name); 60 | }); 61 | }); 62 | } 63 | -------------------------------------------------------------------------------- /tests/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "typeRoots": ["node_modules/@types"] 4 | }, 5 | "extends": "expo/tsconfig.base" 6 | } 7 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "composite": true, 5 | "sourceRoot": "./src", 6 | "sourceMap": true, 7 | "paths": { 8 | "react-native-quick-sqlite": ["./src/index"] 9 | }, 10 | "allowUnreachableCode": false, 11 | "allowUnusedLabels": false, 12 | "esModuleInterop": true, 13 | "forceConsistentCasingInFileNames": true, 14 | "jsx": "react", 15 | "lib": ["esnext"], 16 | "module": "commonjs", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "skipLibCheck": true, 20 | "strict": false, 21 | "target": "esnext", 22 | "emitDecoratorMetadata": true, 23 | "experimentalDecorators": true 24 | }, 25 | "exclude": ["lib", "tests"] 26 | } 27 | --------------------------------------------------------------------------------