AQM = Actions · Queries · Mutations

Migrate your SQL
database to Convex

SunSetter reads your schema — tables, constraints, enums, foreign keys — and generates a complete Convex backend. Schema, queries, mutations, actions, crons, auth, and React hooks from one command.

$ npx @heyoub/sunsetter-aqm wizard
Works with PostgreSQL MySQL SQLite SQL Server
Live schema graph — rotate with cursor
scroll

What it does

Most migration tools move your data and drop everything else. SunSetter carries over your schema logic too.

Constraint Translation

SQL CHECK constraints are parsed and converted to Convex validators. CHECK (age >= 18 AND age <= 120) becomes v.number().gte(18).lte(120).

Index Suggestions

Analyzes your foreign keys and column patterns to suggest Convex indexes with priority levels and copy-paste-ready code.

Full Code Generation

Generates schema.ts, queries, mutations, actions, HTTP endpoints, TypeScript interfaces, React hooks, and cron jobs for every table.

Component Config

Detects write-heavy tables and generates convex.config.ts with rate limiter, aggregate, and migrations components pre-wired.

Cron Scaffolding

Generates crons.ts with cleanup jobs for tables that have expires_at, deleted_at, or stuck-status patterns — using internalMutation.

Auth Scaffolding

Detects a users table with email + password columns and generates Convex Auth setup (auth.ts, auth.config.ts) with the Password provider.

File Storage Mapping

BYTEA, BLOB, and binary columns are mapped to v.id("_storage") with generated upload/download action stubs using Convex file storage.

PostgreSQL Deep Support

Auto-detects pg_enum types, handles partitioned tables and materialized views, and maps extension types like ltree and hstore.

Streaming Migration

Cursor-based pagination keeps memory constant regardless of table size. Supports parallel table migration, checkpointing, dry-run, and rollback.

What you get

Point SunSetter at a SQL database. It writes your Convex project structure.

convex/schema.ts
export default defineSchema({
  users: defineTable({
    name: v.string(),
    email: v.string(),
    age: v.number().gte(18).lte(120),
    role: v.union(
      v.literal("admin"),
      v.literal("editor"),
      v.literal("viewer")
    ),
    avatarId: v.optional(v.id("_storage")),
    orgId: v.id("organizations"),
  })
    .index("by_email", ["email"])
    .index("by_org", ["orgId"])
    .searchIndex("search_name", {
      searchField: "name",
    }),
});
convex/crons.ts
import { cronJobs } from "convex/server";
import { internalMutation } from "./_generated/server";
import { internal } from "./_generated/api";

const crons = cronJobs();

crons.daily(
  "Purge soft-deleted users",
  { hourUTC: 3, minuteUTC: 0 },
  internal.crons.purgeDeletedUsers
);

export default crons;
convex/convex.config.ts
import { defineApp } from "convex/server";
import migrations from
  "@convex-dev/migrations/convex.config";
import rateLimiter from
  "@convex-dev/rate-limiter/convex.config";

const app = defineApp();
app.use(migrations);
app.use(rateLimiter);

export default app;
convex/users/mutations.ts
export const create = mutation({
  args: {
    name: v.string(),
    email: v.string(),
    age: v.number(),
    orgId: v.id("organizations"),
  },
  handler: async (ctx, args) => {
    return ctx.db.insert("users", args);
  },
});
Generated per table:
schema.ts queries.ts mutations.ts actions.ts http.ts types.ts hooks.ts scheduled.ts crons.ts ✦ convex.config.ts ✦ auth.ts ✦

How it works

Three steps: connect, introspect, generate. Takes about 30 seconds on a real schema.

bash LIVE
$ npx @heyoub/sunsetter-aqm wizard
? Database type PostgreSQL
? Connection string postgresql://localhost:5432/myapp
Connected to PostgreSQL 15.4 (Amazon Aurora)
Found 14 tables, 3 enums, 42 indexes in schema "public"
CHECK: users.age >= 18 AND age <= 120 → v.number().gte(18).lte(120)
CHECK: price > 0 → v.number().gt(0)
ENUM: role_type (admin, editor, viewer) → v.union(v.literal(...))
FK: users.org_id → organizations._id → v.id("organizations")
BYTEA: users.avatar → v.id("_storage") + file storage actions
AUTH: users table with email+password detected → auth.ts scaffolded
CRON: users.deleted_at detected → daily purge cron generated
COMPONENTS: 3 write-heavy tables → rate-limiter added to convex.config.ts
Generated 14 table dirs + schema.ts + crons.ts + convex.config.ts + auth.ts
Output: ./convex/
sunsetter wizard Interactive guided setup
sunsetter generate --db postgres --url <conn> Generate code only, no data move
sunsetter migrate --mode schema-and-data Full migration with data transfer
sunsetter introspect Print schema analysis without writing
sunsetter preflight Validate before migrating
sunsetter --mcp Start MCP server for Claude

Claude MCP Integration

SunSetter ships a built-in Model Context Protocol server. Connect it to Claude Code or Claude Desktop and let the AI analyze your schema, plan the migration, and run the tool — without leaving your conversation.

sunsetter --mcp
connectConnect to source database
introspectAnalyze schema structure
generateGenerate Convex code
migrateExecute migration
rollbackUndo last migration

Free and open source

MIT-licensed. Use it, fork it, contribute to it. If you have a complex migration and want hands-on help, consulting is available.