Melon-dbMelon-db

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.ts

Add --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.batch operation mapping
  • Q.experimentalJoinTables / nested join tables (use relationFilters or includes)

See @melon-db/db-codemods for the full compatibility matrix.

Concept mapping

WatermelonDBMelon-db
database.get('tasks')db.collection('tasks')
database.write()db.write()
collection.query()collection.query(ast) or fluent builder
withDatabase / providerMelonDbProvider
Sync pull/push@melon-db/sync synchronize()