CLI

$pnpm add -D @datrix/cli
$datrix <command> [options]

datrix migrate

Compares your schemas against the current database state and runs the necessary migrations.

$datrix migrate
$datrix migrate --dry-run # preview plan without applying
$datrix migrate --status # show pending change counts only
$datrix migrate --verbose # show field/index details in the plan
$datrix migrate --yes # skip the confirmation prompt (New in 0.2.0)

--yes is required for non-interactive environments (CI): without a TTY and without --yes, migrate fails immediately instead of hanging on the confirmation prompt. Ambiguous changes still require a TTY to resolve interactively, or session.resolveAmbiguous() in code.

What happens

1
Load config

Loads datrix.config.ts and connects to the database

2
Diff schemas

Diffs each schema against the live table structure

3
Print plan

Color-coded: + create, - drop, ~ alter (if there are pending changes)

4
Apply

Prompts for confirmation, then applies

Ambiguous changes

When a field is removed and a new one is added at the same time, Datrix cannot tell whether this is a rename or a drop + add. You will be prompted to choose:

Ambiguous changes detected:

user.name->fullName
  ⚠ Renaming will preserve data. Dropping will lose it.

  1. Rename column 'name' to 'fullName' (data preserved)
  2. Drop column 'name' and add column 'fullName' (data lost)

Choose option (1-2):

Call session.resolveAmbiguous(id, type) in code if you want to resolve programmatically instead of interactively.

Auto-migrate on startup

Set migration.auto: true in defineConfig to apply migrations automatically when the app starts — no CLI needed:

export default defineConfig(() => ({
  adapter: new PostgresAdapter({ ... }),
  schemas: [...],
  migration: {
    auto: true,
  },
}))

datrix generate config <db>

New in 0.2.0. Generates a datrix.config.ts template wired up for the chosen adapter. Runs before any config file is loaded, so it works even in a fresh project.

$datrix generate config postgres
$datrix generate config mongodb --output ./config/datrix.config.ts
$datrix generate config mysql --force # overwrite an existing file
ValueAdapter
postgres@datrix/adapter-postgres
postgres-core@datrix/adapter-postgres-core
mysql@datrix/adapter-mysql
json@datrix/adapter-json
mongodb@datrix/adapter-mongodb

Output: datrix.config.ts in the current directory (or the path given by --output). Fails if the file already exists unless --force is passed.

import { createPostgresAdapter } from '@datrix/adapter-postgres';
import { defineConfig } from '@datrix/core';

// Import your schemas here
// import { userSchema } from './schemas/user.schema';

export default defineConfig(() => ({
  adapter: createPostgresAdapter({
    host: process.env.DB_HOST ?? 'localhost',
    port: Number(process.env.DB_PORT) || 5432,
    database: process.env.DB_NAME ?? 'myapp',
    user: process.env.DB_USER ?? 'postgres',
    password: process.env.DB_PASSWORD ?? 'password',
  }),
  schemas: [
    // Add your schemas here
  ],
}));

datrix generate schema <Name>

Generates a defineSchema template file.

$datrix generate schema User
$datrix generate schema BlogPost --output ./src/schemas
$datrix generate schema User --force # overwrite an existing file

Output: schemas/user.schema.ts (or the path specified by --output)

import { defineSchema } from "@datrix/core"

export const userSchema = defineSchema({
  name: "user",

  fields: {
    // Add your fields here
    // id, createdAt, updatedAt are added automatically

    // name: {
    //   type: "string",
    //   required: true,
    //   minLength: 2,
    //   maxLength: 100,
    // },
  },

  indexes: [
    // { fields: ["email"], unique: true },
  ],

  // permission: Only needed if you are using @datrix/api for HTTP access control.
  // permission: {
  //   create: true,
  //   read: true,
  //   update: true,
  //   delete: true,
  // },
} satisfies SchemaDefinition)

After editing, run datrix migrate to apply the new table.


datrix generate types

Generates TypeScript types from all registered schemas into a single file. For each schema it produces a Base interface, Relation/RelationUpdate interfaces for nested types, and Create/Update input types — ready to import across your codebase.

New in 0.2.0: does not require a live database connection — schema definitions are read directly from your config file, so it runs safely in CI or offline local development.

$datrix generate types
$datrix generate types --output ./src/types/db.ts

Output: types/generated.ts (or the path specified by --output)

// generated.ts (example)
import type {
  DatrixEntry,
  RelationBelongsTo,
  RelationHasMany,
} from "@datrix/core"

// ─────────────────────────────────────────
// User  (table: user)
// ─────────────────────────────────────────

export interface UserBase extends DatrixEntry {
  email: string
  name?: string
  role: "admin" | "editor" | "user"
}

export interface UserRelation {
  posts?: PostBase[]
}

export interface UserRelationUpdate {
  posts?: RelationHasMany<PostBase>
}

export type User = UserBase & UserRelation
export type CreateUserInput = Omit<UserBase, keyof DatrixEntry> & UserRelationUpdate
export type UpdateUserInput = Partial<Omit<UserBase, keyof DatrixEntry>> & UserRelationUpdate

// ─────────────────────────────────────────
// Post  (table: post)
// relations: author → belongsTo(user)
// ─────────────────────────────────────────

export interface PostBase extends DatrixEntry {
  title: string
  slug: string
  status: "draft" | "published" | "archived"
}

export interface PostRelation {
  author?: UserBase
}

export interface PostRelationUpdate {
  author?: RelationBelongsTo<UserBase>
}

export type Post = PostBase & PostRelation
export type CreatePostInput = Omit<PostBase, keyof DatrixEntry> & PostRelationUpdate
export type UpdatePostInput = Partial<Omit<PostBase, keyof DatrixEntry>> & PostRelationUpdate

datrix export

Exports all data from your database into a single zip file. The zip contains a metadata.json with schema definitions and one or more CSV chunks per table.

$datrix export
$datrix export --output ./backups/prod-2024-01-15.zip
OptionDescription
--output <path>Output file path (default: ./export_<timestamp>.zip)
--verboseDetailed output

What gets exported

  • All tables including junction tables (manyToMany)
  • All rows, chunked in batches of 1000 for memory efficiency
  • Schema definitions — so the importer can recreate tables without a running app
  • Table creation order is sorted by FK dependency, ensuring a safe import sequence

Format

export_2024-01-15T10-30-00.zip
├── metadata.json        # schema definitions + chunk file index
├── users_0.csv          # first 1000 rows of users
├── users_1.csv          # next 1000 rows of users
├── posts_0.csv
└── post_tag_0.csv       # junction table

Exporting media files

If your project uses @datrix/api-upload, pass --include-files to also download all media files alongside the database export.

$datrix export --include-files
$datrix export --include-files --output ./backups/full-export

This creates an output directory instead of a single zip:

full-export/
├── export.zip           # database export (same format as above)
├── files-progress.txt   # download ledger
└── files/
    ├── 1710000000-photo.jpg
    └── 1710000000-thumb.jpg
OptionDescription
--include-filesDownload media files in addition to DB data
--output <path>Output directory path (default: ./export_<timestamp>/)
--pack-filesPack downloaded files into zip chunks instead of leaving them loose
--pack-files-chunk-size <bytes>Max bytes per chunk zip (default: 1 GB)
--resume <path>Resume a previously interrupted file download

Progress ledger

files-progress.txt tracks the download status of every media file. Each line has the format:

<id> <key> <status>
StatusMeaning
pendingNot yet downloaded
doneSuccessfully downloaded
missingServer returned 404 — file no longer exists at the source
restrictedServer returned 403 — file exists but credentials are not supported yet

missing and restricted entries are skipped silently and will not be retried on resume. All other errors (network failures, 5xx) stop the export so you can resume later.

Resuming an interrupted export

If a download is interrupted, run the same command with --resume pointing to the output directory:

$datrix export --include-files --resume ./backups/full-export

Only pending entries in files-progress.txt will be retried.

Packing files into zip chunks

Use --pack-files to archive downloaded files into chunks after downloading. Original files are removed after packing. Useful when transferring large exports.

$datrix export --include-files --pack-files
$datrix export --include-files --pack-files --pack-files-chunk-size 536870912 # 512 MB chunks
full-export/
├── export.zip
├── files-progress.txt
└── files/
    ├── chunk_0.zip
    └── chunk_1.zip

datrix import

Imports data from a previously exported zip file. Drops all existing data and restores from the export.

$datrix import ./backups/prod-2024-01-15.zip
$datrix import ./backups/prod-2024-01-15.zip --agree # skip all confirmation prompts
$datrix import ./backups/prod-2024-01-15.zip --agree drop-db # only skip the "drop all data" prompt
$datrix import ./backups/prod-2024-01-15.zip --agree missing-files # only skip the "continue without missing files" prompt
OptionDescription
--agreeSkip all confirmation prompts
--agree drop-dbSkip only the "drop all data" prompt — a missing-files prompt (with --with-files) still asks
--agree missing-filesSkip only the "continue without the missing file(s)" prompt — the drop-data prompt still asks
--verboseDetailed output

New in 0.2.0: --agree now accepts scoped values. Previously, passing any string (e.g. --agree missing-files) was coerced to full consent, which could silently skip the "drop all data" confirmation too — this is now fixed to only cover its named scope.

What happens

1
ConfirmPrompts for confirmation (skipped with --agree)
2
Drop tablesDrops all existing tables
3
Recreate tables

From the exported schema definitions — without FK constraints

4
Insert rowsInserts all rows chunk by chunk
5
Add FK constraintsVia ALTER TABLE
6
Reset sequences

Resets auto-increment sequences to continue from the last imported ID

Use cases

  • Database migrations — move data between adapters (e.g. JSON → PostgreSQL)
  • Environment sync — copy production data to staging
  • Backups — restore from a known good state
# Copy production data to a local JSON adapter for development
$datrix export --config ./config/prod.config.ts --output ./prod-snapshot.zip
$datrix import ./prod-snapshot.zip --config ./config/local.config.ts --agree

Global options

OptionDescription
--config <path>Path to config file (default: ./datrix.config.ts)
--verboseDetailed output
--help, -hShow help
--version, -vPrint the installed CLI version (New in 0.2.0)
$datrix migrate --config ./config/datrix.config.ts --verbose
$datrix --version

New in 0.2.0: an unrecognized single-dash option (e.g. -x) now exits with an error instead of being silently ignored.