Skip to main content

@helix/shared/migrations

Unified migration services for Tier 2 (tenant) and Tier 3 (product) databases.

Overview

Provides migration services that work like Laravel's php artisan tenants:migrate:

  • TenantMigrator: Migrate all tenant databases (Tier 2)
  • ProductMigrator: Migrate all product databases (Tier 3)

Both support:

  • Deployment: Migrate all active databases
  • Provisioning: Migrate a single newly created database

Installation

This is an internal library. Import using:

import { TenantMigrator, ProductMigrator } from '@helix/shared/migrations';

Usage

Deployment (All Databases)

# Migrate all tenant databases (Tier 2)
npm run migrate:tenants

# Migrate all product databases (Tier 3)
npm run migrate:products

# Migrate specific product type
npm run migrate:products -- --product=kira

Provisioning (Single Database)

import { TenantMigrator, ProductMigrator } from '@helix/shared/migrations';

// After creating new tenant
const tenant = await tenantService.create(data);
await tenantMigrator.migrateOne(tenant.id);

// After provisioning new product
const product = await productService.provision(tenantId, 'kira');
await productMigrator.migrateOne(product.id);

TenantMigrator (Tier 2)

Migrates tenant shared databases (tenant_{uuid}_shared).

Methods

migrateAll(concurrency?: number)

Migrate all active tenant databases in parallel.

const results = await tenantMigrator.migrateAll(5);
// { success: ['tenant_1', 'tenant_2'], failed: [] }

migrateOne(tenantId: string)

Migrate a single tenant database.

await tenantMigrator.migrateOne('abc-123');

needsMigration(tenantId: string)

Check if tenant database has pending migrations.

const pending = await tenantMigrator.needsMigration('abc-123');
// true if migrations pending

Example: Tenant Provisioning

@Injectable()
export class TenantService {
constructor(
private readonly db: DatabaseService,
private readonly tenantMigrator: TenantMigrator,
) {}

async createTenant(data: CreateTenantDto) {
// 1. Create tenant record
const tenant = await this.db.central().tenant.create({
data: {
name: data.name,
domain: data.domain,
jurisdiction: data.jurisdiction,
workosOrganizationId: data.workosOrgId,
},
});

// 2. Provision Tier 2 database
const databaseName = `tenant_${tenant.id}_shared`;
const dbCreds = await this.createDatabase(databaseName, data.jurisdiction);

// 3. Store database credentials
await this.db.central().tenantDatabase.create({
data: {
tenantId: tenant.id,
databaseHost: dbCreds.host,
databasePort: dbCreds.port,
databaseName: dbCreds.name,
databaseUsername: dbCreds.username,
databasePassword: dbCreds.password, // Encrypted
},
});

// 4. Run migrations on new tenant database
await this.tenantMigrator.migrateOne(tenant.id);

return tenant;
}
}

ProductMigrator (Tier 3)

Migrates product databases (tenant_{uuid}_{product}).

Methods

migrateAll(concurrency?: number)

Migrate all active product databases in parallel.

const results = await productMigrator.migrateAll(5);
// { success: ['product_1', 'product_2'], failed: [] }

migrateOne(tenantProductId: string)

Migrate a single product database.

await productMigrator.migrateOne('product-xyz');

migrateByProduct(productType: string, concurrency?: number)

Migrate all databases for a specific product type.

// Migrate all KIRA databases
const results = await productMigrator.migrateByProduct('kira', 5);

needsMigration(tenantProductId: string)

Check if product database has pending migrations.

const pending = await productMigrator.needsMigration('product-xyz');

Example: Product Provisioning

@Injectable()
export class ProductProvisioningService {
constructor(
private readonly db: DatabaseService,
private readonly productMigrator: ProductMigrator,
) {}

async provisionProduct(tenantId: string, productType: string) {
const tenant = await this.db.central().tenant.findUnique({
where: { id: tenantId },
});

// 1. Create product record
const product = await this.db.central().tenantProduct.create({
data: {
tenantId,
productType,
s3BucketPath: `helix-${tenant.jurisdiction}/${tenantId}/${productType}`,
},
});

// 2. Provision Tier 3 database
const databaseName = `tenant_${tenantId}_${productType}`;
const dbCreds = await this.createDatabase(databaseName, tenant.jurisdiction, productType);

// 3. Store database credentials
await this.db.central().productDatabase.create({
data: {
tenantProductId: product.id,
databaseHost: dbCreds.host,
databasePort: dbCreds.port,
databaseName: dbCreds.name,
databaseUsername: dbCreds.username,
databasePassword: dbCreds.password, // Encrypted
},
});

// 4. Run migrations on new product database
await this.productMigrator.migrateOne(product.id);

return product;
}
}

Deployment Flow

#!/bin/bash
# deploy.sh

echo "Deploying Helix platform..."

# 1. Pull latest code
git pull origin main

# 2. Install dependencies
npm install

# 3. Build all services
nx build --all

# 4. Run migrations (in order)
echo "Running Central DB migrations..."
cd prisma/central && npx prisma migrate deploy && cd ../..

echo "Running Tenant DB migrations..."
npm run migrate:tenants

echo "Running Product DB migrations..."
npm run migrate:products

# 5. Restart services
pm2 restart all

echo "Deployment complete!"

CLI Commands

Add to package.json:

{
"scripts": {
"migrate:central": "cd prisma/central && npx prisma migrate deploy",
"migrate:tenants": "tsx scripts/migrate-tenants.ts",
"migrate:products": "tsx scripts/migrate-products.ts"
}
}

CLI Scripts

// scripts/migrate-tenants.ts
import { NestFactory } from '@nestjs/core';
import { TenantMigrator } from '@helix/shared/migrations';
import { AppModule } from './migration-app.module';

async function migrate() {
const app = await NestFactory.createApplicationContext(AppModule);
const migrator = app.get(TenantMigrator);

const results = await migrator.migrateAll();

console.log(`Success: ${results.success.length}`);
console.log(`Failed: ${results.failed.length}`);

if (results.failed.length > 0) {
process.exit(1);
}

await app.close();
}

migrate();
// scripts/migrate-products.ts
import { NestFactory } from '@nestjs/core';
import { ProductMigrator } from '@helix/shared/migrations';
import { AppModule } from './migration-app.module';

async function migrate() {
const app = await NestFactory.createApplicationContext(AppModule);
const migrator = app.get(ProductMigrator);

const productType = process.argv.find(arg => arg.startsWith('--product='))?.split('=')[1];

const results = productType
? await migrator.migrateByProduct(productType)
: await migrator.migrateAll();

console.log(`Success: ${results.success.length}`);
console.log(`Failed: ${results.failed.length}`);

if (results.failed.length > 0) {
process.exit(1);
}

await app.close();
}

migrate();

Error Handling

// Tenant not found
try {
await tenantMigrator.migrateOne('invalid-id');
} catch (error) {
// Error: Tenant not found: invalid-id
}

// Database not provisioned
try {
await tenantMigrator.migrateOne('tenant-without-db');
} catch (error) {
// Error: Tenant database not provisioned: tenant-without-db
}

// Migration failed
try {
await productMigrator.migrateOne('product-123');
} catch (error) {
// Error: Prisma migration failed: ...
}

Multi-Jurisdiction Support

Migrations respect the tenant's jurisdiction:

// UK Tenant
{
jurisdiction: 'uk',
tenantDatabase: {
host: 'uk-tier2-db.helix.com', // UK database server
// ...
}
}

// EU Tenant
{
jurisdiction: 'eu',
tenantDatabase: {
host: 'eu-tier2-db.helix.com', // EU database server
// ...
}
}

Migrations run against the correct database server based on stored credentials.


Concurrency Control

Both migrators support parallel execution with concurrency limits:

// Migrate 10 tenants at a time
await tenantMigrator.migrateAll(10);

// Migrate 5 products at a time
await productMigrator.migrateAll(5);

Default: 5 concurrent migrations

Why Limit?

  • Prevents overwhelming database servers
  • Avoids connection pool exhaustion
  • Better error tracking

Monitoring

Both migrators provide detailed logging:

[TenantMigrator] Starting migration for all tenant databases...
[TenantMigrator] Found 50 active tenants to migrate
[TenantMigrator] ✓ Migrated tenant: Acme Corp (abc-123)
[TenantMigrator] ✓ Migrated tenant: Widget Inc (def-456)
[TenantMigrator] ✗ Failed to migrate tenant: Test Co (xyz-789)
[TenantMigrator] Migration complete: 49 succeeded, 1 failed


Development

# Build the library
nx build shared-migrations

# Lint the library
nx lint shared-migrations

# Test the library
nx test shared-migrations

License

Proprietary - CleverChain Limited