Nozbe / WatermelonDB

🍉 Reactive & asynchronous database for powerful React and React Native apps ⚡️
https://watermelondb.dev
MIT License
10.27k stars 579 forks source link

Failed to load better_sqlite3.node module with WatermelonDB on React Native #1796

Open oushima opened 1 month ago

oushima commented 1 month ago

Description

I am encountering an issue where the better_sqlite3.node module fails to load when using WatermelonDB in the web version of my React Native project. The mobile versions (iOS and Android) work fine. The error message suggests that the module cannot be found, although it exists in the expected directory.

Error Message

[🍉] Uh-oh. Database failed to load, we're in big trouble. This might happen if you didn't set up native code correctly (iOS, Android), or if you didn't recompile native app after WatermelonDB update. It might also mean that IndexedDB or SQLite refused to open. Error: Failed to open the database. - Unknown named module: "/Users/oushima/Public/IngredientInspector/frontend/build/better_sqlite3.node"
    at Database.open (/Users/oushima/Public/IngredientInspector/frontend/node_modules/@nozbe/watermelondb/adapters/sqlite/sqlite-node/Database.js:30:13)
    at new Database (/Users/oushima/Public/IngredientInspector/frontend/node_modules/@nozbe/watermelondb/adapters/sqlite/sqlite-node/Database.js:14:10)
    at DatabaseDriver.init (/Users/oushima/Public/IngredientInspector/frontend/node_modules/@nozbe/watermelondb/adapters/sqlite/sqlite-node/DatabaseDriver.js:77:21)
    at DatabaseDriver.initialize (/Users/oushima/Public/IngredientInspector/frontend/node_modules/@nozbe/watermelondb/adapters/sqlite/sqlite-node/DatabaseDriver.js:60:10)
    at DatabaseBridge.apply [as initialize] (/Users/oushima/Public/IngredientInspector/frontend/node_modules/@nozbe/watermelondb/adapters/sqlite/sqlite-node/DatabaseBridge.js:32:14)
    at SqliteNodeDispatcher.call (/Users/oushima/Public/IngredientInspector/frontend/node_modules/@nozbe/watermelondb/adapters/sqlite/makeDispatcher/index.js:18:12)
    at SQLiteAdapter._init (/Users/oushima/Public/IngredientInspector/frontend/node_modules/@nozbe/watermelondb/adapters/sqlite/index.js:86:22)
    at withCallback (/Users/oushima/Public/IngredientInspector/frontend/node_modules/@nozbe/watermelondb/adapters/sqlite/index.js:47:13)
    at /Users/oushima/Public/IngredientInspector/frontend/node_modules/@nozbe/watermelondb/utils/fp/Result/index.js:10:5
    at new Promise (<anonymous>)
Error: Failed to open the database. - Unknown named module: "/Users/oushima/Public/IngredientInspector/frontend/build/better_sqlite3.node"
    at Database.open (/Users/oushima/Public/IngredientInspector/frontend/node_modules/@nozbe/watermelondb/adapters/sqlite/sqlite-node/Database.js:30:13)
    at new Database (/Users/oushima/Public/IngredientInspector/frontend/node_modules/@nozbe/watermelondb/adapters/sqlite/sqlite-node/Database.js:14:10)
    at DatabaseDriver.init (/Users/oushima/Public/IngredientInspector/frontend/node_modules/@nozbe/watermelondb/adapters/sqlite/sqlite-node/DatabaseDriver.js:77:21)
    at DatabaseDriver.initialize (/Users/oushima/Public/IngredientInspector/frontend/node_modules/@nozbe/watermelondb/adapters/sqlite/sqlite-node/DatabaseDriver.js:60:10)
    at DatabaseBridge.apply [as initialize] (/Users/oushima/Public/IngredientInspector/frontend/node_modules/@nozbe/watermelondb/adapters/sqlite/sqlite-node/DatabaseBridge.js:32:14)
    at SqliteNodeDispatcher.call (/Users/oushima/Public/IngredientInspector/frontend/node_modules/@nozbe/watermelondb/adapters/sqlite/makeDispatcher/index.js:18:12)
    at SQLiteAdapter._init (/Users/oushima/Public/IngredientInspector/frontend/node_modules/@nozbe/watermelondb/adapters/sqlite/index.js:86:22)
    at withCallback (/Users/oushima/Public/IngredientInspector/frontend/node_modules/@nozbe/watermelondb/adapters/sqlite/index.js:47:13)
    at /Users/oushima/Public/IngredientInspector/frontend/node_modules/@nozbe/watermelondb/utils/fp/Result/index.js:10:5
    at new Promise (<anonymous>)

Steps to Reproduce

Install WatermelonDB and better-sqlite3 in a React Native project. Configure WatermelonDB with SQLiteAdapter. Run the project on the web.

Expected Behavior

The better_sqlite3.node module should load correctly, and the database should initialize without errors on the web version.

Observed Behavior

The web version fails to load the database, throwing an error indicating that the better_sqlite3.node module is an unknown named module. The iOS and Android versions work correctly.

Environment

├── @babel/core@7.24.5
├── @expo/config@8.5.6
├── @expo/metro-config@0.17.7
├── @expo/vector-icons@14.0.2
├── @morrowdigital/watermelondb-expo-plugin@2.3.2
├── @nozbe/watermelondb@0.27.1
├── @nozbe/with-observables@1.6.0
├── @react-navigation/native@6.1.17
├── @types/react-native@0.73.0
├── @types/react@18.2.79
├── axios@1.6.8
├── better-sqlite3@9.6.0
├── expo-build-properties@0.11.1
├── expo-camera@14.1.3
├── expo-checkbox@2.7.0
├── expo-font@11.10.3
├── expo-image@1.10.6
├── expo-linking@6.2.2
├── expo-router@3.4.10
├── expo-splash-screen@0.26.5
├── expo-sqlite@13.4.0
├── expo-status-bar@1.11.1
├── expo-system-ui@2.9.4
├── expo-web-browser@12.8.2
├── expo@50.0.18
├── jest-expo@50.0.4
├── jest@29.7.0
├── react-dom@18.2.0
├── react-native-safe-area-context@4.8.2
├── react-native-screens@3.29.0
├── react-native-svg@14.1.0
├── react-native-web@0.19.11
├── react-native@0.73.6
├── react-test-renderer@18.2.0
├── react@18.2.0
├── ts-node@10.9.2
└── typescript@5.4.5

The better_sqlite3.node file exists in the node_modules/better-sqlite3/build/Release directory. I've tried clearing the npm cache, reinstalling dependencies, rebuilding native modules, and ensuring the path resolution is correct. Other native modules load without issues.

Any guidance or suggestions to resolve this issue would be greatly appreciated.

Merott commented 4 weeks ago

I ran into the same issue and realised we need to use LokiJS for web. It's in the docs:

The above will work on React Native (iOS/Android) and NodeJS. For the web, instead of SQLiteAdapter use LokiJSAdapter

I've created adapter.ts and adapter.web.ts:

// adapter.ts

import { type LokiAdapterOptions } from '@nozbe/watermelondb/adapters/lokijs'
import SQLiteAdapter from '@nozbe/watermelondb/adapters/sqlite'
import { SQLiteAdapterOptions } from '@nozbe/watermelondb/adapters/sqlite/type'

export const createAdapter = (
  options: Pick<
    SQLiteAdapterOptions,
    // Accept only the options shared between SQLiteAdapterOptions and LokiAdapterOptions
    Extract<keyof SQLiteAdapterOptions, keyof LokiAdapterOptions>
  >,
) =>
  new SQLiteAdapter({
    jsi: true,
    ...options,
  })
// adapter.web.ts

import LokiJSAdapter, {
  type LokiAdapterOptions,
} from '@nozbe/watermelondb/adapters/lokijs'
import { type SQLiteAdapterOptions } from '@nozbe/watermelondb/adapters/sqlite/type'

export const createAdapter = (
  options: Pick<
    LokiAdapterOptions,
    // Accept only the options shared between LokiAdapterOptions and SQLiteAdapterOptions
    Extract<keyof LokiAdapterOptions, keyof SQLiteAdapterOptions>
  >,
) =>
  new LokiJSAdapter({
    useWebWorker: false,
    useIncrementalIndexedDB: true,
    ...options,
  })

Use createAdapter to create the appropriate adapter when creating the database instance:

// database.ts

import { Database } from '@nozbe/watermelondb'

import { Post } from './models/Post'
import { createAdapter } from './adapter' // <---- imports from adapter.web.ts on web
import { schema } from './schema'

export const database = new Database({
  adapter: createAdapter({ schema }),
  modelClasses: [Post],
})