UNPKG

@rikishi/watermelondb

Version:

Build powerful React Native and React web apps that scale from hundreds to tens of thousands of records and remain fast

889 lines (810 loc) 269 kB
<!DOCTYPE HTML> <html lang="en" class="sidebar-visible no-js light"> <head> <!-- Book generated using mdBook --> <meta charset="UTF-8"> <title>WatermelonDB documentation</title> <meta name="robots" content="noindex" /> <!-- Custom HTML head --> <meta content="text/html; charset=utf-8" http-equiv="Content-Type"> <meta name="description" content=""> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="theme-color" content="#ffffff" /> <link rel="icon" href="favicon.svg"> <link rel="shortcut icon" href="favicon.png"> <link rel="stylesheet" href="css/variables.css"> <link rel="stylesheet" href="css/general.css"> <link rel="stylesheet" href="css/chrome.css"> <link rel="stylesheet" href="css/print.css" media="print"> <!-- Fonts --> <link rel="stylesheet" href="FontAwesome/css/font-awesome.css"> <link rel="stylesheet" href="fonts/fonts.css"> <!-- Highlight.js Stylesheets --> <link rel="stylesheet" href="highlight.css"> <link rel="stylesheet" href="tomorrow-night.css"> <link rel="stylesheet" href="ayu-highlight.css"> <!-- Custom theme stylesheets --> </head> <body> <!-- Provide site root to javascript --> <script type="text/javascript"> var path_to_root = ""; var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light"; </script> <!-- Work around some values being stored in localStorage wrapped in quotes --> <script type="text/javascript"> try { var theme = localStorage.getItem('mdbook-theme'); var sidebar = localStorage.getItem('mdbook-sidebar'); if (theme.startsWith('"') && theme.endsWith('"')) { localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1)); } if (sidebar.startsWith('"') && sidebar.endsWith('"')) { localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1)); } } catch (e) { } </script> <!-- Set the theme before any content is loaded, prevents flash --> <script type="text/javascript"> var theme; try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { } if (theme === null || theme === undefined) { theme = default_theme; } var html = document.querySelector('html'); html.classList.remove('no-js') html.classList.remove('light') html.classList.add(theme); html.classList.add('js'); </script> <!-- Hide / unhide sidebar before it is displayed --> <script type="text/javascript"> var html = document.querySelector('html'); var sidebar = 'hidden'; if (document.body.clientWidth >= 1080) { try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { } sidebar = sidebar || 'visible'; } html.classList.remove('sidebar-visible'); html.classList.add("sidebar-" + sidebar); </script> <nav id="sidebar" class="sidebar" aria-label="Table of contents"> <div class="sidebar-scrollbox"> <ol class="chapter"><li class="chapter-item expanded "><a href="ch01-00-get-excited.html"><strong aria-hidden="true">1.</strong> Get excited</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="index.html"><strong aria-hidden="true">1.1.</strong> Check out the README</a></li><li class="chapter-item expanded "><a href="Demo.html"><strong aria-hidden="true">1.2.</strong> See the demo</a></li></ol></li><li class="chapter-item expanded "><a href="ch02-00-learn-to-use.html"><strong aria-hidden="true">2.</strong> Learn to use Watermelon</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="Installation.html"><strong aria-hidden="true">2.1.</strong> Installation</a></li><li class="chapter-item expanded "><a href="Setup.html"><strong aria-hidden="true">2.2.</strong> Setup</a></li><li class="chapter-item expanded "><a href="Schema.html"><strong aria-hidden="true">2.3.</strong> Schema</a></li><li class="chapter-item expanded "><a href="Model.html"><strong aria-hidden="true">2.4.</strong> Defining Models</a></li><li class="chapter-item expanded "><a href="CRUD.html"><strong aria-hidden="true">2.5.</strong> Create, Read, Update, Delete</a></li><li class="chapter-item expanded "><a href="Components.html"><strong aria-hidden="true">2.6.</strong> Connecting to React Components</a></li><li class="chapter-item expanded "><a href="Query.html"><strong aria-hidden="true">2.7.</strong> Querying</a></li><li class="chapter-item expanded "><a href="Relation.html"><strong aria-hidden="true">2.8.</strong> Relations</a></li><li class="chapter-item expanded "><a href="Writers.html"><strong aria-hidden="true">2.9.</strong> Writers, Readers, batching</a></li></ol></li><li class="chapter-item expanded "><a href="ch03-00-advanced.html"><strong aria-hidden="true">3.</strong> Advanced guides</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="Advanced/Migrations.html"><strong aria-hidden="true">3.1.</strong> Migrations</a></li><li class="chapter-item expanded "><a href="Advanced/Sync.html"><strong aria-hidden="true">3.2.</strong> Sync</a></li><li class="chapter-item expanded "><a href="Advanced/CreateUpdateTracking.html"><strong aria-hidden="true">3.3.</strong> Automatic create/update tracking</a></li><li class="chapter-item expanded "><a href="Advanced/AdvancedFields.html"><strong aria-hidden="true">3.4.</strong> Advanced fields</a></li><li class="chapter-item expanded "><a href="Advanced/Flow.html"><strong aria-hidden="true">3.5.</strong> Flow</a></li><li class="chapter-item expanded "><a href="Advanced/LocalStorage.html"><strong aria-hidden="true">3.6.</strong> LocalStorage</a></li><li class="chapter-item expanded "><a href="Advanced/ProTips.html"><strong aria-hidden="true">3.7.</strong> Pro tips</a></li><li class="chapter-item expanded "><a href="Advanced/Performance.html"><strong aria-hidden="true">3.8.</strong> Performance tips</a></li><li class="chapter-item expanded "><a href="Advanced/SharingDatabaseAcrossTargets.html"><strong aria-hidden="true">3.9.</strong> iOS - Sharing database across targets</a></li></ol></li><li class="chapter-item expanded "><a href="ch04-00-deeper.html"><strong aria-hidden="true">4.</strong> Dig deeper into WatermelonDB</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="Implementation/Architecture.html"><strong aria-hidden="true">4.1.</strong> Architecture</a></li><li class="chapter-item expanded "><a href="Implementation/Adapters.html"><strong aria-hidden="true">4.2.</strong> Adapters</a></li><li class="chapter-item expanded "><a href="Implementation/SyncImpl.html"><strong aria-hidden="true">4.3.</strong> Sync implementation</a></li></ol></li><li class="chapter-item expanded "><a href="ch04-00-deeper.html"><strong aria-hidden="true">5.</strong> Other</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="Roadmap.html"><strong aria-hidden="true">5.1.</strong> Roadmap</a></li><li class="chapter-item expanded "><a href="CONTRIBUTING.html"><strong aria-hidden="true">5.2.</strong> Contributing</a></li><li class="chapter-item expanded "><a href="CHANGELOG.html"><strong aria-hidden="true">5.3.</strong> Changelog</a></li></ol></li></ol> </div> <div id="sidebar-resize-handle" class="sidebar-resize-handle"></div> </nav> <div id="page-wrapper" class="page-wrapper"> <div class="page"> <div id="menu-bar-hover-placeholder"></div> <div id="menu-bar" class="menu-bar sticky bordered"> <div class="left-buttons"> <button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar"> <i class="fa fa-bars"></i> </button> <button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list"> <i class="fa fa-paint-brush"></i> </button> <ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu"> <li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li> <li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li> <li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li> <li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li> <li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li> </ul> <button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar"> <i class="fa fa-search"></i> </button> </div> <h1 class="menu-title">WatermelonDB documentation</h1> <div class="right-buttons"> <a href="print.html" title="Print this book" aria-label="Print this book"> <i id="print-button" class="fa fa-print"></i> </a> </div> </div> <div id="search-wrapper" class="hidden"> <form id="searchbar-outer" class="searchbar-outer"> <input type="search" name="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header"> </form> <div id="searchresults-outer" class="searchresults-outer hidden"> <div id="searchresults-header" class="searchresults-header"></div> <ul id="searchresults"> </ul> </div> </div> <!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM --> <script type="text/javascript"> document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible'); document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible'); Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) { link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1); }); </script> <div id="content" class="content"> <main> <h3 id="get-excited"><a class="header" href="#get-excited">Get excited</a></h3> <p align="center"> <img src="https://github.com/Nozbe/WatermelonDB/raw/master/assets/logo-horizontal2.png" alt="WatermelonDB" width="539" /> </p> <h4 align="center"> A reactive database framework </h4> <p align="center"> Build powerful React and React Native apps that scale from hundreds to tens of thousands of records and remain <em>fast</em> ⚡️ </p> <p align="center"> <a href="https://github.com/Nozbe/WatermelonDB/blob/master/LICENSE"> <img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="MIT License"> </a> <a href="https://www.npmjs.com/package/@rikishi/watermelondb"> <img src="https://img.shields.io/npm/v/@rikishi/watermelondb.svg" alt="npm"> </a> </p> <table><thead><tr><th></th><th>WatermelonDB</th></tr></thead><tbody> <tr><td>⚡️</td><td><strong>Launch your app instantly</strong> no matter how much data you have</td></tr> <tr><td>📈</td><td><strong>Highly scalable</strong> from hundreds to tens of thousands of records</td></tr> <tr><td>😎</td><td><strong>Lazy loaded</strong>. Only load data when you need it</td></tr> <tr><td>🔄</td><td><strong>Offline-first.</strong> <a href="https://nozbe.github.io/WatermelonDB/Advanced/Sync.html">Sync</a> with your own backend</td></tr> <tr><td>📱</td><td><strong>Multiplatform</strong>. iOS, Android, web, and Node.js</td></tr> <tr><td>⚛️</td><td><strong>Optimized for React.</strong> Easily plug data into components</td></tr> <tr><td>🧰</td><td><strong>Framework-agnostic.</strong> Use JS API to plug into other UI frameworks</td></tr> <tr><td>⏱</td><td><strong>Fast.</strong> And getting faster with every release!</td></tr> <tr><td>✅</td><td><strong>Proven.</strong> Powers <a href="https://nozbe.com/teams">Nozbe Teams</a> since 2017 (and <a href="index.html#who-uses-watermelondb">many others</a>)</td></tr> <tr><td>✨</td><td><strong>Reactive.</strong> (Optional) <a href="https://github.com/ReactiveX/rxjs">RxJS</a> API</td></tr> <tr><td>🔗</td><td><strong>Relational.</strong> Built on rock-solid <a href="https://www.sqlite.org">SQLite</a> foundation</td></tr> <tr><td>⚠️</td><td><strong>Static typing</strong> with <a href="https://flow.org">Flow</a> or <a href="https://typescriptlang.org">TypeScript</a></td></tr> </tbody></table> <h2 id="why-watermelon"><a class="header" href="#why-watermelon">Why Watermelon?</a></h2> <p><strong>WatermelonDB</strong> is a new way of dealing with user data in React Native and React web apps.</p> <p>It's optimized for building <strong>complex applications</strong> in React Native, and the number one goal is <strong>real-world performance</strong>. In simple words, <em>your app must launch fast</em>.</p> <p>For simple apps, using Redux or MobX with a persistence adapter is the easiest way to go. But when you start scaling to thousands or tens of thousands of database records, your app will now be slow to launch (especially on slower Android devices). Loading a full database into JavaScript is expensive!</p> <p>Watermelon fixes it <strong>by being lazy</strong>. Nothing is loaded until it's requested. And since all querying is performed directly on the rock-solid <a href="https://www.sqlite.org/index.html">SQLite database</a> on a separate native thread, most queries resolve in an instant.</p> <p>But unlike using SQLite directly, Watermelon is <strong>fully observable</strong>. So whenever you change a record, all UI that depends on it will automatically re-render. For example, completing a task in a to-do app will re-render the task component, the list (to reorder), and all relevant task counters. <a href="https://www.youtube.com/watch?v=UlZ1QnFF4Cw"><strong>Learn more</strong></a>.</p> <table><thead><tr><th><a href="https://www.youtube.com/watch?v=UlZ1QnFF4Cw"><img src="https://github.com/Nozbe/WatermelonDB/raw/master/assets/watermelon-talk-thumbnail.jpg" alt="React Native EU: Next-generation React Databases" width="300" /></a></th><th><a href="https://watermelondb.now.sh/"><img src="https://github.com/Nozbe/WatermelonDB/raw/master/assets/watermelon-demo-thumbnail.png" alt="WatermelonDB Demo" width="300" /></a></th></tr></thead><tbody> <tr><td><p align="center"><a href="https://www.youtube.com/watch?v=UlZ1QnFF4Cw">📺 <strong>Next-generation React databases</strong><br>(a talk about WatermelonDB)</a></p></td><td><p align="center"><a href="https://watermelondb.now.sh/">✨ <strong>Check out web Demo</strong></a></p></td></tr> </tbody></table> <h2 id="usage"><a class="header" href="#usage">Usage</a></h2> <p><strong>Quick (over-simplified) example:</strong> an app with posts and comments.</p> <p>First, you define Models:</p> <pre><code class="language-js">class Post extends Model { @field('name') name @field('body') body @children('comments') comments } class Comment extends Model { @field('body') body @field('author') author } </code></pre> <p>Then, you connect components to the data:</p> <pre><code class="language-js">const Comment = ({ comment }) =&gt; ( &lt;View style={styles.commentBox}&gt; &lt;Text&gt;{comment.body} — by {comment.author}&lt;/Text&gt; &lt;/View&gt; ) // This is how you make your app reactive! ✨ const enhance = withObservables(['comment'], ({ comment }) =&gt; ({ comment, })) const EnhancedComment = enhance(Comment) </code></pre> <p>And now you can render the whole Post:</p> <pre><code class="language-js">const Post = ({ post, comments }) =&gt; ( &lt;View&gt; &lt;Text&gt;{post.name}&lt;/Text&gt; &lt;Text&gt;Comments:&lt;/Text&gt; {comments.map(comment =&gt; &lt;EnhancedComment key={comment.id} comment={comment} /&gt; )} &lt;/View&gt; ) const enhance = withObservables(['post'], ({ post }) =&gt; ({ post, comments: post.comments })) </code></pre> <p>The result is fully reactive! Whenever a post or comment is added, changed, or removed, the right components <strong>will automatically re-render</strong> on screen. Doesn't matter if a change occurred in a totally different part of the app, it all just works out of the box!</p> <h3 id="-learn-more-a-hrefhttpsnozbegithubiowatermelondbsee-full-documentationa"><a class="header" href="#-learn-more-a-hrefhttpsnozbegithubiowatermelondbsee-full-documentationa">➡️ <strong>Learn more:</strong> <a href="https://nozbe.github.io/WatermelonDB/">see full documentation</a></a></h3> <h2 id="who-uses-watermelondb"><a class="header" href="#who-uses-watermelondb">Who uses WatermelonDB</a></h2> <a href="https://nozbe.com/teams/"> <img src="https://github.com/Nozbe/WatermelonDB/raw/master/assets/apps/nozbe-teams.png" alt="Nozbe Teams" width="300" /> </a> <br> <a href="https://capmo.de"> <img src="https://github.com/Nozbe/WatermelonDB/raw/master/assets/apps/capmo.png" alt="CAPMO" width="300" /> </a> <br> <a href="https://steady.health"> <img src="https://github.com/Nozbe/WatermelonDB/raw/master/assets/apps/steady.png" alt="Steady" width="150"> </a> <br> <a href="https://aerobotics.com"> <img src="https://github.com/Nozbe/WatermelonDB/raw/master/assets/apps/aerobotics.png" alt="Aerobotics" width="300" /> </a> <br> <a href="https://smashappz.com"> <img src="https://github.com/Nozbe/WatermelonDB/raw/master/assets/apps/smashappz.jpg" alt="Smash Appz" width="300" /> </a> <br> <a href="https://rocket.chat/"> <img src="https://github.com/Nozbe/WatermelonDB/raw/master/assets/apps/rocketchat.png" alt="Rocket Chat" width="300" /> </a> <br> <a href="https://halogo.com.au/"> <img src="https://github.com/Nozbe/WatermelonDB/raw/master/assets/apps/halogo_logo.png" alt="HaloGo" width="300" /> </a> <br> <a href="https://sportsrecruits.com/"> <img src="https://github.com/Nozbe/WatermelonDB/raw/master/assets/apps/sportsrecruits-logo.png" alt="SportsRecruits" width="300" /> </a> <br> <a href="https://chatable.io/"> <img src="https://github.com/Nozbe/WatermelonDB/raw/master/assets/apps/chatable_logo.png" alt="Chatable" width="300" /> </a> <br> <p><em>Does your company or app use 🍉? Open a pull request and add your logo/icon with link here!</em></p> <h2 id="contributing"><a class="header" href="#contributing">Contributing</a></h2> <img src="https://github.com/Nozbe/WatermelonDB/raw/master/assets/needyou.jpg" alt="We need you" width="220" /> <p><strong>WatermelonDB is an open-source project and it needs your help to thrive!</strong></p> <p>If there's a missing feature, a bug, or other improvement you'd like, we encourage you to contribute! Feel free to open an issue to get some guidance and see <a href="./CONTRIBUTING.html">Contributing guide</a> for details about project setup, testing, etc.</p> <p>If you're just getting started, see <a href="https://github.com/Nozbe/WatermelonDB/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22">good first issues</a> that are easy to contribute to. If you make a non-trivial contribution, email me, and I'll send you a nice 🍉 sticker!</p> <p>If you make or are considering making an app using WatermelonDB, please let us know!</p> <h2 id="author-and-license"><a class="header" href="#author-and-license">Author and license</a></h2> <p><strong>WatermelonDB</strong> was created by <a href="https://github.com/Nozbe">@Nozbe</a>. Main author and maintainer is <a href="https://github.com/radex">Radek Pietruszewski</a>.</p> <p><strong>Contributors:</strong> <a href="https://github.com/mobily">@mobily</a>, <a href="https://github.com/kokusGr">@kokusGr</a>, <a href="https://github.com/rozPierog">@rozPierog</a>, <a href="https://github.com/rkrajewski">@rkrajewski</a>, <a href="https://github.com/domeknn">@domeknn</a>, <a href="https://github.com/Tereszkiewicz">@Tereszkiewicz</a> and <a href="https://github.com/Nozbe/WatermelonDB/graphs/contributors">more</a>.</p> <p>WatermelonDB is available under the MIT license. See the <a href="./LICENSE">LICENSE file</a> for more info.</p> <h1 id="demo"><a class="header" href="#demo">Demo</a></h1> <p>See how WatermelonDB performs at large scales in the demo app.</p> <h2 id="online-demo"><a class="header" href="#online-demo">Online demo</a></h2> <h3> <a href="https://watermelondb.now.sh"> <img src="https://github.com/Nozbe/WatermelonDB/raw/master/assets/watermelon-demo-medium.png" alt="WatermelonDB Demo" width="600" /><br> Check out WatermelonDB demo online </a> </h3> <p>Note that where Watermelon really shines is in React Native apps — see instructions below ⬇️</p> <h2 id="running-react-native-demo"><a class="header" href="#running-react-native-demo">Running React Native demo</a></h2> <p>To compile the WatermelonDB demo on your own machine:</p> <ol> <li>Install <a href="https://facebook.github.io/react-native/docs/getting-started.html">React Native toolkit</a> if you haven't already</li> <li>Download this project <pre><code class="language-bash">git clone https://github.com/Nozbe/WatermelonDB.git cd WatermelonDB/examples/native yarn </code></pre> </li> <li>Run the React Native packager: <pre><code class="language-bash">yarn dev </code></pre> </li> <li>Run the app on iOS or Android: <pre><code class="language-bash">yarn start:ios # or: yarn start:android </code></pre> </li> </ol> <p>⚠️ Note that for accurate measurement of performance, you need to compile the demo app in Release mode and run it on a real device, not the simulator.</p> <p>⚠️ If iOS app doesn't compile, try running it from Xcode instead of the terminal first</p> <p>⚠️ You might want to <code>git checkout</code> the <a href="https://github.com/Nozbe/WatermelonDB/releases">latest stable tag</a> if the demo app doesn't work</p> <h2 id="running-web-demo"><a class="header" href="#running-web-demo">Running web demo</a></h2> <p>To compile the WatermelonDB demo on your own machine:</p> <ol> <li>Download this project <pre><code class="language-bash">git clone https://github.com/Nozbe/WatermelonDB.git cd WatermelonDB/examples/web yarn </code></pre> </li> <li>Run the server: <pre><code class="language-bash">yarn dev </code></pre> </li> <li>Webpack will point you to the right URL to open in the browser</li> </ol> <p>You can also <a href="https://zeit.co/now">use Now</a> to deploy the demo app (requires a Zeit account):</p> <pre><code class="language-bash">now </code></pre> <p>⚠️ You might want to <code>git checkout</code> the <a href="https://github.com/Nozbe/WatermelonDB/releases">latest stable tag</a> if the demo app doesn't work</p> <h3 id="learn-to-use-watermelon"><a class="header" href="#learn-to-use-watermelon">Learn to use Watermelon</a></h3> <p>Learn the basics of how to use WatermelonDB</p> <h1 id="installation"><a class="header" href="#installation">Installation</a></h1> <p>First, add Watermelon to your project:</p> <pre><code class="language-bash">yarn add @rikishi/watermelondb # (or with npm:) npm install @rikishi/watermelondb </code></pre> <h2 id="react-native-setup"><a class="header" href="#react-native-setup">React Native setup</a></h2> <ol> <li> <p>Install the Babel plugin for decorators if you haven't already:</p> <pre><code class="language-bash">yarn add --dev @babel/plugin-proposal-decorators # (or with npm:) npm install -D @babel/plugin-proposal-decorators </code></pre> </li> <li> <p>Add ES6 decorators support to your <code>.babelrc</code> file:</p> <pre><code class="language-json">{ &quot;presets&quot;: [&quot;module:metro-react-native-babel-preset&quot;], &quot;plugins&quot;: [ [&quot;@babel/plugin-proposal-decorators&quot;, { &quot;legacy&quot;: true }] ] } </code></pre> </li> <li> <p>Set up your iOS or Android project — see instructions below</p> </li> </ol> <h3 id="ios-react-native"><a class="header" href="#ios-react-native">iOS (React Native)</a></h3> <ol> <li> <p><strong>Set up Babel config in your project</strong></p> <p>See instructions above ⬆️</p> </li> <li> <p><strong>Add Swift support to your Xcode project</strong>:</p> <ul> <li>Open <code>ios/YourAppName.xcodeproj</code> in Xcode</li> <li>Right-click on <strong>(your app name)</strong> in the Project Navigator on the left, and click <strong>New File…</strong></li> <li>Create a single empty Swift file (<code>wmelon.swift</code>) to the project (make sure that <strong>Your App Name</strong> target is selected when adding), and when Xcode asks, press <strong>Create Bridging Header</strong> and <strong>do not remove</strong> the Swift file afterwards</li> </ul> </li> <li> <p><strong>Link WatermelonDB's native library using CocoaPods</strong></p> <p>Add this to your <code>Podfile</code>:</p> <pre><code class="language-ruby"># If you're using autolinking, this line might not be needed pod 'WatermelonDB', :path =&gt; '../node_modules/@rikishi/watermelondb' # NOTE: Do not remove, needed to keep WatermelonDB compiling: pod 'React-jsi', :path =&gt; '../node_modules/react-native/ReactCommon/jsi', :modular_headers =&gt; true # NOTE: This is required as of v0.23 pod 'simdjson', path: '../node_modules/@nozbe/simdjson' </code></pre> <p>Note that as of WatermelonDB 0.22, manual (non-CocoaPods) linking is not supported.</p> <p>At least Xcode 12.2 and iOS 13 are recommended (earlier versions are not tested for compatibility).</p> </li> <li> <p><strong>Fix up your Bridging Header</strong></p> <p>You will likely see that the iOS build fails to compile. If this happens, locate the Swift Bridging Header (likely <code>ios/YourAppName/YourAppName-Bridging-Header.h</code>), and paste this:</p> <pre><code class="language-objc">#import &lt;React/RCTBundleURLProvider.h&gt; #import &lt;React/RCTRootView.h&gt; #import &lt;React/RCTViewManager.h&gt; #import &lt;React/RCTBridgeModule.h&gt; // Silence warning #import &quot;../../node_modules/@rikishi/watermelondb/native/ios/WatermelonDB/SupportingFiles/Bridging.h&quot; </code></pre> <p>You might have to tweak the import path to correctly locate Watermelon's bridging header.</p> </li> </ol> <h3 id="android-react-native"><a class="header" href="#android-react-native">Android (React Native)</a></h3> <p><strong>Set up Babel config in your project</strong></p> <p>See instructions above ⬆️</p> <p>On RN60+, auto linking should work.</p> <details> <summary>Linking Manually</summary> <p>Users on React Native 0.60+ automatically have access to &quot;autolinking&quot;, requiring no further manual installation steps. If you are on React Native 0.60+ please skip this section. If you are on React Native &lt; 0.60 please do the following in <strong>addition</strong> to the previous steps:</p> <ol> <li>In <code>android/settings.gradle</code>, add:</li> </ol> <pre><code class="language-gradle">include ':watermelondb' project(':watermelondb').projectDir = new File(rootProject.projectDir, '../node_modules/@rikishi/watermelondb/native/android') </code></pre> <ol start="2"> <li>In <code>android/app/build.gradle</code>, add:</li> </ol> <pre><code class="language-gradle">// ... dependencies { // ... implementation project(':watermelondb') // ⬅️ This! } </code></pre> <ol start="3"> <li>And finally, in <code>android/app/src/main/java/{YOUR_APP_PACKAGE}/MainApplication.java</code>, add:</li> </ol> <pre><code class="language-java">// ... import com.nozbe.watermelondb.WatermelonDBPackage; // ⬅️ This! // ... @Override protected List&lt;ReactPackage&gt; getPackages() { return Arrays.&lt;ReactPackage&gt;asList( new MainReactPackage(), new WatermelonDBPackage() // ⬅️ Here! ); } </code></pre> </details> <details> <summary>Custom Kotlin Version ⚠️</summary> Make sure the kotlin version is set to 1.3.50 or above. Just set ext properties `kotlinVersion` in `android/build.gradle`, and WatermelonDB will use the specified kotlin version. <pre><code class="language-gradle">buildscript { ext.kotlinVersion = '1.3.50' } </code></pre> </details> <details> <summary>Troubleshooting</summary> If you get this error: <blockquote> <p><code>Can't find variable: Symbol</code></p> </blockquote> <p>You're using an ancient version of JSC. Install <a href="https://github.com/react-community/jsc-android-buildscripts"><code>jsc-android</code></a> or Hermes.</p> </details> <details> <summary>JSI Installation (Optional)</summary> <p>To enable fast, highly performant, synchronous JSI operation on Android, you need to take a few additional steps manually.</p> <ol> <li> <p>Make sure you have NDK installed (version <code>20.1.5948944</code> has been tested to work when writing this guide)</p> </li> <li> <p>In <code>android/settings.gradle</code>, add:</p> <pre><code class="language-gradle">include ':watermelondb-jsi' project(':watermelondb-jsi').projectDir = new File(rootProject.projectDir, '../node_modules/@rikishi/watermelondb/native/android-jsi') </code></pre> </li> <li> <p>In <code>android/app/build.gradle</code>, add:</p> <pre><code class="language-gradle">// ... android { // ... packagingOptions { pickFirst '**/libc++_shared.so' // ⬅️ This (if missing) } } dependencies { // ... implementation project(':watermelondb-jsi') // ⬅️ This! } </code></pre> </li> <li> <p>And finally, in <code>android/app/src/main/java/{YOUR_APP_PACKAGE}/MainApplication.java</code>, add:</p> <pre><code class="language-java">// ... import com.nozbe.watermelondb.jsi.WatermelonDBJSIPackage; // ⬅️ This! import com.facebook.react.bridge.JSIModulePackage; // ⬅️ This! // ... private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { // ... @Override protected JSIModulePackage getJSIModulePackage() { return new WatermelonDBJSIPackage(); // ⬅️ This! } } </code></pre> <p>or if you have <strong>multiple</strong> JSI Packages:</p> <pre><code class="language-java">// ... import java.util.Arrays; // ⬅️ This! import com.facebook.react.bridge.JSIModuleSpec; // ⬅️ This! import com.facebook.react.bridge.JSIModulePackage; // ⬅️ This! import com.facebook.react.bridge.ReactApplicationContext; // ⬅️ This! import com.facebook.react.bridge.JavaScriptContextHolder; // ⬅️ This! import com.nozbe.watermelondb.jsi.WatermelonDBJSIPackage; // ⬅️ This! // ... private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { // ... @Override protected JSIModulePackage getJSIModulePackage() { return new JSIModulePackage() { @Override public List&lt;JSIModuleSpec&gt; getJSIModules( final ReactApplicationContext reactApplicationContext, final JavaScriptContextHolder jsContext ) { List&lt;JSIModuleSpec&gt; modules = Arrays.asList(); modules.addAll(new WatermelonDBJSIPackage().getJSIModules(reactApplicationContext, jsContext)); // ⬅️ This! // ⬅️ add more JSI packages here by conventions above return modules; } }; } } </code></pre> </li> </ol> </details> <h2 id="web-setup"><a class="header" href="#web-setup">Web setup</a></h2> <p>This guide assumes you use Webpack as your bundler.</p> <ol> <li>If you haven't already, install Babel plugins for decorators, static class properties, and async/await to get the most out of Watermelon. This assumes you use Babel 7 and already support ES6 syntax. <pre><code class="language-bash">yarn add --dev @babel/plugin-proposal-decorators yarn add --dev @babel/plugin-proposal-class-properties yarn add --dev @babel/plugin-transform-runtime # (or with npm:) npm install -D @babel/plugin-proposal-decorators npm install -D @babel/plugin-proposal-class-properties npm install -D @babel/plugin-transform-runtime </code></pre> </li> <li>Add ES7 support to your <code>.babelrc</code> file: <pre><code class="language-json">{ &quot;plugins&quot;: [ [&quot;@babel/plugin-proposal-decorators&quot;, { &quot;legacy&quot;: true }], [&quot;@babel/plugin-proposal-class-properties&quot;, { &quot;loose&quot;: true }], [ &quot;@babel/plugin-transform-runtime&quot;, { &quot;helpers&quot;: true, &quot;regenerator&quot;: true } ] ] } </code></pre> </li> </ol> <h2 id="nodejs-sqlite-setup"><a class="header" href="#nodejs-sqlite-setup">NodeJS (SQLite) setup</a></h2> <p>You only need this if you want to use WatermelonDB in NodeJS with SQLite (e.g. for scripts that share code with your web/React Native app)</p> <ol> <li>Install <a href="https://github.com/JoshuaWise/better-sqlite3">better-sqlite3</a> peer dependency <pre><code class="language-sh">yarn add --dev better-sqlite3 # (or with npm:) npm install -D better-sqlite3 </code></pre> </li> </ol> <hr /> <h2 id="next-steps"><a class="header" href="#next-steps">Next steps</a></h2> <p>➡️ After Watermelon is installed, <a href="./Setup.html"><strong>set it up</strong></a></p> <h1 id="set-up-your-app-for-watermelondb"><a class="header" href="#set-up-your-app-for-watermelondb">Set up your app for WatermelonDB</a></h1> <p>Make sure you <a href="./Installation.html">installed Watermelon</a> before proceeding.</p> <p>Create <code>model/schema.js</code> in your project. You'll need it for <a href="./Schema.html">the next step</a>.</p> <pre><code class="language-js">import { appSchema, tableSchema } from '@rikishi/watermelondb' export default appSchema({ version: 1, tables: [ // We'll add tableSchemas here later ] }) </code></pre> <p>Similarly, create <code>model/migrations.js</code>. (<a href="./Advanced/Migrations.html">More information about migrations</a>):</p> <pre><code class="language-js">import { schemaMigrations } from '@rikishi/watermelondb/Schema/migrations' export default schemaMigrations({ migrations: [ // We'll add migration definitions here later ], }) </code></pre> <p>Now, in your <code>index.native.js</code>:</p> <pre><code class="language-js">import { Platform } from 'react-native' import { Database } from '@rikishi/watermelondb' import SQLiteAdapter from '@rikishi/watermelondb/adapters/sqlite' import schema from './model/schema' import migrations from './model/migrations' // import Post from './model/Post' // ⬅️ You'll import your Models here // First, create the adapter to the underlying database: const adapter = new SQLiteAdapter({ schema, // (You might want to comment it out for development purposes -- see Migrations documentation) migrations, // (optional database name or file system path) // dbName: 'myapp', // (recommended option, should work flawlessly out of the box on iOS. On Android, // additional installation steps have to be taken - disable if you run into issues...) jsi: true, /* Platform.OS === 'ios' */ // (optional, but you should implement this method) onSetUpError: error =&gt; { // Database failed to load -- offer the user to reload the app or log out } }) // Then, make a Watermelon database from it! const database = new Database({ adapter, modelClasses: [ // Post, // ⬅️ You'll add Models to Watermelon here ], }) </code></pre> <p>The above will work on React Native (iOS/Android) and NodeJS. For the web, instead of <code>SQLiteAdapter</code> use <code>LokiJSAdapter</code>:</p> <pre><code class="language-js">import LokiJSAdapter from '@rikishi/watermelondb/adapters/lokijs' const adapter = new LokiJSAdapter({ schema, // (You might want to comment out migrations for development purposes -- see Migrations documentation) migrations, useWebWorker: false, useIncrementalIndexedDB: true, // dbName: 'myapp', // optional db name // --- Optional, but recommended event handlers: onQuotaExceededError: (error) =&gt; { // Browser ran out of disk space -- offer the user to reload the app or log out }, onSetUpError: (error) =&gt; { // Database failed to load -- offer the user to reload the app or log out }, extraIncrementalIDBOptions: { onDidOverwrite: () =&gt; { // Called when this adapter is forced to overwrite contents of IndexedDB. // This happens if there's another open tab of the same app that's making changes. // Try to synchronize the app now, and if user is offline, alert them that if they close this // tab, some data may be lost }, onversionchange: () =&gt; { // database was deleted in another browser tab (user logged out), so we must make sure we delete // it in this tab as well - usually best to just refresh the page if (checkIfUserIsLoggedIn()) { window.location.reload() } }, } }) // The rest is the same! </code></pre> <hr /> <h2 id="next-steps-1"><a class="header" href="#next-steps-1">Next steps</a></h2> <p>➡️ After Watermelon is installed, <a href="./Schema.html"><strong>define your app's schema</strong></a></p> <h1 id="schema"><a class="header" href="#schema">Schema</a></h1> <p>When using WatermelonDB, you're dealing with <strong>Models</strong> and <strong>Collections</strong>. However, underneath Watermelon sits an <strong>underlying database</strong> (SQLite or LokiJS) which speaks a different language: <strong>tables and columns</strong>. Together, those are called a <strong>database schema</strong> and we must define it first.</p> <h2 id="defining-a-schema"><a class="header" href="#defining-a-schema">Defining a Schema</a></h2> <p>Say you want Models <code>Post</code>, <code>Comment</code> in your app. For each of those Models, you define a table. And for every field of a Model (e.g. name of the blog post, author of the comment) you define a column. For example:</p> <pre><code class="language-js">// model/schema.js import { appSchema, tableSchema } from '@rikishi/watermelondb' export const mySchema = appSchema({ version: 1, tables: [ tableSchema({ name: 'posts', columns: [ { name: 'title', type: 'string' }, { name: 'subtitle', type: 'string', isOptional: true }, { name: 'body', type: 'string' }, { name: 'is_pinned', type: 'boolean' }, ] }), tableSchema({ name: 'comments', columns: [ { name: 'body', type: 'string' }, { name: 'post_id', type: 'string', isIndexed: true }, ] }), ] }) </code></pre> <p><strong>Note:</strong> It is database convention to use plural and snake_case names for table names. Column names are also snake_case. So <code>Post</code> become <code>posts</code> and <code>createdAt</code> becomes <code>created_at</code>.</p> <h3 id="column-types"><a class="header" href="#column-types">Column types</a></h3> <p>Columns have one of three types: <code>string</code>, <code>number</code>, or <code>boolean</code>.</p> <p>Fields of those types will default to <code>''</code>, <code>0</code>, or <code>false</code> respectively, if you create a record with a missing field.</p> <p>To allow fields to be <code>null</code>, mark the column as <code>isOptional: true</code>.</p> <h3 id="naming-conventions"><a class="header" href="#naming-conventions">Naming conventions</a></h3> <p>To add a relation to a table (e.g. <code>Post</code> where a <code>Comment</code> was published, or author of a comment), add a string column ending with <code>_id</code>:</p> <pre><code class="language-js">{ name: 'post_id', type: 'string' }, { name: 'author_id', type: 'string' }, </code></pre> <p>Boolean columns should have names starting with <code>is_</code>:</p> <pre><code class="language-js">{ name: 'is_pinned', type: 'boolean' } </code></pre> <p>Date fields should be <code>number</code> (dates are stored as Unix timestamps) and have names ending with <code>_at</code>:</p> <pre><code class="language-js">{ name: 'last_seen_at', type: 'number', isOptional: true } </code></pre> <h3 id="special-columns"><a class="header" href="#special-columns">Special columns</a></h3> <p>All tables <em>automatically</em> have a string column <code>id</code> (of <code>string</code> type) to uniquely identify records -- therefore you cannot declare a column named <code>id</code> yourself. (There are also special <code>_status</code> and <code>_changed</code> columns used for <a href="./Advanced/Sync.html">synchronization</a> - you shouldn't touch them yourself).</p> <p>You can add special <code>created_at</code> / <code>updated_at</code> columns to enable <a href="./Advanced/CreateUpdateTracking.html">automatic create/update tracking</a>.</p> <h3 id="modifying-schema"><a class="header" href="#modifying-schema">Modifying Schema</a></h3> <p>Watermelon cannot automatically detect Schema changes. Therefore, whenever you change the Schema, you must increment its version number (<code>version:</code> field).</p> <p>During early development, this is all you need to do - on app reload, this will cause the database to be cleared completely.</p> <p>To seamlessly update the schema (without deleting user data), use <a href="./Advanced/Migrations.html">Migrations</a>.</p> <p>⚠️ Always use Migrations if you already shipped your app.</p> <h3 id="indexing"><a class="header" href="#indexing">Indexing</a></h3> <p>To enable database indexing, add <code>isIndexed: true</code> to a column.</p> <p>Indexing makes querying by a column faster, at the expense of create/update speed and database size.</p> <p>For example, if you often query all comments belonging to a post (that is, query comments by its <code>post_id</code> column), you should mark the <code>post_id</code> column as indexed.</p> <p>However, if you rarely query all comments by its author, indexing <code>author_id</code> is probably not worth it.</p> <p>In general, most <code>_id</code> fields are indexed. Occasionally, <code>boolean</code> fields are worth indexing (but it's a &quot;low quality index&quot;). However, you should almost never index date (<code>_at</code>) columns or <code>string</code> columns. You definitely do not want to index long-form user text.</p> <p>⚠️ Do not mark all columns as indexed to &quot;make Watermelon faster&quot;. Indexing has a real performance cost and should be used only when appropriate.</p> <h2 id="advanced"><a class="header" href="#advanced">Advanced</a></h2> <h3 id="unsafe-sql-schema"><a class="header" href="#unsafe-sql-schema">Unsafe SQL schema</a></h3> <p>If you want to modify the SQL used to set up the SQLite database, you can pass <code>unsafeSql</code> parameter to <code>tableSchema</code> and <code>appSchema</code>. This parameter is a function that receives SQL generated by Watermelon, and you can return whatever you want - so you can append, prepend, replace parts of SQL, or return your own SQL altogether. When passed to <code>tableSchema</code>, it receives SQL generated for just that table, and when to <code>appSchema</code> - the entire schema SQL.</p> <p>⚠️ Note that SQL generated by WatermelonDB is not considered to be a stable API, so be careful about your transforms as they can break at any time.</p> <pre><code class="language-js">appSchema({ ... tables: [ tableSchema({ name: 'tasks', columns: [...], unsafeSql: sql =&gt; sql.replace(/create table [^)]+\)/, '$&amp; without rowid'), }), ], unsafeSql: (sql, kind) =&gt; { // Note that this function is called not just when first setting up the database // Additionally, when running very large batches, all database indices may be dropped and later // recreated as an optimization. More kinds may be added in the future. switch (kind) { case 'setup': return `create blabla;${sql}` case 'create_indices': case 'drop_indices': return sql default: throw new Error('unexpected unsafeSql kind') } }, }) </code></pre> <hr /> <h2 id="next-steps-2"><a class="header" href="#next-steps-2">Next steps</a></h2> <p>➡️ After you define your schema, go ahead and <a href="./Model.html"><strong>define your Models</strong></a></p> <h1 id="defining-models"><a class="header" href="#defining-models">Defining Models</a></h1> <p>A <strong>Model</strong> class represents a type of thing in your app. For example, <code>Post</code>, <code>Comment</code>, <code>User</code>.</p> <p>Before defining a Model, make sure you <a href="./Schema.html">defined its schema</a>.</p> <h2 id="create-a-model"><a class="header" href="#create-a-model">Create a Model</a></h2> <p>Let's define the <code>Post</code> model:</p> <pre><code class="language-js">// model/Post.js import { Model } from '@rikishi/watermelondb' export default class Post extends Model { static table = 'posts' } </code></pre> <p>Specify the table name for this Model — the same you defined <a href="./Schema.html">in the schema</a>.</p> <p>Now add the new Model to <code>Database</code>:</p> <pre><code class="language-js">// index.js import Post from 'model/Post' const database = new Database({ // ... modelClasses: [Post], }) </code></pre> <h3 id="associations"><a class="header" href="#associations">Associations</a></h3> <p>Many models relate to one another. A <code>Post</code> has many <code>Comment</code>s. And every <code>Comment</code> belongs to a <code>Post</code>. (Every relation is double-sided). Define those associations like so:</p> <pre><code class="language-js">class Post extends Model { static table = 'posts' static associations = { comments: { type: 'has_many', foreignKey: 'post_id' }, } } class Comment extends Model { static table = 'comments' static associations = { posts: { type: 'belongs_to', key: 'post_id' }, } } </code></pre> <p>On the &quot;child&quot; side (<code>comments</code>) you define a <code>belongs_to</code> association, and pass a column name (key) that points to the parent (<code>post_id</code> is the ID of the post the comment belongs to).</p> <p>On the &quot;parent&quot; side (<code>posts</code>) you define an equivalent <code>has_many</code> association and pass the same column name (⚠️ note that the name here is <code>foreignKey</code>).</p> <h2 id="add-fields"><a class="header" href="#add-fields">Add fields</a></h2> <p>Next, define the Model's <em>fields</em> (properties). Those correspond to <a href="./Schema.html">table columns</a> defined earlier in the schema.</p> <pre><code class="language-js">import { field, text } from '@rikishi/watermelondb/decorators' class Post extends Model { static table = 'posts' static associations = { comments: { type: 'has_many', foreignKey: 'post_id' }, } @text('title') title @text('body') body @field('is_pinned') isPinned } </code></pre> <p>Fields are defined using ES6 decorators. Pass <strong>column name</strong> you defined in Schema as the argument to <code>@field</code>.</p> <p><strong>Field types</strong>. Fields are guaranteed to be the same type (string/number/boolean) as the column type defined in Schema. If column is marked <code>isOptional: true</code>, fields may also be null.</p> <p><strong>User text fields</strong>. For fields that contain arbitrary text specified by the user (e.g. names, titles, comment bodies), use <code>@text</code> - a simple extension of <code>@field</code> that also trims whitespace.</p> <p><strong>Note:</strong> Why do I have to type the field/column name twice? The database convention is to use <code>snake_case</code> for names, and the JavaScript convention is to use camelCase. So for any multi-word name, the two differ. Also, for resiliency, we believe it's better to be explicit, because over time, you might want to refactor how you name your JavaScript field names, but column names must stay the same for backward compatibility.</p> <h3 id="date-fields"><a class="header" href="#date-fields">Date fields</a></h3> <p>For date fields, use <code>@date</code> instead of <code>@field</code>. This will return a JavaScript <code>Date</code> object (instead of Unix timestamp integer).</p> <pre><code class="language-js">import { date } from '@rikishi/watermelondb/decorators' class Post extends Model { // ... @date('last_event_at') lastEventAt } </code></pre> <h3 id="derived-fields"><a class="header" href="#derived-fields">Derived fields</a></h3> <p>Use ES6 getters to define model properties that can be calculated based on database fields:</p> <pre><code class="language-js">import { field, text } from '@rikishi/watermelondb/decorators' class Post extends Model { static table = 'posts' @date('archived_at') archivedAt get isRecentlyArchived() { // in the last 7 days return this.archivedAt &amp;&amp; this.archivedAt.getTime() &gt; Date.now() - 7 * 24 * 3600 * 1000 } } </code></pre> <h3 id="to-one-relation-fields"><a class="header" href="#to-one-relation-fields">To-one relation fields</a></h3> <p>To point to a related record, e.g. <code>Post</code> a <code>Comment</code> belongs to, or author (<code>User</code>) of a <code>Comment</code>, use <code>@relation</code> or <code>@immutableRelation</code>:</p> <pre><code class="language-js">impor