Skip to main content
Version: 0.27.1

Watermelon ❤️ Flow

Watermelon was developed with Flow in mind.

If you're a Flow user yourself (and we highly recommend it!), here's some things you need to keep in mind:

Setup

Add this to your .flowconfig file so that Flow can see Watermelon's types.

[declarations]
<PROJECT_ROOT>/node_modules/@nozbe/watermelondb/.*

[options]

module.name_mapper='^@nozbe/watermelondb\(.*\)$' -> '<PROJECT_ROOT>/node_modules/@nozbe/watermelondb/src\1'

Note that this won't work if you put the entire node_modules/ folder under the [ignore] section. In that case, change it to only ignore the specific node modules that throw errors in your app, so that Flow can scan Watermelon files.

Tables and columns

Table and column names are opaque types in Flow.

So if you try to use simple strings, like so:

class Comment extends Model {
static table = 'comments'

@text('body') body
}

You'll get errors, because you're passing 'comments' (a string) where TableName<Comment> is expected, and 'body' (again, a string) where ColumnName is expected.

When using Watermelon with Flow, you must pre-define all your table and column names in one place, then only use those symbols (and not strings) in all other places.

We recommend defining symbols like this:

// 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<Comment>),
// ...
}

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' },
],
}),
// ...
]
})

And then using them like so:

// 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
}

But isn't that a lot of boilerplate?

Yes, it looks more boilerplate'y than the non-Flow examples, however:

  • you're protected from typos — strings are defined once
  • easier refactoring — you only change column name in one place
  • no orphan columns or tables — no way to accidentally refer to a column or table that was removed from the schema
  • TableName is typed with the model class it refers to, which allows Flow to find other mistakes in your code

In general, we find that untyped string constants lead to bugs, and defining typed constants is a good practice.

associations

When using Flow, you define model associations like this:

import { Model, associations } from '@nozbe/watermelondb'
import { Tables, Columns } from './schema.js'

const Column = Columns.posts

class Post extends Model {
static table = Tables.posts
static associations = associations(
[Tables.comments, { type: 'has_many', foreignKey: Columns.comments.postId }],
[Tables.users, { type: 'belongs_to', key: Column.authorId }],
)
}

Common types

Many types are tagged with the model class the type refers to:

TableName<Post> // a table name referring to posts
Collection<Post> // the Collection for posts
Relation<Comment> // a relation that can fetch a Comment
Relation<?Comment> // a relation that can fetch a Comment or `null`
Query<Comment> // a query that can fetch many Comments

Always mark the type of model fields. Remember to include ? if the underlying table column is optional. Flow can't check if model fields match the schema or if they match the decorator's signature.

@text(Column.body) body: string
@date(Column.createdAt) createdAt: Date
@date(Column.archivedAt) archivedAt: ?Date

If you need to refer to an ID of a record, always use the RecordId type alias, not string (they're the same, but the former is self-documenting).

If you ever access the record's raw data (DON'T do that unless you really know what you're doing), use DirtyRaw to refer to raw data from external sources (database, server), and RawRecord after it was passed through sanitizedRaw.