### Upgrade Web Example Dependencies
Source: https://watermelondb.dev/docs/Implementation/Publishing
Navigate to the web examples directory and upgrade dependencies to their latest versions. Then, start the development server to check if the web demo works.
```bash
cd ../web
yarn upgrade-interactive --latest
yarn dev
# check out if web works
```
--------------------------------
### Upgrade Native Example Dependencies
Source: https://watermelondb.dev/docs/Implementation/Publishing
Navigate to the native examples directory and upgrade dependencies to their latest versions. Then, start the development server for iOS and Android.
```bash
cd examples/native
yarn upgrade-interactive --latest
yarn dev
yarn start:ios
yarn start:android
```
--------------------------------
### Install Babel plugins for Web
Source: https://watermelondb.dev/docs/Installation
Install required Babel plugins to support decorators, class properties, and async/await.
```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
```
--------------------------------
### Clone Repository and Install Dependencies
Source: https://watermelondb.dev/docs/CONTRIBUTING
Download the WatermelonDB source code and install all necessary project dependencies using Yarn. This is the first step for setting up a development environment.
```bash
git clone https://github.com/Nozbe/WatermelonDB.git
cd WatermelonDB
yarn
```
--------------------------------
### Install better-sqlite3 for NodeJS
Source: https://watermelondb.dev/docs/Installation
Install the peer dependency required for using WatermelonDB with SQLite in a NodeJS environment.
```bash
yarn add --dev better-sqlite3
# (or with npm:)
npm install -D better-sqlite3
```
--------------------------------
### Initialize Migrations Setup
Source: https://watermelondb.dev/docs/Advanced/Migrations
Add a new file for migrations and import schemaMigrations. This file will hold all your migration definitions.
```javascript
// app/model/migrations.js
import { schemaMigrations } from '@nozbe/watermelondb/Schema/migrations'
export default schemaMigrations({
migrations: [
// We'll add migration definitions here later
],
})
```
--------------------------------
### Configure iOS Podfile
Source: https://watermelondb.dev/docs/CHANGELOG
Add this line to your Podfile for iOS installation.
```ruby
pod 'simdjson', path: '../node_modules/@nozbe/simdjson'
```
--------------------------------
### Install Babel decorator plugin
Source: https://watermelondb.dev/docs/Installation
Install the required Babel plugin for ES6 decorators support.
```bash
yarn add --dev @babel/plugin-proposal-decorators
# (or with npm:)
npm install -D @babel/plugin-proposal-decorators
```
--------------------------------
### Initialize WatermelonDBJSIPackage
Source: https://watermelondb.dev/docs/Installation
In `android/app/src/main/java/{YOUR_APP_PACKAGE}/MainApplication.java`, override `getJSIModulePackage` to return a new `WatermelonDBJSIPackage`. This is for basic JSI setup.
```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!
}
}
```
--------------------------------
### Install WatermelonDB package
Source: https://watermelondb.dev/docs/Installation
Add the WatermelonDB library to your project using yarn or npm.
```bash
yarn add @nozbe/watermelondb
# (or with npm:)
npm install @nozbe/watermelondb
```
--------------------------------
### Setup Database Provider for Component Access
Source: https://watermelondb.dev/docs/Components
Wrap your application with DatabaseProvider to make the database instance available to components. Requires adapter and model classes.
```javascript
import { DatabaseProvider } from '@nozbe/watermelondb/react'
// ...
const database = new Database({
adapter,
modelClasses: [Blog, Post, Comment],
})
render(
, document.getElementById('application')
)
```
--------------------------------
### Hook Up Migrations to Database Adapter
Source: https://watermelondb.dev/docs/Advanced/Migrations
Connect your defined migrations to the SQLiteAdapter during database setup. Ensure the migrations object is passed to the adapter configuration.
```javascript
// index.js
import migrations from 'model/migrations'
const adapter = new SQLiteAdapter({
schema: mySchema,
migrations,
})
```
--------------------------------
### Run WatermelonDB in Development Mode
Source: https://watermelondb.dev/docs/CONTRIBUTING
Starts a development server that observes changes in JavaScript source files and recompiles them as needed. Useful for local development.
```bash
yarn dev
```
--------------------------------
### Fetch Query Results and Count
Source: https://watermelondb.dev/docs/Query
Use fetch() to get the current list of records and fetchCount() to get the number of records matching a query. Shortcut syntax is available for both.
```javascript
const comments = await post.comments.fetch()
const verifiedCommentCount = await post.verifiedComments.fetchCount()
// Shortcut syntax:
const comments = await post.comments
const verifiedCommentCount = await post.verifiedComments.count
```
--------------------------------
### Define WatermelonDB Schema with Tables and Columns
Source: https://watermelondb.dev/docs/Schema
Define your database schema using `appSchema` and `tableSchema`. This example sets up 'posts' and 'comments' tables with various column types and properties.
```javascript
import { appSchema, tableSchema } from '@nozbe/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 },
]
}),
]
})
```
--------------------------------
### Define Schema Migrations in WatermelonDB
Source: https://watermelondb.dev/docs/Advanced/Migrations
Example of defining multiple schema migrations, including creating a new 'comments' table and adding columns to the 'posts' table. Ensure migrations are ordered by `toVersion`.
```javascript
schemaMigrations({
migrations: [
{
toVersion: 3,
steps: [
createTable({
name: 'comments',
columns: [
{ name: 'post_id', type: 'string', isIndexed: true },
{ name: 'body', type: 'string' },
],
}),
addColumns({
table: 'posts',
columns: [
{ name: 'subtitle', type: 'string', isOptional: true },
{ name: 'is_pinned', type: 'boolean' },
],
}),
],
},
{
toVersion: 2,
steps: [
// ...
],
},
],
})
```
--------------------------------
### Execute a Custom Query on a Table
Source: https://watermelondb.dev/docs/Query
Query any table by specifying the table name and conditions. This example fetches users who commented on a post with a specific ID.
```javascript
import { Q } from '@nozbe/watermelondb'
const users = await database.get('users').query(
// conditions that a user must match:
Q.on('comments', 'post_id', somePostId)
).fetch()
```
--------------------------------
### Include WatermelonDB-JSI in Android Settings
Source: https://watermelondb.dev/docs/Installation
Add this to your `android/settings.gradle` file for JSI installation. This step is required for enabling fast, synchronous JSI operation on Android.
```gradle
include ':watermelondb-jsi'
project(':watermelondb-jsi').projectDir =
new File(rootProject.projectDir, '../node_modules/@nozbe/watermelondb/native/android-jsi')
```
--------------------------------
### Set, Get, and Remove Local Storage Values
Source: https://watermelondb.dev/docs/Advanced/LocalStorage
Demonstrates how to set, retrieve, and remove values from WatermelonDB's local storage. Values must be JSON-serializable. Keys should be predefined and not user-supplied.
```javascript
await database.localStorage.set("user_id", "abcdef")
```
```javascript
const userId = await database.localStorage.get("user_id") // string or undefined if no value for this key
```
```javascript
await database.localStorage.remove("user_id")
```
--------------------------------
### Example Changes Object Structure
Source: https://watermelondb.dev/docs/Sync/Backend
Represents the raw data format for synchronized records, using table names as keys and containing arrays for created, updated, and deleted items.
```javascript
{
projects: {
created: [
{ id: 'aaaa', name: 'Foo', is_favorite: true },
{ id: 'bbbb', name: 'Bar', is_favorite: false },
],
updated: [
{ id: 'ccc', name: 'Baz', is_favorite: true },
],
deleted: ['ddd'],
},
tasks: {
created: [],
updated: [
{ id: 'tttt', name: 'Buy eggs' },
],
deleted: [],
},
...
}
```
--------------------------------
### Include WatermelonDB in Android Settings
Source: https://watermelondb.dev/docs/Installation
Add this to your `android/settings.gradle` file to include the WatermelonDB library. This is part of the manual setup process when autolinking is not used.
```gradle
include ':watermelondb'
project(':watermelondb').projectDir =
new File(rootProject.projectDir, '../node_modules/@nozbe/watermelondb/native/android')
```
--------------------------------
### Handling Extra Data with onDidPullChanges
Source: https://watermelondb.dev/docs/Sync/Frontend
Passes extra keys in the sync response back to the app, usable with or without Turbo mode. This example shows how to process 'messages' from the sync response.
```javascript
await synchronize({
database,
pullChanges: async ({ lastPulledAt, schemaVersion, migration }) => {
// ...
},
unsafeTurbo: useTurbo,
onDidPullChanges: async ({ messages }) => {
if (messages) {
messages.forEach((message) => {
alert(message)
})
}
},
// ...
})
```
--------------------------------
### Observe Query Results in React Components
Source: https://watermelondb.dev/docs/Query
Connect queries to React components using withObservables to automatically update the UI when data changes. This example observes comments and a count of verified comments for a post.
```javascript
withObservables(['post'], ({ post }) => ({
post,
comments: post.comments,
verifiedCommentCount: post.verifiedComments.observeCount(),
}))
```
--------------------------------
### Observe Relation Changes with withObservables
Source: https://watermelondb.dev/docs/Relation
Connects Relations to Components using observe(). This example shows how to get the 'author' prop for a 'comment'.
```javascript
withObservables(['comment'], ({ comment }) => ({
comment,
author: comment.author, // shortcut syntax for `author: comment.author.observe()`
}))
```
--------------------------------
### Modify App Schema SQL with unsafeSql
Source: https://watermelondb.dev/docs/Schema
Use unsafeSql within appSchema to modify the entire schema SQL. This function receives the SQL and a 'kind' parameter indicating the operation type (e.g., 'setup', 'create_indices'). This example prepends custom SQL for the 'setup' kind.
```javascript
appSchema({
...
tables: [
tableSchema({
name: 'tasks',
columns: [...],
unsafeSql: sql => sql.replace(/create table [^)]+\)/, '$& without rowid'),
}),
],
unsafeSql: (sql, kind) => {
// 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')
}
},
})
```
--------------------------------
### Fixing crash on older Android React Native targets
Source: https://watermelondb.dev/docs/CHANGELOG
This fix addresses a crash on older Android React Native targets that lack 'jsc-android' installed. No specific code example is provided as it's a build/environment fix.
--------------------------------
### Deploy Web Demo
Source: https://watermelondb.dev/docs/Implementation/Publishing
Deploy the updated web demo using `now`. Alias the deployment to `watermelondb` for easy access.
```bash
now
now alias watermelondb-xxxxxxxxx.now.sh watermelondb
```
--------------------------------
### Disable WatermelonDB logging
Source: https://watermelondb.dev/docs/Advanced/Logging
Call the silence method on the logger instance before the application starts to suppress all output.
```javascript
import logger from '@nozbe/watermelondb/utils/common/logger';
logger.silence();
```
--------------------------------
### Add WatermelonDB-JSI Dependency
Source: https://watermelondb.dev/docs/Installation
Include the `watermelondb-jsi` library as an implementation dependency in your `android/app/build.gradle`. This is part of the JSI installation process.
```gradle
// ...
dependencies {
// ...
implementation project(':watermelondb-jsi') // ⬅️ This!
}
```
--------------------------------
### Initialize LokiJSAdapter for web
Source: https://watermelondb.dev/docs/Setup
Configure the LokiJS adapter for browser-based applications.
```javascript
import LokiJSAdapter from '@nozbe/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) => {
// Browser ran out of disk space -- offer the user to reload the app or log out
},
onSetUpError: (error) => {
// Database failed to load -- offer the user to reload the app or log out
},
extraIncrementalIDBOptions: {
onDidOverwrite: () => {
// 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: () => {
// 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!
```
--------------------------------
### Get a collection of records
Source: https://watermelondb.dev/docs/CRUD
Use `database.get('tableName')` to obtain a `Collection` object for interacting with records of a specific type.
```javascript
const postsCollection = database.get('posts')
```
--------------------------------
### Basic Turbo Login Sync Implementation
Source: https://watermelondb.dev/docs/Sync/Frontend
Use this for the initial sync when the database is empty. Ensure SQLiteAdapter with JSI is enabled and Chrome Remote Debugging is disabled. This method bypasses JSON parsing in pullChanges for raw text.
```javascript
const isFirstSync = ...
const useTurbo = isFirstSync
await synchronize({
database,
pullChanges: async ({ lastPulledAt, schemaVersion, migration }) => {
const response = await fetch(`https://my.backend/sync?${...}`)
if (!response.ok) {
throw new Error(await response.text())
}
if (useTurbo) {
// NOTE: DO NOT parse JSON, we want raw text
const json = await response.text()
return { syncJson: json }
} else {
const { changes, timestamp } = await response.json()
return { changes, timestamp }
}
},
unsafeTurbo: useTurbo,
// ...
})
```
--------------------------------
### Use new React helpers from @nozbe/watermelondb/react
Source: https://watermelondb.dev/docs/CHANGELOG
New React and React Native helpers are now available from the @nozbe/watermelondb/react folder. This includes DatabaseProvider, useDatabase, withDatabase, compose, withHooks, and the new WithObservables component.
```javascript
import { DatabaseProvider, useDatabase, withDatabase, compose, withHooks, WithObservables } from '@nozbe/watermelondb/react'
```
--------------------------------
### Import diagnostics tools from @nozbe/watermelondb/diagnostics
Source: https://watermelondb.dev/docs/CHANGELOG
Debug and diagnostic tools are now available from the @nozbe/watermelondb/diagnostics folder. This includes censorRaw, diagnoseDatabaseStructure, and diagnoseSyncConsistency.
```javascript
import { censorRaw, diagnoseDatabaseStructure, diagnoseSyncConsistency } from '@nozbe/watermelondb/diagnostics'
```
--------------------------------
### Add metro-minify-terser
Source: https://watermelondb.dev/docs/Sync/Troubleshoot
Install the terser minifier for Metro bundler to resolve React Native compilation issues with Watermelon Sync.
```bash
yarn add metro-minify-terser
```
--------------------------------
### Observe Many-To-Many Relation Authors
Source: https://watermelondb.dev/docs/Relation
Uses withObservables to fetch and observe the 'authors' relation for a given 'post', leveraging the many-to-many setup.
```javascript
withObservables(['post'], ({ post }) => ({
authors: post.authors,
}))
```
--------------------------------
### Build WatermelonDB
Source: https://watermelondb.dev/docs/Implementation/Publishing
Build the WatermelonDB project for distribution. This command generates the distributable files in the `dist/` directory.
```bash
yarn build
```
--------------------------------
### Adding Sync Logging with a Plain Object
Source: https://watermelondb.dev/docs/Sync/Frontend
Pass a plain object to synchronize to capture diagnostic information directly. Properties like startedAt and finishedAt will be populated by the sync process.
```javascript
// You don't have to use SyncLogger, just pass a plain object to synchronize()
const log = {}
await synchronize({ database, log, ... })
console.log(log.startedAt)
console.log(log.finishedAt)
```
--------------------------------
### Initialize SQLiteAdapter for native platforms
Source: https://watermelondb.dev/docs/Setup
Configure the SQLite adapter for React Native or NodeJS environments.
```javascript
import { Platform } from 'react-native'
import { Database } from '@nozbe/watermelondb'
import SQLiteAdapter from '@nozbe/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 => {
// 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
],
})
```
--------------------------------
### Configure iOS Podfile
Source: https://watermelondb.dev/docs/Installation
Add necessary native dependencies to your Podfile for iOS builds.
```ruby
# Uncomment this line if you're not using auto-linking or if auto-linking causes trouble
# pod 'WatermelonDB', path: '../node_modules/@nozbe/watermelondb'
# WatermelonDB dependency, should not be needed on modern React Native
# (please file an issue if this causes issues for you)
# pod 'React-jsi', path: '../node_modules/react-native/ReactCommon/jsi', modular_headers: true
# WatermelonDB dependency
pod 'simdjson', path: '../node_modules/@nozbe/simdjson', modular_headers: true
```
--------------------------------
### Configure Migration Sync
Source: https://watermelondb.dev/docs/Implementation/SyncImpl
Initializes the synchronization process with a specified schema version for migration support.
```javascript
synchronize({ migrationsEnabledAtVersion: XXX })
```
--------------------------------
### Run SwiftLint
Source: https://watermelondb.dev/docs/CONTRIBUTING
Lints native iOS Swift code to ensure it conforms to WatermelonDB's coding standards.
```bash
yarn swiftlint
```
--------------------------------
### Assign New Record to Relation using set()
Source: https://watermelondb.dev/docs/Relation
Assigns a new record to a relation within a create or update block. This example assigns a user to a comment.
```javascript
await database.get('comments').create(comment => {
comment.author.set(someUser)
// ...
})
```
--------------------------------
### Configure .babelrc for Web
Source: https://watermelondb.dev/docs/Installation
Add the necessary plugin configurations to your .babelrc file to enable ES7 features.
```json
{
"plugins": [
["@babel/plugin-proposal-decorators", { "legacy": true }],
["@babel/plugin-proposal-class-properties", { "loose": true }],
[
"@babel/plugin-transform-runtime",
{
"helpers": true,
"regenerator": true
}
]
]
}
```
--------------------------------
### Execute Raw SQL Queries
Source: https://watermelondb.dev/docs/CHANGELOG
New syntax for running unsafe raw SQL queries on a collection.
```javascript
collection.query(Q.unsafeSqlQuery("select * from tasks where foo = ?", ['bar'])).fetch()
```
--------------------------------
### Define a Custom Query on a Model
Source: https://watermelondb.dev/docs/Query
Create custom queries directly on a Model to fetch related records. This example fetches users who commented on a specific post.
```javascript
class Post extends Model {
// ...
@lazy commenters = this.collections.get('users').query(
Q.on('comments', 'post_id', this.id)
)
}
```
--------------------------------
### Define a New Migration with createTable
Source: https://watermelondb.dev/docs/Advanced/Migrations
Define a new migration by specifying the 'toVersion' and 'steps'. Use 'createTable' to define the structure of a new table, including its name and columns.
```javascript
// app/model/migrations.js
import { schemaMigrations, createTable } from '@nozbe/watermelondb/Schema/migrations'
export default schemaMigrations({
migrations: [
{
// ⚠️ Set this to a number one larger than the current schema version
toVersion: 2,
steps: [
// See "Migrations API" for more details
createTable({
name: 'comments',
columns: [
{ name: 'post_id', type: 'string', isIndexed: true },
{ name: 'body', type: 'string' },
],
}),
],
},
],
})
```
--------------------------------
### Implement Replacement Sync
Source: https://watermelondb.dev/docs/Sync/Frontend
Configure pullChanges to use the replacement strategy by adding the experimentalStrategy field to the returned object.
```javascript
{
changes: { ... },
timestamp: ...,
experimentalStrategy: 'replacement',
}
```
--------------------------------
### Define schema migrations
Source: https://watermelondb.dev/docs/Setup
Create a migrations file to manage database versioning.
```javascript
import { schemaMigrations } from '@nozbe/watermelondb/Schema/migrations'
export default schemaMigrations({
migrations: [
// We'll add migration definitions here later
],
})
```
--------------------------------
### Modify Table Schema SQL with unsafeSql
Source: https://watermelondb.dev/docs/Schema
Use unsafeSql within tableSchema to modify the SQL generated for a specific table. This example appends 'without rowid' to the CREATE TABLE statement.
```javascript
tableSchema({
name: 'tasks',
columns: [...],
unsafeSql: sql => sql.replace(/create table [^)]+\)/, '$& without rowid'),
})
```
--------------------------------
### Enable Experimental JSI SQLite Adapter
Source: https://watermelondb.dev/docs/CHANGELOG
Add the experimentalUseJSI flag to the SQLiteAdapter constructor to enable the JSI-based native SQLite integration.
```javascript
experimentalUseJSI: true
```
--------------------------------
### Initialize Multiple JSI Packages
Source: https://watermelondb.dev/docs/Installation
When using multiple JSI packages (e.g., with `reanimated`), provide a custom `JSIModulePackage` implementation in `MainApplication.java`. This allows you to combine JSI modules from different libraries.
```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 getJSIModules(
final ReactApplicationContext reactApplicationContext,
final JavaScriptContextHolder jsContext
) {
List modules = Arrays.asList();
modules.addAll(new WatermelonDBJSIPackage().getJSIModules(reactApplicationContext, jsContext)); // ⬅️ This!
// ⬅️ add more JSI packages here by conventions above, for example:
// modules.addAll(new ReanimatedJSIModulePackage().getJSIModules(reactApplicationContext, jsContext));
return modules;
}
};
}
}
```
--------------------------------
### Override record ID during creation
Source: https://watermelondb.dev/docs/CRUD
To set a custom ID for a record during creation, for example, to sync with a remote server, use the `_raw.id` property within the create builder function. The ID must be a string.
```javascript
await database.get('posts').create(post => {
post._raw.id = serverId
})
```
--------------------------------
### Synchronize with Logging
Source: https://watermelondb.dev/docs/CHANGELOG
Enables basic synchronization logging by passing an empty object to the `synchronize` function. This object will be populated with diagnostic information like `startedAt`.
```javascript
const log = {}
await synchronize({ database, log, ...})
console.log(log.startedAt)
```
--------------------------------
### Mark Comment as Spam using @writer Decorator
Source: https://watermelondb.dev/docs/Writers
Example of an updater action on a `Comment` model using the `@writer` decorator to mark a comment as spam. Remember to mark actions as `async` and `await` on `.create()` and `.update()`.
```javascript
class Comment extends Model {
// ...
@field('is_spam') isSpam
@writer async markAsSpam() {
await this.update(comment => {
comment.isSpam = true
})
}
}
```
--------------------------------
### Run ESLint
Source: https://watermelondb.dev/docs/CONTRIBUTING
Lints the codebase using ESLint to check for code style and potential errors.
```bash
yarn eslint
```
--------------------------------
### Define application schema
Source: https://watermelondb.dev/docs/Setup
Create a schema file to define the database structure using appSchema.
```javascript
import { appSchema, tableSchema } from '@nozbe/watermelondb'
export default appSchema({
version: 1,
tables: [
// We'll add tableSchemas here later
]
})
```
--------------------------------
### Prepare Record Creation from Dirty Raw Data
Source: https://watermelondb.dev/docs/CHANGELOG
Use `Collection.prepareCreateFromDirtyRaw()` to prepare a new record instance from raw data, potentially including unsaved changes.
```javascript
Collection.prepareCreateFromDirtyRaw()
```
--------------------------------
### Execute unsafe raw SQL commands
Source: https://watermelondb.dev/docs/CRUD
For advanced use cases, `database.adapter.unsafeExecute()` allows direct execution of raw SQL (for SQLite) or LokiJS commands. Use this as a last resort.
```javascript
await database.write(() => {
// sqlite:
await database.adapter.unsafeExecute({
sqls: [
// [sql_query, [placeholder arguments, ...]]
['create table temporary_test (id, foo, bar)', []],
['insert into temporary_test (id, foo, bar) values (?, ?, ?)', ['t1', true, 3.14]],
]
})
// lokijs:
await database.adapter.unsafeExecute({
loki: loki => {
loki.addCollection('temporary_test', { unique: ['id'], indices: [], disableMeta: true })
loki.getCollection('temporary_test').insert({ id: 't1', foo: true, bar: 3.14 })
}
})
})
```
--------------------------------
### Run Flow
Source: https://watermelondb.dev/docs/CONTRIBUTING
Performs static type checking on the codebase using Flow.
```bash
yarn flow
```
--------------------------------
### Configure Babel for decorators
Source: https://watermelondb.dev/docs/Installation
Update your .babelrc file to enable legacy decorator support.
```json
{
"presets": ["module:metro-react-native-babel-preset"],
"plugins": [["@babel/plugin-proposal-decorators", { "legacy": true }]]
}
```
--------------------------------
### Enable Actions in Database Constructor
Source: https://watermelondb.dev/docs/CHANGELOG
When initializing the Database, it is now mandatory to pass the `actionsEnabled` option. It is recommended to set this to `true` for full functionality.
```javascript
const database = new Database({
adapter: ...,
modelClasses: [...],
actionsEnabled: true
})
```
--------------------------------
### Conditions with Other Operators
Source: https://watermelondb.dev/docs/Query
Demonstrates various comparison operators and their JavaScript equivalents for building query conditions.
```APIDOC
## Conditions with Other Operators
### Description
This section illustrates how to use various operators within `Q.where()` to build complex query conditions, showing their direct JavaScript equivalents for clarity.
### Method
`Q.where(column, operator(value))`
### Endpoint
N/A (Client-side query builder)
### Parameters
#### Query Parameters
- **column** (string) - Required - The column name to apply the condition on.
- **operator(value)** (function) - Required - An operator function (e.g., `Q.eq`, `Q.gt`, `Q.like`) with its value.
### Request Example
```javascript
// Example using Q.eq
Q.where('is_verified', Q.eq(true))
// Example using Q.notEq
Q.where('archived_at', Q.notEq(null))
// Example using Q.gt
Q.where('likes', Q.gt(0))
// Example using Q.gte
Q.where('likes', Q.gte(100))
// Example using Q.lt
Q.where('dislikes', Q.lt(100))
// Example using Q.lte
Q.where('dislikes', Q.lte(100))
// Example using Q.between
Q.where('likes', Q.between(10, 100))
// Example using Q.oneOf
Q.where('status', Q.oneOf(['published', 'draft']))
// Example using Q.notIn
Q.where('status', Q.notIn(['archived', 'deleted']))
// Example using Q.like
Q.where('status', Q.like('%bl_sh%'))
// Example using Q.notLike
Q.where('status', Q.notLike('%bl_sh%'))
// Example using Q.includes
Q.where('status', Q.includes('promoted'))
```
### Response
N/A (This is a query builder, not an API endpoint)
#### Success Response (200)
N/A
#### Response Example
N/A
```
--------------------------------
### WatermelonDB JOIN Queries with Q.on
Source: https://watermelondb.dev/docs/Query
Query records from one table based on conditions in a related table using Q.on. This simulates JOIN operations in SQL.
```javascript
// Shortcut syntax:
database.get('comments').query(
Q.on('posts', 'author_id', john.id),
)
// Full syntax:
database.get('comments').query(
Q.on('posts', Q.where('author_id', Q.eq(john.id))),
)
```
--------------------------------
### Configure Flow for WatermelonDB
Source: https://watermelondb.dev/docs/Advanced/Flow
Add these declarations to your `.flowconfig` to enable Flow to recognize WatermelonDB's types. Ensure your `module.name_mapper` is correctly configured.
```ini
[declarations]
/node_modules/@nozbe/watermelondb/.*
[options]
module.name_mapper='^@nozbe/watermelondb(.*\]' -> '/node_modules/@nozbe/watermelondb/src\1'
```
--------------------------------
### Configure Packaging Options for JSI
Source: https://watermelondb.dev/docs/Installation
Add the `packagingOptions` block to your `android/app/build.gradle` to resolve potential conflicts with shared libraries when using JSI.
```gradle
// ...
android {
// ...
packagingOptions {
pickFirst '**/libc++_shared.so' // ⬅️ This (if missing)
}
}
```
--------------------------------
### Create a reactive list component
Source: https://watermelondb.dev/docs/Components
Demonstrates observing a parent record and a child query to keep lists updated.
```javascript
import { withObservables } from '@nozbe/watermelondb/react'
import EnhancedComment from 'components/Comment'
const Post = ({ post, comments }) => (
{post.name}
{post.body}
Comments
{comments.map(comment =>
)}
)
const enhance = withObservables(['post'], ({ post }) => ({
post,
comments: post.comments, // Shortcut syntax for `post.comments.observe()`
}))
const EnhancedPost = enhance(Post)
export default EnhancedPost
```
--------------------------------
### POST /push
Source: https://watermelondb.dev/docs/Sync/Backend
Applies local changes to the database and synchronizes with the server.
```APIDOC
## POST /push
### Description
Applies local changes (create, update, delete) to the database. Handles various scenarios like existing IDs, non-existent records, and server modifications to ensure data consistency.
### Method
POST
### Endpoint
/push
### Parameters
#### Request Body
- **changes** (object) - Required - An object containing local changes to be applied to the database. This includes new records, updates to existing records, and IDs of records to delete.
### Request Example
```json
{
"changes": {
"users": [
{
"id": "user1",
"name": "Alice",
"email": "alice@example.com"
}
],
"posts": [
{
"id": "post1",
"title": "New Post",
"content": "This is the content."
}
]
},
"_lastPulledAt": 1678886400000
}
```
### Response
#### Success Response (200)
Indicates that all local changes were successfully applied to the database.
#### Error Response (e.g., 409 Conflict, 400 Bad Request)
Returned when a conflict is detected (e.g., record modified on server after pull) or if data validation fails, requiring a re-pull operation.
#### Response Example (Success)
```json
{
"status": "success"
}
```
### Notes
- The endpoint MUST be fully transactional. If any error occurs, all changes MUST be reverted.
- `_status` and `_changed` fields in the `changes` object should be ignored.
- Data validation (collection/column names, ID format) and sanitization of record fields are recommended.
- Descendants of deleted records SHOULD also be deleted.
```
--------------------------------
### Query with multiple conditions using Q.on
Source: https://watermelondb.dev/docs/CHANGELOG
Use this syntax to apply multiple conditions to a related collection within a query.
```javascript
collection.query(Q.on('projects', [Q.where('foo', 'bar'), Q.where('bar', 'baz')]))
```
--------------------------------
### Conditions on Related Tables (JOIN Queries)
Source: https://watermelondb.dev/docs/Query
Explains how to query data across related tables using `Q.on`.
```APIDOC
## Conditions on Related Tables (JOIN Queries)
### Description
The `Q.on` operator allows you to apply conditions on a related table, effectively performing a JOIN operation in your queries.
### Method
`Q.on(relatedTable, condition)`
`Q.on(relatedTable, relatedColumn, value)`
### Endpoint
N/A (Client-side query builder)
### Parameters
#### Query Parameters
- **relatedTable** (string) - Required - The name of the related table to join on.
- **condition** (object) - Required - A query condition to apply to the related table.
- **relatedColumn** (string) - Required - The column in the related table to compare.
- **value** - Required - The value to compare against in the related column.
### Request Example
```javascript
// Query all comments under posts published by John (shortcut syntax)
database.get('comments').query(
Q.on('posts', 'author_id', john.id)
)
// Query all comments under posts published by John (full syntax)
database.get('comments').query(
Q.on('posts', Q.where('author_id', Q.eq(john.id)))
)
// Query all comments under posts that are written by John AND are either published or belong to draftBlog
database.get('comments').query(
Q.on('posts', [
Q.where('author_id', john.id),
Q.or(
Q.where('published', true),
Q.where('blog_id', draftBlog.id)
)
])
)
// Nesting Q.on within AND/OR (requires explicit table joins)
tasksCollection.query(
Q.experimentalJoinTables(['projects']),
Q.or(
Q.where('is_followed', true),
Q.on('projects', 'is_followed', true)
)
)
// Deep Q.on (joining multiple levels)
tasksCollection.query(
Q.experimentalNestedJoin('projects', 'teams'),
Q.on('projects', Q.on('teams', 'foo', 'bar'))
)
```
### Response
N/A (This is a query builder, not an API endpoint)
#### Success Response (200)
N/A
#### Response Example
N/A
### Notes
- Tables must be associated before using `Q.on`.
- `Q.experimentalJoinTables` and `Q.experimentalNestedJoin` are experimental APIs and subject to change.
```
--------------------------------
### Format Code with Prettier
Source: https://watermelondb.dev/docs/CONTRIBUTING
Apply code formatting rules using Prettier to maintain a consistent code style across the project. Run this before committing changes.
```bash
yarn prettier
```
--------------------------------
### Configure iOS Podfile for WatermelonDB
Source: https://watermelondb.dev/docs/CHANGELOG
Update the Podfile to include the required simdjson dependency for WatermelonDB.
```ruby
# Uncomment this line if you're not using auto-linking
# pod 'WatermelonDB', path: '../node_modules/@nozbe/watermelondb'
# WatermelonDB dependency
pod 'simdjson', path: '../node_modules/@nozbe/simdjson', modular_headers: true
```
--------------------------------
### Migration Sync Logic Table
Source: https://watermelondb.dev/docs/Implementation/SyncImpl
A reference table detailing how last pulled at (LPA), migration enabled version (MEA), last synced schema (LS), and current version (CV) interact to determine migration behavior.
```text
LPA = last pulled at
MEA = migrationsEnabledAtVersion, schema version at which future migration support was introduced
LS = last synced schema version (may be null due to backwards compat)
CV = current schema version
LPA MEA LS CV migration set LS=CV? comment
null X X 10 null YES first sync. regardless of whether the app
is migration sync aware, we can note LS=CV
to fetch all migrations once available
100 null X X null NO indicates app is not migration sync aware so
we're not setting LS to allow future migration sync
100 X 10 10 null NO up to date, no migration
100 9 9 10 {9-10} YES correct migration sync
100 9 null 10 {9-10} YES fallback migration. might not contain all
necessary migrations, since we can't know for sure
that user logged in at then-current-version==MEA
100 9 11 10 ERROR NO LS > CV indicates programmer error
100 11 X 10 ERROR NO MEA > CV indicates programmer error
```
--------------------------------
### Use Q.like for SQL LIKE queries
Source: https://watermelondb.dev/docs/CHANGELOG
The `Q.like` operator allows you to perform queries similar to SQL's `LIKE` operator.
```javascript
Q.like('name', '%John%')
```
--------------------------------
### Define Table and Column Names with Flow
Source: https://watermelondb.dev/docs/Advanced/Flow
Pre-define all table and column names in a central schema file using WatermelonDB's `tableName` and `columnName` functions. This ensures type safety and prevents typos.
```javascript
// File: model/schema.js
// @flow
import { tableName, columnName, type TableName, appSchema, tableSchema } from '@nozbe/watermelondb'
import type Comment from './Comment.js'
export const Tables = {
comments: (tableName('comments'): TableName),
// ...
}
export const Columns = {
comments: {
body: columnName('body'),
// ...
}
}
export const appSchema = appSchema({
version: 1,
tables: [
tableSchema({
name: Tables.comments,
columns: [
{ name: Columns.comments.body, type: 'string' },
],
}),
// ...
]
})
```
```javascript
// File: model/Comment.js
// @flow
import { Model } from '@nozbe/watermelondb'
import { text } from '@nozbe/watermelondb/decorators'
import { Tables, Columns } from './schema.js'
const Column = Columns.comments
export default class Comment extends Model {
static table = Tables.comments
@text(Column.body) body: string
}
```
--------------------------------
### Subscribe to Database Changes for Specific Tables
Source: https://watermelondb.dev/docs/CHANGELOG
Use the experimental `database.experimentalSubscribe` method as a vanilla JavaScript alternative to Rx-based `database.withChangesForTables()`. This method notifies the subscriber only once after a batch of changes affecting the subscribed collections. It does not notify immediately upon subscription and does not provide details about the changes, only a signal.
```javascript
database.experimentalSubscribe(['table1', 'table2'], () => { ... })
```
--------------------------------
### Advanced Observing
Source: https://watermelondb.dev/docs/Query
Details how to observe changes in query results, including specific columns.
```APIDOC
## Advanced Observing
### Description
`query.observeWithColumns` allows you to create an observable that emits not only when the set of matching records changes, but also when specific columns of any matched record are updated.
### Method
`query.observeWithColumns([column1, column2, ...])`
### Endpoint
N/A (Client-side query builder)
### Parameters
#### Query Parameters
- **column1, column2, ...** (string) - Required - The names of the columns to observe for changes.
### Request Example
```javascript
// Observe changes to the list of records AND changes in 'foo' or 'bar' columns of matched records
const observable = query.observeWithColumns(['foo', 'bar'])
observable.subscribe(records => {
// Handle updated records
});
```
### Response
N/A (This is a query builder, not an API endpoint)
#### Success Response (200)
N/A
#### Response Example
N/A
### Notes
- This is particularly useful for observing sorted lists where changes in specific fields might affect the order.
```
--------------------------------
### Run Android Integration Tests
Source: https://watermelondb.dev/docs/CONTRIBUTING
Executes integration tests for the Android platform, involving the full WatermelonDB stack with SQLite and React Native.
```bash
yarn test:android
```
--------------------------------
### Run Automated Tests
Source: https://watermelondb.dev/docs/Implementation/Publishing
Execute all automated tests and linters before publishing. Ensure all checks pass.
```bash
yarn ci:check && yarn test:ios && yarn test:android && yarn swiftlint && yarn ktlint
```
--------------------------------
### Add Create/Update Tracking Columns to Schema
Source: https://watermelondb.dev/docs/Advanced/CreateUpdateTracking
Define `created_at` and `updated_at` columns as numbers in your table schema to enable tracking.
```javascript
tableSchema({
name: 'posts',
columns: [
// other columns
{ name: 'created_at', type: 'number' },
{ name: 'updated_at', type: 'number' },
]
}),
```
--------------------------------
### WatermelonDB Query Conditions with Operators
Source: https://watermelondb.dev/docs/Query
Demonstrates various comparison operators for filtering records. Use these for exact matches, inequalities, ranges, and set inclusions.
```javascript
Q.where('is_verified', true)
```
```javascript
Q.where('is_verified', Q.eq(true))
```
```javascript
Q.where('archived_at', Q.notEq(null))
```
```javascript
Q.where('likes', Q.gt(0))
```
```javascript
Q.where('likes', Q.weakGt(0))
```
```javascript
Q.where('likes', Q.gte(100))
```
```javascript
Q.where('dislikes', Q.lt(100))
```
```javascript
Q.where('dislikes', Q.lte(100))
```
```javascript
Q.where('likes', Q.between(10, 100))
```
```javascript
Q.where('status', Q.oneOf(['published', 'draft']))
```
```javascript
Q.where('status', Q.notIn(['archived', 'deleted']))
```
```javascript
Q.where('status', Q.like('%bl_sh%'))
```
```javascript
Q.where('status', Q.notLike('%bl_sh%'))
```
```javascript
Q.where('status', Q.includes('promoted'))
```
--------------------------------
### WatermelonDB LIKE Query for User Input Search
Source: https://watermelondb.dev/docs/Query
Use Q.like with Q.sanitizeLikeString to safely search user-generated content. This prevents issues with special characters like '%' and '_'.
```javascript
usersCollection.query(
Q.where("username", Q.like(`${Q.sanitizeLikeString("jas")}%`)
)
```
```javascript
Q.like(`%${Q.sanitizeLikeString(userInput)}%`)
```
```javascript
Q.notLike(`%${Q.sanitizeLikeString(userInput)}%`)
```
--------------------------------
### Run Jest Tests
Source: https://watermelondb.dev/docs/CONTRIBUTING
Executes the unit tests using the Jest testing framework.
```bash
yarn test
```
--------------------------------
### Subscribe to Query Results
Source: https://watermelondb.dev/docs/CHANGELOG
Use the experimental `query.experimentalSubscribe` methods to subscribe to query results. Options include subscribing to the full records, specific columns, or just the count.
```javascript
query.experimentalSubscribe(records => { ... })
query.experimentalSubscribeWithColumns(['col1', 'col2'], records => { ... })
query.experimentalSubscribeToCount(count => { ... })
```
--------------------------------
### Adding Sync Logging with SyncLogger
Source: https://watermelondb.dev/docs/Sync/Frontend
Instantiate SyncLogger with a memory limit and pass its new log to synchronize. The logger stores diagnostic information, which can be accessed via logger.logs or logger.formattedLogs.
```javascript
// Using built-in SyncLogger
import SyncLogger from '@nozbe/watermelondb/sync/SyncLogger'
const logger = new SyncLogger(10 /* limit of sync logs to keep in memory */ )
await synchronize({ database, log: logger.newLog(), ... })
// this returns all logs (censored and safe to use in production code)
console.log(logger.logs)
// same, but pretty-formatted to a string (a user can easy copy this for diagnostic purposes)
console.log(logger.formattedLogs)
```