diff --git a/README.md b/README.md index b5c7e208..b333b6ad 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ > [!NOTE] > Requires [Nitro modules](https://nitro.margelo.com/) and React Native `0.75` or later. -Nitro SQLite embeds SQLite and exposes a JSI API. Each operation is available in **sync** and **async** form; async runs off the JS thread to avoid blocking the UI. +Nitro SQLite embeds SQLite and exposes a JSI API on iOS, macOS, visionOS, and Android. Each operation is available in **sync** and **async** form; async runs off the JS thread to avoid blocking the UI. --- @@ -44,6 +44,12 @@ npm install react-native-nitro-sqlite react-native-nitro-modules npx pod-install ``` +For a React Native macOS app, run CocoaPods from the `macos` directory: + +```bash +cd macos && pod install +``` + --- # API overview @@ -184,7 +190,7 @@ const { rowsAffected, commands } = db.loadFile('/absolute/path/to/file.sql') # Loading existing databases -Databases are created under the app documents directory (iOS) or files directory (Android). `location` is a directory path relative to that root, not an absolute file path. For example, `open({ name: 'myDb.sqlite', location: 'databases' })` opens `myDb.sqlite` under the `databases` directory. To use a database from another app-accessible location, copy or move it into this directory first. On iOS, files outside the app sandbox are inaccessible. +Databases are created under the app Documents directory (iOS and visionOS), Application Support directory (macOS), or files directory (Android). `location` is a directory path relative to that root, not an absolute file path. For example, `open({ name: 'myDb.sqlite', location: 'databases' })` opens `myDb.sqlite` under the `databases` directory. To use a database from another app-accessible location, copy or move it into this directory first. In sandboxed Apple apps, files outside the app sandbox are inaccessible. Close a connection before deleting its database. A connection must not be used after `close()` or `delete()`. @@ -222,10 +228,11 @@ Vector search is an opt-in companion package. It statically links sqlite-vec int npm install react-native-nitro-sqlite-vec ``` 2. Enable it for each native platform, then rebuild the app: - - **iOS:** run CocoaPods with `NITRO_SQLITE_VEC=1`, for example: + - **Apple platforms (iOS, macOS, visionOS):** run CocoaPods with `NITRO_SQLITE_VEC=1`, for example: ```bash NITRO_SQLITE_VEC=1 npx pod-install ``` + For React Native macOS, run `NITRO_SQLITE_VEC=1 pod install` from `macos/`. - **Android:** add this to `android/gradle.properties`: ```properties nitroSqliteVec=true @@ -302,7 +309,7 @@ You can use this package as a TypeORM driver. Because of Metro and Node resoluti # Configuration -## Configure bundled SQLite thread safety on iOS +## Configure bundled SQLite thread safety on Apple platforms The bundled SQLite library compiles with `SQLITE_THREADSAFE=1` by default. This includes SQLite's mutex code and selects serialized mode, which lets SQLite serialize concurrent access to database connections and prepared statements. Configure it in your app's `package.json`: @@ -325,7 +332,7 @@ With `SQLITE_THREADSAFE=0`, SQLite removes its mutex code and cannot be made thr When `NITRO_SQLITE_USE_PHONE_VERSION=1`, the pod links the system SQLite library instead of compiling the bundled source. `NITRO_SQLITE_THREADSAFE` does not change how that system library was compiled. -## Configure SQLite performance mode on iOS +## Configure SQLite performance mode on Apple platforms The bundled SQLite library enables NitroSQLite's performance compile flags by default. Disable them independently from thread safety in your app's `package.json`: @@ -340,7 +347,7 @@ The bundled SQLite library enables NitroSQLite's performance compile flags by de `performanceMode` accepts `true` or `false`. `NITRO_SQLITE_PERFORMANCE_MODE` overrides the package setting for one Pod installation and accepts `true`, `false`, `1`, or `0`. Disabling performance mode omits NitroSQLite's SQLite optimization flags but does not change `SQLITE_THREADSAFE`. -## Use system SQLite on iOS +## Use system SQLite on Apple platforms To use the system SQLite instead of the bundled one: @@ -348,9 +355,11 @@ To use the system SQLite instead of the bundled one: NITRO_SQLITE_USE_PHONE_VERSION=1 npx pod-install ``` +For React Native macOS, run the command from `macos/` with `pod install` instead of `npx pod-install`. + ## Compile-time options (e.g. FTS5, Geopoly) -**iOS** — in your app’s `ios/Podfile`, in a `post_install` block: +**Apple platforms** — in your app’s `Podfile`, in a `post_install` block: ```ruby installer.pods_project.targets.each do |target| @@ -369,7 +378,7 @@ end nitroSqliteFlags="-DSQLITE_ENABLE_FTS5=1" ``` -## App groups (iOS) +## App groups (Apple platforms) To put the database in an app group (e.g. for extensions), set `RNNitroSQLite_AppGroup` in your `Info.plist` to the app group ID and add the App Groups capability in Xcode. diff --git a/packages/react-native-nitro-sqlite-vec/README.md b/packages/react-native-nitro-sqlite-vec/README.md index 934246fb..d1487a74 100644 --- a/packages/react-native-nitro-sqlite-vec/README.md +++ b/packages/react-native-nitro-sqlite-vec/README.md @@ -10,10 +10,11 @@ Install both this package and `react-native-nitro-sqlite`, then enable the nativ npm install react-native-nitro-sqlite react-native-nitro-sqlite-vec ``` -- **iOS:** +- **Apple platforms (iOS, macOS, visionOS):** ```bash NITRO_SQLITE_VEC=1 npx pod-install ``` + For React Native macOS, run `NITRO_SQLITE_VEC=1 pod install` from `macos/`. - **Android:** add this to `android/gradle.properties` and rebuild: ```properties nitroSqliteVec=true diff --git a/packages/react-native-nitro-sqlite-vec/RNNitroSqliteVec.podspec b/packages/react-native-nitro-sqlite-vec/RNNitroSqliteVec.podspec index 18ebface..56b6bd5c 100644 --- a/packages/react-native-nitro-sqlite-vec/RNNitroSqliteVec.podspec +++ b/packages/react-native-nitro-sqlite-vec/RNNitroSqliteVec.podspec @@ -2,7 +2,7 @@ require "json" package = JSON.parse(File.read(File.join(__dir__, "package.json"))) -# iOS opt-in (NITRO_SQLITE_VEC=1); compile sqlite-vec and static-link into the core's sqlite3. +# Apple-platform opt-in (NITRO_SQLITE_VEC=1); compile sqlite-vec and static-link into the core's sqlite3. nitro_sqlite_vec = ENV['NITRO_SQLITE_VEC'] == '1' # The core's bundled sqlite3.h (compiled with SQLITE_CORE to link it directly). @@ -15,7 +15,11 @@ Pod::Spec.new do |s| s.homepage = "https://github.com/margelo/react-native-nitro-sqlite" s.license = "MIT" s.authors = "Margelo" - s.platforms = { :ios => min_ios_version_supported, :visionos => "1.0" } + s.platforms = { + :ios => min_ios_version_supported, + :visionos => "1.0", + :osx => "10.13", + } s.source = { :git => "https://github.com/margelo/react-native-nitro-sqlite.git", :tag => "#{s.version}" } if nitro_sqlite_vec diff --git a/packages/react-native-nitro-sqlite-vec/package.json b/packages/react-native-nitro-sqlite-vec/package.json index dd2a3614..8afd830c 100644 --- a/packages/react-native-nitro-sqlite-vec/package.json +++ b/packages/react-native-nitro-sqlite-vec/package.json @@ -27,6 +27,7 @@ "vector-search", "nitro-modules", "ios", + "macos", "android" ], "repository": { diff --git a/packages/react-native-nitro-sqlite-vec/react-native.config.js b/packages/react-native-nitro-sqlite-vec/react-native.config.js index ba3ab018..2faef343 100644 --- a/packages/react-native-nitro-sqlite-vec/react-native.config.js +++ b/packages/react-native-nitro-sqlite-vec/react-native.config.js @@ -1,8 +1,9 @@ -// Android compiles these sources via the core's CMake, so autolink iOS only. +// CocoaPods autolinks this package on Apple platforms; Android compiles it via the core's CMake. module.exports = { dependency: { platforms: { ios: {}, + macos: {}, android: null, }, }, diff --git a/packages/react-native-nitro-sqlite/RNNitroSQLite.podspec b/packages/react-native-nitro-sqlite/RNNitroSQLite.podspec index 25386e8e..e933819f 100644 --- a/packages/react-native-nitro-sqlite/RNNitroSQLite.podspec +++ b/packages/react-native-nitro-sqlite/RNNitroSQLite.podspec @@ -51,7 +51,11 @@ Pod::Spec.new do |s| s.homepage = package["homepage"] s.license = package["license"] s.authors = package["author"] - s.platforms = { :ios => min_ios_version_supported, :visionos => "1.0" } + s.platforms = { + :ios => min_ios_version_supported, + :visionos => "1.0", + :osx => "10.13", + } s.source = { :git => "https://github.com/margelo/react-native-nitro-sqlite.git", :tag => "#{s.version}" } # Opt-in vector search (NITRO_SQLITE_VEC=1); the companion pod compiles the sources. @@ -59,9 +63,9 @@ Pod::Spec.new do |s| nitro_sqlite_vec_cpp = File.expand_path(File.join(__dir__, "..", "react-native-nitro-sqlite-vec", "cpp")) s.source_files = [ - # Implementation (Swift) + # Apple platform implementation (Swift) "ios/**/*.{swift}", - # Autolinking/Registration (Objective-C++) + # Apple platform autolinking/registration (Objective-C++) "ios/**/*.{h,hpp,m,mm}", # Implementation (C++ objects) "cpp/**/*.{h,hpp,c,cpp}" diff --git a/packages/react-native-nitro-sqlite/ios/OnLoad.mm b/packages/react-native-nitro-sqlite/ios/OnLoad.mm index 6ce72584..31891e29 100644 --- a/packages/react-native-nitro-sqlite/ios/OnLoad.mm +++ b/packages/react-native-nitro-sqlite/ios/OnLoad.mm @@ -1,7 +1,53 @@ #import +#import #import "RNNitroSQLite-Swift-Cxx-Umbrella.hpp" #import "HybridNitroSQLite.hpp" +static NSString *defaultDatabaseDirectory(void) { + NSFileManager *fileManager = [NSFileManager defaultManager]; + +#if TARGET_OS_OSX + NSError *error = nil; + NSURL *applicationSupportURL = [fileManager URLForDirectory:NSApplicationSupportDirectory + inDomain:NSUserDomainMask + appropriateForURL:nil + create:YES + error:&error]; + if (applicationSupportURL == nil) { + @throw [NSException exceptionWithName:@"SQLiteInitializationException" + reason:[NSString stringWithFormat:@"Could not find the Application Support directory: %@", error] + userInfo:nil]; + } + + NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier]; + if (bundleIdentifier == nil) { + bundleIdentifier = [[NSProcessInfo processInfo] processName]; + } + + NSURL *databaseURL = [applicationSupportURL URLByAppendingPathComponent:bundleIdentifier isDirectory:YES]; + if (![fileManager createDirectoryAtURL:databaseURL withIntermediateDirectories:YES attributes:nil error:&error]) { + @throw [NSException exceptionWithName:@"SQLiteInitializationException" + reason:[NSString stringWithFormat:@"Could not create the database directory: %@", error] + userInfo:nil]; + } + + return [databaseURL path]; +#else + NSURL *documentsURL = [fileManager URLForDirectory:NSDocumentDirectory + inDomain:NSUserDomainMask + appropriateForURL:nil + create:YES + error:nil]; + if (documentsURL == nil) { + @throw [NSException exceptionWithName:@"SQLiteInitializationException" + reason:@"Could not find the Documents directory" + userInfo:nil]; + } + + return [documentsURL path]; +#endif +} + @interface OnLoad : NSObject @end @@ -11,7 +57,7 @@ @implementation OnLoad using namespace margelo::nitro::rnnitrosqlite; + (void)load { - // Get appGroupID value from Info.plist using key "AppGroup" + // Get appGroupID value from Info.plist using key "RNNitroSQLite_AppGroup". NSString *appGroupID = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"RNNitroSQLite_AppGroup"]; NSString *documentPath; @@ -21,7 +67,7 @@ + (void)load { NSURL *storeUrl = [fileManager containerURLForSecurityApplicationGroupIdentifier:appGroupID]; if (storeUrl == nil) { - NSLog(@"Invalid AppGroup ID provided (%@). Check the value of \"AppGroup\" in your Info.plist file", appGroupID); + NSLog(@"Invalid App Group ID provided (%@). Check the value of \"RNNitroSQLite_AppGroup\" in your Info.plist file", appGroupID); @throw [NSException exceptionWithName:@"SQLiteInitializationException" reason:@"Error while initializing SQLite database (AppGroup)" userInfo:nil]; @@ -30,9 +76,7 @@ + (void)load { documentPath = [storeUrl path]; } else { - // Get iOS app's document directory (to safely store database .sqlite3 file) - NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true); - documentPath = [paths objectAtIndex:0]; + documentPath = defaultDatabaseDirectory(); } HybridNitroSQLite::docPath = [documentPath UTF8String]; diff --git a/packages/react-native-nitro-sqlite/package.json b/packages/react-native-nitro-sqlite/package.json index 259d2797..da96ce0d 100644 --- a/packages/react-native-nitro-sqlite/package.json +++ b/packages/react-native-nitro-sqlite/package.json @@ -50,6 +50,7 @@ "sqlite", "nitro-modules", "ios", + "macos", "android" ], "repository": { diff --git a/packages/react-native-nitro-sqlite/react-native.config.js b/packages/react-native-nitro-sqlite/react-native.config.js index 3fdf8eaa..36876a8a 100644 --- a/packages/react-native-nitro-sqlite/react-native.config.js +++ b/packages/react-native-nitro-sqlite/react-native.config.js @@ -7,6 +7,7 @@ module.exports = { * @type {import('@react-native-community/cli-types').IOSDependencyParams} */ ios: {}, + macos: {}, /** * @type {import('@react-native-community/cli-types').AndroidDependencyParams} */ diff --git a/packages/react-native-nitro-sqlite/src/OnLoad.ts b/packages/react-native-nitro-sqlite/src/OnLoad.ts index 91f02202..a069caad 100644 --- a/packages/react-native-nitro-sqlite/src/OnLoad.ts +++ b/packages/react-native-nitro-sqlite/src/OnLoad.ts @@ -1,2 +1,2 @@ -// NOOP on iOS +// Apple platforms initialize the database directory from OnLoad.mm. export const init = () => {}