> ## Documentation Index
> Fetch the complete documentation index at: https://www.orionjs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Migrations

> Data migrations and schema updates in Orionjs

Migrations in Orionjs are used to perform data transformations and schema updates on existing data. They run automatically via a background job and track completion state to ensure each migration runs only once.

## Installation

```bash theme={null}
pnpm add @orion-js/migrations
```

## Structure and Naming

* Use `@MigrationService()` decorator from `@orion-js/migrations`
* Each migration should be a separate class
* Follow naming convention: `Migrate{Entity}{Action}.v{Version}` (e.g., `MigrateUsersPlanType.v1`)
* Implement a `runMigration()` method that performs the migration logic
* Version suffix (`.v1`, `.v2`) allows re-running modified migrations
* Put migrations in `app/{component}/migrations/Migrate{Entity}{Action}/index.ts`

## Best Practices

* **Idempotency**: Design migrations to be safely re-runnable when possible
* **Progress logging**: Log progress every N documents for long-running migrations
* **Query optimization**: Only fetch documents that need updating (use `$exists: false` or similar filters)
* **Batch processing**: Process documents in a cursor loop, not loading all into memory
* **Direct collection access**: Migrations are the exception where direct collection access is allowed
* **Use `useMongoTransactions: false`** unless you specifically need transaction support
* Use dependency injection to leverage existing services for business logic
* Define local interfaces for document shapes to avoid tight coupling with schema changes

## Complete Example

```typescript theme={null}
import {logger} from '@orion-js/logger'
import {MigrationService} from '@orion-js/migrations'
import {createCollection} from '@orion-js/mongodb'
import {Inject} from '@orion-js/services'
import {CalculateCallerPlanTypeService} from 'app/ai/services/CalculateCallerPlanType'

/**
 * Local interface for the document structure we're migrating.
 * Keeps the migration decoupled from schema changes.
 */
interface GenerationLogCaller {
  userId?: string
  organizationId?: string
  isAnonymous?: boolean
}

interface GenerationLogDoc {
  _id: string
  caller?: GenerationLogCaller
}

/**
 * Migration to set planType and planId for all existing GenerationLogs.
 * Uses current subscription status since all logs were generated recently.
 */
@MigrationService({
  name: 'MigrateGenerationLogsPlanType.v1',
  useMongoTransactions: false,
})
export class MigrateGenerationLogsPlanType {
  // Direct collection access is allowed during migrations
  private collection = createCollection({name: 'ai.generation_logs'})

  @Inject(() => CalculateCallerPlanTypeService)
  private calculateCallerPlanTypeService: CalculateCallerPlanTypeService

  async runMigration() {
    // Only fetch documents that need updating
    const cursor = this.collection.find({
      'caller.planType': {$exists: false},
    })

    let processed = 0
    let updated = 0

    // Process documents in a cursor loop to avoid memory issues
    for await (const doc of cursor) {
      const log = doc as GenerationLogDoc
      const planInfo = await this.calculateCallerPlanTypeService.execute(log.caller)

      await this.collection.updateOne(
        {_id: log._id},
        {
          $set: {
            'caller.planType': planInfo.planType,
            ...(planInfo.planId && {'caller.planId': planInfo.planId}),
          },
        },
      )

      updated++
      processed++

      // Log progress every 500 documents
      if (processed % 500 === 0) {
        logger.info('Migration progress', {processed, updated})
      }
    }

    logger.info('MigrateGenerationLogsPlanType complete', {processed, updated})
  }
}
```

## Simple Migration Example

For simple field additions or transformations without external dependencies:

```typescript theme={null}
import {logger} from '@orion-js/logger'
import {MigrationService} from '@orion-js/migrations'
import {createCollection} from '@orion-js/mongodb'

/**
 * Add default status field to all users without one.
 */
@MigrationService({
  name: 'MigrateUsersAddDefaultStatus.v1',
  useMongoTransactions: false,
})
export class MigrateUsersAddDefaultStatus {
  private collection = createCollection({name: 'auth.users'})

  async runMigration() {
    const result = await this.collection.updateMany(
      {status: {$exists: false}},
      {$set: {status: 'active'}},
    )

    logger.info('MigrateUsersAddDefaultStatus complete', {
      matchedCount: result.matchedCount,
      modifiedCount: result.modifiedCount,
    })
  }
}
```

## Loading Migrations

Migrations must be registered using `loadMigrations()` to enable automatic execution:

```typescript theme={null}
import {loadMigrations} from '@orion-js/migrations'
import {MigrateGenerationLogsPlanType} from './migrations/MigrateGenerationLogsPlanType'
import {MigrateUsersEmailFormat} from './migrations/MigrateUsersEmailFormat'

// Register all migrations - they will run automatically via background job
loadMigrations([
  MigrateGenerationLogsPlanType,
  MigrateUsersEmailFormat,
])
```

## How Migrations Run

* Migrations are executed by a background job that polls every 30 seconds
* Only one migration runs at a time, in the order they are registered
* Completed migrations are tracked in the `orionjs.migrations` collection
* The migration name serves as the unique identifier for tracking completion
* A lock prevents concurrent migration execution across multiple server instances

## MongoDB Transactions

For operations that need to be atomic, you can use MongoDB transactions:

```typescript theme={null}
@MigrationService({
  name: 'TransactionalMigration.v1',
  useMongoTransactions: true
})
export class TransactionalMigration {
  async runMigration() {
    // All database operations will be in a transaction
    // If any operation fails, all changes will be rolled back
  }
}
```

## Long-Running Migrations

For migrations that take a long time, extend the lock time:

```typescript theme={null}
async runMigration(context: ExecutionContext) {
  // Extend lock time to two hours
  context.extendLockTime(1000 * 60 * 60 * 2)

  // Long-running operations...
}
```

## Disabling Automatic Execution

If you want to manually control when migrations run:

```typescript theme={null}
loadMigrations([MigrationExample1, MigrationExample2], {
  omitJob: true
})
```

Then manually trigger migrations:

```typescript theme={null}
import {getInstance} from '@orion-js/services'
import {MigrationsService} from '@orion-js/migrations'
import {createContext} from '@orion-js/dogs'

const migrationService = getInstance(MigrationsService)
const context = createContext()
await migrationService.runMigrations(migrations, context)
```

## When to Create a New Migration Version

Create a new version (`.v2`, `.v3`, etc.) when:

* The original migration logic had a bug that needs fixing
* You need to re-run a migration with modified logic
* The migration name must change to run again (completed names are tracked)
