Skip to content

chore: add Drizzle schema and generated migrations for default and per-server databases#7395

Open
diegolmello wants to merge 2 commits into
feat/native-1272-sqlcipher-migrationfrom
feat/native-1275-drizzle-schema
Open

chore: add Drizzle schema and generated migrations for default and per-server databases#7395
diegolmello wants to merge 2 commits into
feat/native-1272-sqlcipher-migrationfrom
feat/native-1275-drizzle-schema

Conversation

@diegolmello

@diegolmello diegolmello commented Jun 13, 2026

Copy link
Copy Markdown
Member

Proposed changes

First code step of the WatermelonDB → expo-sqlite + Drizzle migration: adds the Drizzle ORM schema definitions and generated SQL migrations for both databases, plus the drizzle-kit tooling configs. No runtime behavior changes — nothing imports these modules yet.

  • Schemas (app/lib/database/driver/schema/):
    • app.ts — the 13 per-server tables (messages, subscriptions, rooms, threads, thread_messages, custom_emojis, frequently_used_emojis, uploads, settings, roles, permissions, slash_commands, users) with exact WatermelonDB table/column name parity.
    • servers.ts — the 3 default-database tables (servers, users, servers_history).
    • index.ts — typed exports and table registries for both database kinds.
  • Generated migrations (app/lib/database/driver/migrations/): drizzle-kit output (SQL + snapshots + journal + migrations.js for the expo driver), one folder per database.
  • Configs: drizzle.app.config.ts / drizzle.servers.config.ts (dialect sqlite, driver expo).
  • Dependencies: drizzle-orm 0.45.2 (runtime), drizzle-kit 0.31.10 (dev).

Schema decisions (from the migration PRD):

  • Sync bookkeeping columns (_status, _changed) are dropped — the new layer does not use WatermelonDB's sync protocol.
  • All columns are nullable except the id text primary keys, matching WatermelonDB's actual SQLite DDL (it emits no NOT NULL constraints on data columns), so the wipe-and-restore migration can re-insert any legacy row without constraint failures.
  • JSON-shaped columns stay as text for now; typed accessors come with the data-access facade work.

Issue(s)

https://rocketchat.atlassian.net/browse/NATIVE-1275

How to test or reproduce

  • TZ=UTC pnpm test app/lib/database/driver — schema tests assert table/column parity against the WatermelonDB schema and pin the generated SQL shapes of representative queries.
  • pnpm exec tsc --noEmit -p . — type-checks the schema exports.
  • Regenerating migrations with pnpm exec drizzle-kit generate --config drizzle.app.config.ts (and the servers config) produces no diff.

Screenshots

N/A — no UI changes.

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • Improvement (non-breaking change which improves a current function)
  • New feature (non-breaking change which adds functionality)
  • Documentation update (if none of the other choices apply)

Checklist

  • I have read the CONTRIBUTING doc
  • I have signed the CLA
  • Lint and unit tests pass locally with my changes
  • I have added tests that prove my fix is effective or that my feature works (if applicable)
  • I have added necessary documentation (if applicable)
  • Any dependent changes have been merged and published in downstream modules

Further comments

Part of the WatermelonDB replacement track. The driver adapter (connection lifecycle, SQLCipher keying, live-query hooks) follows in a separate PR built on top of this one.

Summary by CodeRabbit

  • New Features

    • Established database infrastructure for core chat functionality including message storage, conversation threads, user management, permissions, and settings
  • Chores

    • Updated linting and formatting configuration
    • Added database management tools to development dependencies

@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1698ffbd-21c2-4700-8257-6253fddaebb3

📥 Commits

Reviewing files that changed from the base of the PR and between 67477fb and 2c2103b.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (17)
  • .eslintignore
  • .prettierignore
  • app/lib/database/driver/migrations/app/0000_equal_trish_tilby.sql
  • app/lib/database/driver/migrations/app/meta/0000_snapshot.json
  • app/lib/database/driver/migrations/app/meta/_journal.json
  • app/lib/database/driver/migrations/app/migrations.js
  • app/lib/database/driver/migrations/servers/0000_flawless_spencer_smythe.sql
  • app/lib/database/driver/migrations/servers/meta/0000_snapshot.json
  • app/lib/database/driver/migrations/servers/meta/_journal.json
  • app/lib/database/driver/migrations/servers/migrations.js
  • app/lib/database/driver/schema/__tests__/queryShapes.test.ts
  • app/lib/database/driver/schema/app.ts
  • app/lib/database/driver/schema/index.ts
  • app/lib/database/driver/schema/servers.ts
  • drizzle.app.config.ts
  • drizzle.servers.config.ts
  • package.json
📜 Recent review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: ESLint and Test / run-eslint-and-test
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{js,jsx,ts,tsx,json}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Prettier formatting with tabs, single quotes, 130 character line width, no trailing commas, and avoid arrow function parentheses

Files:

  • app/lib/database/driver/migrations/app/meta/_journal.json
  • drizzle.app.config.ts
  • app/lib/database/driver/migrations/servers/meta/_journal.json
  • app/lib/database/driver/migrations/app/migrations.js
  • app/lib/database/driver/migrations/servers/migrations.js
  • drizzle.servers.config.ts
  • package.json
  • app/lib/database/driver/schema/servers.ts
  • app/lib/database/driver/schema/index.ts
  • app/lib/database/driver/schema/__tests__/queryShapes.test.ts
  • app/lib/database/driver/migrations/servers/meta/0000_snapshot.json
  • app/lib/database/driver/schema/app.ts
  • app/lib/database/driver/migrations/app/meta/0000_snapshot.json
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,jsx,tsx}: Use descriptive names for functions, variables, and classes that clearly convey their purpose
Write comments that explain the 'why' behind code decisions, not the 'what'
Keep functions small and focused on a single responsibility
Use const by default, let when reassignment is needed, and avoid var
Prefer async/await over .then() chains for handling asynchronous operations
Use explicit error handling with try/catch blocks for async operations
Avoid deeply nested code; refactor complex logic into helper functions

Files:

  • drizzle.app.config.ts
  • app/lib/database/driver/migrations/app/migrations.js
  • app/lib/database/driver/migrations/servers/migrations.js
  • drizzle.servers.config.ts
  • app/lib/database/driver/schema/servers.ts
  • app/lib/database/driver/schema/index.ts
  • app/lib/database/driver/schema/__tests__/queryShapes.test.ts
  • app/lib/database/driver/schema/app.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use TypeScript for type safety; add explicit type annotations to function parameters and return types
Prefer interfaces over type aliases for defining object shapes in TypeScript
Use enums for sets of related constants rather than magic strings or numbers

Use TypeScript with strict mode enabled

Files:

  • drizzle.app.config.ts
  • drizzle.servers.config.ts
  • app/lib/database/driver/schema/servers.ts
  • app/lib/database/driver/schema/index.ts
  • app/lib/database/driver/schema/__tests__/queryShapes.test.ts
  • app/lib/database/driver/schema/app.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Enforce ESLint rules from @rocket.chat/eslint-config with React, React Native, TypeScript, and Jest plugins

Files:

  • drizzle.app.config.ts
  • app/lib/database/driver/migrations/app/migrations.js
  • app/lib/database/driver/migrations/servers/migrations.js
  • drizzle.servers.config.ts
  • app/lib/database/driver/schema/servers.ts
  • app/lib/database/driver/schema/index.ts
  • app/lib/database/driver/schema/__tests__/queryShapes.test.ts
  • app/lib/database/driver/schema/app.ts
🧠 Learnings (3)
📚 Learning: 2026-04-30T17:07:51.020Z
Learnt from: diegolmello
Repo: RocketChat/Rocket.Chat.ReactNative PR: 7274
File: app/lib/services/voip/MediaCallEvents.ts:0-0
Timestamp: 2026-04-30T17:07:51.020Z
Learning: In this Rocket.Chat React Native codebase, the ESLint rule `no-void: error` is enforced. When you see a promise returned from an async call that is not awaited (a “floating promise”), do not silence it with the `void somePromise()` pattern. Instead, handle the promise explicitly by attaching `.catch(...)` (or otherwise awaiting/handling the error) so unhandled-rejection risks are addressed in a way that satisfies the existing ESLint configuration.

Applied to files:

  • drizzle.app.config.ts
  • drizzle.servers.config.ts
  • app/lib/database/driver/schema/servers.ts
  • app/lib/database/driver/schema/index.ts
  • app/lib/database/driver/schema/__tests__/queryShapes.test.ts
  • app/lib/database/driver/schema/app.ts
📚 Learning: 2026-02-05T13:55:00.974Z
Learnt from: Rohit3523
Repo: RocketChat/Rocket.Chat.ReactNative PR: 6930
File: package.json:101-101
Timestamp: 2026-02-05T13:55:00.974Z
Learning: In this repository, the dependency on react-native-image-crop-picker should reference the RocketChat fork (RocketChat/react-native-image-crop-picker) with explicit commit pins, not the upstream ivpusic/react-native-image-crop-picker. Update package.json dependencies (and any lockfile) to point to the fork URL and a specific commit, ensuring edge-to-edge Android fixes are included. This pattern should apply to all package.json files in the repo that declare this dependency.

Applied to files:

  • package.json
📚 Learning: 2026-05-07T17:47:14.516Z
Learnt from: diegolmello
Repo: RocketChat/Rocket.Chat.ReactNative PR: 7303
File: package.json:5-5
Timestamp: 2026-05-07T17:47:14.516Z
Learning: When reviewing pnpm `packageManager` version pins in any `package.json` (e.g., `"packageManager": "pnpm@<version>"`), don’t rely solely on web-search results to determine whether a version exists. For very recently published versions, cross-check the target version against the official pnpm release page (https://github.com/pnpm/pnpm/releases) and the npm registry page for pnpm (https://www.npmjs.com/package/pnpm) before flagging the pinned version as non-existent.

Applied to files:

  • package.json
🔇 Additional comments (17)
app/lib/database/driver/schema/app.ts (1)

1-302: LGTM!

app/lib/database/driver/migrations/app/0000_equal_trish_tilby.sql (1)

1-283: LGTM!

app/lib/database/driver/migrations/app/meta/0000_snapshot.json (1)

1-1862: LGTM!

app/lib/database/driver/migrations/app/meta/_journal.json (1)

1-13: LGTM!

app/lib/database/driver/migrations/app/migrations.js (1)

1-12: LGTM!

app/lib/database/driver/schema/servers.ts (1)

1-54: LGTM!

app/lib/database/driver/migrations/servers/0000_flawless_spencer_smythe.sql (1)

1-49: LGTM!

app/lib/database/driver/migrations/servers/meta/0000_snapshot.json (1)

1-329: LGTM!

app/lib/database/driver/migrations/servers/meta/_journal.json (1)

1-13: LGTM!

app/lib/database/driver/migrations/servers/migrations.js (1)

1-12: LGTM!

app/lib/database/driver/schema/index.ts (1)

1-88: LGTM!

app/lib/database/driver/schema/__tests__/queryShapes.test.ts (1)

1-169: LGTM!

drizzle.servers.config.ts (1)

1-8: Same 'expo' driver verification needed as in drizzle.app.config.ts.

This configuration mirrors drizzle.app.config.ts and uses the same driver: 'expo' option. The verification requested for the app config applies here as well.

.eslintignore (1)

9-9: LGTM!

.prettierignore (1)

32-32: LGTM!

drizzle.app.config.ts (1)

1-8: Remove the concern about driver: 'expo'
driver: 'expo' is a supported drizzle-kit 0.31.10 driver value for the sqlite dialect, so the configuration is valid as written.

package.json (1)

60-60: Verify Drizzle dependency versions and security advisories.

drizzle-orm@0.45.2 and drizzle-kit@0.31.10 are published on npm, and OSV.dev reports no known security vulnerabilities for those exact versions.


Walkthrough

This PR introduces Drizzle ORM infrastructure for SQLite persistence in a React Native app, adding two independent databases (app and servers) with 16 total tables, SQL migrations, type definitions, and validation tests to enable structured ORM-based data access.

Changes

Drizzle ORM Database Foundation

Layer / File(s) Summary
Dependencies, Linter Configuration, and Drizzle Kit Setup
.eslintignore, .prettierignore, package.json, drizzle.app.config.ts, drizzle.servers.config.ts
Package dependencies for drizzle-orm and drizzle-kit are added. Drizzle Kit is configured for both app and servers databases with SQLite Expo driver, schema, and migrations output paths. Migration directories are excluded from ESLint and Prettier processing.
App Database Schema Definitions (13 Tables)
app/lib/database/driver/schema/app.ts
Defines 13 Drizzle ORM SQLite tables for app-scoped data: subscriptionsTable (with indexes on t, name, rid, team_id), roomsTable, messagesTable (indexed on rid), threadsTable (indexed on rid), threadMessagesTable (indexed on rid and subscription_id), customEmojisTable, frequentlyUsedEmojisTable, uploadsTable (indexed on rid), settingsTable, rolesTable, permissionsTable, slashCommandsTable, and usersAppTable (indexed on _id and username).
Servers Database Schema Definitions (3 Tables)
app/lib/database/driver/schema/servers.ts
Defines 3 Drizzle ORM SQLite tables for server-scoped data: usersServersTable (user-server mappings with role/status flags), serversTable (server configuration, version support, metadata), and serversHistoryTable (with index on url for history lookups).
App Database Migrations and Metadata
app/lib/database/driver/migrations/app/0000_equal_trish_tilby.sql, app/lib/database/driver/migrations/app/meta/0000_snapshot.json, app/lib/database/driver/migrations/app/meta/_journal.json, app/lib/database/driver/migrations/app/migrations.js
SQL DDL migration creates 13 app tables with schema-defined columns and indexes. Snapshot JSON documents full schema metadata and constraints. Journal JSON tracks migration version and timestamp. Migrations.js entry point wires journal and SQL migration for Drizzle/Expo runtime execution.
Servers Database Migrations and Metadata
app/lib/database/driver/migrations/servers/0000_flawless_spencer_smythe.sql, app/lib/database/driver/migrations/servers/meta/0000_snapshot.json, app/lib/database/driver/migrations/servers/meta/_journal.json, app/lib/database/driver/migrations/servers/migrations.js
SQL DDL migration creates 3 servers tables with url index on servers_history. Snapshot JSON documents schema structure. Journal JSON tracks migration version and timestamp. Migrations.js entry point wires journal and SQL migration for Drizzle/Expo runtime execution.
Central Schema Export and Type Aliases
app/lib/database/driver/schema/index.ts
Re-exports all 16 table objects from app and servers schemas. Defines TypeScript type aliases (TSubscriptionRow, TSubscriptionInsert, TRoomRow, TRoomInsert, etc.) using Drizzle's InferSelectModel and InferInsertModel to standardize row and insert payload types.
Query Shape Validation Tests
app/lib/database/driver/schema/__tests__/queryShapes.test.ts
Jest test suite validates Drizzle SQL query generation across multiple queries (subscriptions, messages, threads, emojis, users, permissions) with assertions on SQL structure, parameter bindings, and filter/search/sort patterns.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested labels

type: feature

Suggested reviewers

  • Rohit3523
  • OtavioStasiak
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding Drizzle schema definitions and generated migrations for both per-server and default databases, which is the core focus of this PR.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install timed out. The project may have too many dependencies for the sandbox.

Warning

Review ran into problems

🔥 Problems

Errors were encountered while retrieving linked issues.

Errors (1)
  • NATIVE-1275: Request failed with status code 401

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@diegolmello diegolmello left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Structural review (NATIVE-1275). Schema is column-complete vs the WatermelonDB source and the generated migrations are clean drizzle-kit output (no hand-edits). Two findings worth acting on before merge are inline below.

Minor notes, not blocking: the two near-identical drizzle.app.config.ts / drizzle.servers.config.ts exist only because drizzle-kit takes one schema target per config — a one-line comment saying so would stop a future "dedupe" that breaks generation. The mixed indentation in migrations/*/migrations.js is drizzle-kit's own Expo-template output (both files are prettier-ignored), so it's not actionable.

avatar_etag: text('avatar_etag')
});

export const messagesTable = sqliteTable(

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

slop: heavy — column block copy-pasted across three tables. messagesTable, threadsTable and threadMessagesTable repeat the same ~30 columns verbatim; they differ by only ~6 fields total. Drizzle supports column-object spreading: define a messageColumns object once and spread it into each table, adding only the table-specific fields. Collapses this file from ~300 to ~160 lines and makes a future column addition a one-place change instead of three.

msg: text('msg'),
t: text('t'),
rid: text('rid'),
ts: real('ts'),

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bug: med — no .notNull() anywhere in the schema. Every column here (and in servers.ts) is nullable, but the WatermelonDB source marks many as required — e.g. on messages alone ts, u, _updated_at, alias, parse_urls; also uploads.size/progress, rooms.encrypted/ro, subscriptions.ts/unread, frequently_used_emojis.count. Writes that would have thrown in WMDB will silently store NULL. Add .notNull() to every field that isn't isOptional in the WMDB source and re-run drizzle-kit generate. If relaxing the constraints is intentional for the clean break, say so in a comment.


// ---- app (per-server) database row types ----

export type TSubscriptionRow = InferSelectModel<typeof subscriptionsTable>;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

slop: nit — speculative TXxxInsert aliases. 13 InferInsertModel aliases are exported but none are consumed in this PR. Defer the Insert variants until a call site actually needs them, or keep only if the next stacked PR consumes them immediately.

@diegolmello diegolmello left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: Drizzle schema + generated migrations

Schema-only foundation; nothing imports it yet. Quality is high — I diffed all 16 tables (13 per-server + 3 servers-db) column-by-column against the WatermelonDB schemas in app/lib/database/schema/ and found full parity: no missing tables, no missing columns, correct SQLite affinities (incl. real() for the WMDB number/boolean cases), and every WMDB index reproduced. The deliberate choices (universal nullability, keeping verbatim SQLite column names) are clearly motivated.

Findings are all minor / process-level, no inline anchors needed:

🟡 [medium] No db:generate npm script for either config — the test instructions tell reviewers to run pnpm exec drizzle-kit generate --config drizzle.app.config.ts (and the servers variant) by hand. A "db:generate:app" / "db:generate:servers" pair makes the regenerate→diff loop repeatable for the next PR author.

🟡 [medium] The servers schema (serversTable, usersServersTable, serversHistoryTable) has zero query-shape test coverage — queryShapes.test.ts imports only from ./app. Add at least one serversTable and one usersServersTable lookup so a future divergence is caught.

🟢 [low] Generated files (*.sql, _journal.json, migrations.js, 0000_snapshot.json) have no trailing newline and are in .eslintignore/.prettierignore, so the hook won't fix them — expect noisy diffs on every regeneration unless normalised.

🟢 [low] All *_at/ts/ls timestamp columns are real() (correct WMDB parity — they store integer epoch-ms in a REAL-affinity column). Worth a one-line schema comment so accessor authors don't expect a Date.

Open question for the wipe-and-restore migration (not this PR): WMDB non-optional string columns default to '' on insert, never NULL, but the Drizzle schema makes them all nullable; and users_count was a WMDB string historically, now read as a number. When legacy rows are copied, watch for '' → NULL and '' → NaN/0 drift where downstream code does === '' checks.

Reviewed by an automated pass; treat comments as suggestions, not blockers.

@diegolmello diegolmello changed the base branch from develop to feat/native-1272-sqlcipher-migration June 19, 2026 15:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant