Migrating from WatermelonDB
Codemods and concept mapping from WatermelonDB to Melon-db.
Use @melon-db/db-codemods for automated migration helpers.
CLI codemods
bun run melon-codemod migrate-queries --path=./src
bun run melon-codemod migrate-writes --path=./src
bun run melon-codemod migrate-react --path=./src
bun run melon-codemod migrate-schema --path=./src/models/Task.tsAdd --dry-run to preview changes. migrate-schema prints extracted DatabaseSchemaDefinition JSON (read-only).
Runtime query translator
The compatibility layer converts serializable Watermelon Q clauses to Melon-db QueryAst without a WatermelonDB dependency. Nested Q.and / Q.or are supported in the translator and in migrate-queries.
Q.on (join) queries
Melon-db supports Q.on at runtime when the parent schema defines a matching belongsTo relation. The compat translator maps Q.on to QueryAst.relationFilters (SQLite compiles an IN subquery). The codemod still emits a recipe comment when it cannot auto-translate; pass schema to translateWatermelonQuery for full translation.
Runtime (preferred when schema has tasks.project → projects):
import { translateWatermelonQuery } from '@melon-db/db-codemods';
import { taskSchema } from './schema';
const ast = translateWatermelonQuery(
'tasks',
[
{ type: 'on', table: 'projects', condition: { type: 'where', field: 'name', value: 'Acme' } },
{ type: 'where', field: 'status', value: 'open' },
],
taskSchema,
);
const tasks = await db.collection('tasks').findMany(ast);Manual AST (relationFilters):
const tasks = await db.collection('tasks').findMany(
queryAst('tasks', {
where: predicate('status', 'eq', 'open'),
relationFilters: [
{ relation: 'project', where: predicate('name', 'eq', 'Acme') },
],
}),
);Includes vs filters: use .include('project', { where: … }) when you need nested related rows on each parent. Use relationFilters / Q.on when you only need to filter parents by related fields.
Two-step fallback (no relation metadata):
const projects = await db.collection('projects').findMany(
q.from('projects').where('name', 'eq', 'Acme').toAst(),
);
const projectIds = projects.map((p) => p.id);
const tasks = await db.collection('tasks').findMany(
q.from('tasks')
.where('status', 'eq', 'open')
.where('projectId', 'in', projectIds)
.toAst(),
);withObservables
The migrate-react codemod detects common withObservables HOC patterns and emits hook migration comments. Replace the HOC with hooks inside your component:
// Before
const Enhanced = withObservables(['tasks'], ({ database }) => ({
tasks: database.get('tasks').query(Q.where('status', 'open')).observe(),
}))(TaskList);
// After
import { useFindMany } from '@melon-db/db-react';
function TaskList() {
const tasks = useFindMany('tasks', /* compile query args */);
// ...
}Complex HOC shapes still require manual migration.
Schema extraction (spike)
migrate-schema extracts a single Watermelon Model class file into Melon-db DatabaseSchemaDefinition JSON. Multi-file schema assembly and migration version mapping are not automated yet.
Manual migration
These patterns still require manual work:
- Full multi-file schema / migration codegen
- Complex
database.batchoperation mapping Q.experimentalJoinTables/ nested join tables (userelationFiltersor includes)
See @melon-db/db-codemods for the full compatibility matrix.
Concept mapping
| WatermelonDB | Melon-db |
|---|---|
database.get('tasks') | db.collection('tasks') |
database.write() | db.write() |
collection.query() | collection.query(ast) or fluent builder |
withDatabase / provider | MelonDbProvider |
| Sync pull/push | @melon-db/sync synchronize() |
