@ftschopp/dynatable-migrations
v1.2.0
Published
DynamoDB migration tool for single table design with schema versioning
Downloads
166
Maintainers
Readme
@ftschopp/dynatable-migrations
DynamoDB migration tool for single table design with schema versioning.
Features
- Single Table Design - Built specifically for DynamoDB single table design patterns
- Semantic Versioning - Migrations use semver (0.1.0, 0.2.0, 1.0.0) for clear version tracking
- Up/Down Migrations - Support for both applying and rolling back migrations
- Migration History - All migration records stored in your DynamoDB table using Single Table Design
- Distributed Locking - Prevents concurrent migrations with automatic lock expiration
- Dry Run Mode - Preview changes before applying them
- TypeScript First - Full TypeScript support with type safety
- CLI Tool - Easy-to-use command-line interface
Installation
npm install @ftschopp/dynatable-migrations
# or
yarn add @ftschopp/dynatable-migrationsQuick Start
1. Initialize
Create migration structure in your project:
npx dynatable-migrate initThis creates:
migrations/directory for your migration filesdynatable.config.jsconfiguration file
2. Configure
Edit dynatable.config.js:
module.exports = {
tableName: 'MyTable',
client: {
region: 'us-east-1',
// For local DynamoDB
endpoint: 'http://localhost:8000',
credentials: {
accessKeyId: 'local',
secretAccessKey: 'local',
},
},
migrationsDir: './migrations',
};3. Create Migration
# Create with auto-incremented patch version (default)
npx dynatable-migrate create add_user_email
# Create with specific bump type
npx dynatable-migrate create add_feature --type minor
npx dynatable-migrate create breaking_change --type major
# Create with explicit version
npx dynatable-migrate create custom_version --explicit 2.0.0This creates a file like migrations/0.1.0_add_user_email.ts
4. Edit Migration
import { Migration } from '@ftschopp/dynatable-migrations';
export const migration: Migration = {
version: '0.1.0',
name: 'add_user_email',
description: 'Add email field to User entity',
async up(context) {
const { client, tableName, dynamodb } = context;
const { ScanCommand, UpdateCommand } = dynamodb;
// Scan all users
const result = await client.send(
new ScanCommand({
TableName: tableName,
FilterExpression: 'begins_with(PK, :pk)',
ExpressionAttributeValues: { ':pk': 'USER#' },
})
);
// Add email field to each user
for (const item of result.Items || []) {
await client.send(
new UpdateCommand({
TableName: tableName,
Key: { PK: item.PK, SK: item.SK },
UpdateExpression: 'SET email = :email, emailVerified = :verified',
ExpressionAttributeValues: {
':email': null,
':verified': false,
},
})
);
}
},
async down(context) {
const { client, tableName, dynamodb } = context;
const { ScanCommand, UpdateCommand } = dynamodb;
const result = await client.send(
new ScanCommand({
TableName: tableName,
FilterExpression: 'begins_with(PK, :pk)',
ExpressionAttributeValues: { ':pk': 'USER#' },
})
);
for (const item of result.Items || []) {
await client.send(
new UpdateCommand({
TableName: tableName,
Key: { PK: item.PK, SK: item.SK },
UpdateExpression: 'REMOVE email, emailVerified',
})
);
}
},
};5. Run Migrations
# Check status
npx dynatable-migrate status
# Preview changes (dry run)
npx dynatable-migrate up --dry-run
# Apply all pending migrations
npx dynatable-migrate up
# Apply only 1 migration
npx dynatable-migrate up --limit 1
# Rollback last migration
npx dynatable-migrate down
# Rollback last 2 migrations
npx dynatable-migrate down --steps 2
# Preview rollback
npx dynatable-migrate down --dry-runCLI Commands
init
Initialize migrations in your project.
dynatable-migrate initcreate <name>
Create a new migration file.
dynatable-migrate create add_user_profileOptions:
-c, --config <path>- Custom config file path-t, --type <type>- Version bump type:major,minor, orpatch(default: patch)-e, --explicit <version>- Explicit version (e.g., 2.0.0)
Examples:
# Patch bump: 0.1.0 -> 0.1.1
dynatable-migrate create fix_typo
# Minor bump: 0.1.1 -> 0.2.0
dynatable-migrate create add_notifications --type minor
# Major bump: 0.2.0 -> 1.0.0
dynatable-migrate create breaking_schema_change --type major
# Explicit version
dynatable-migrate create hotfix --explicit 0.1.2up
Run pending migrations.
dynatable-migrate upOptions:
-c, --config <path>- Custom config file path-l, --limit <number>- Limit number of migrations to run-d, --dry-run- Preview what would be done without making changes
down
Rollback migrations.
dynatable-migrate downOptions:
-c, --config <path>- Custom config file path-s, --steps <number>- Number of migrations to rollback (default: 1)-d, --dry-run- Preview what would be done without making changes
status
Show migration status.
dynatable-migrate statusOptions:
-c, --config <path>- Custom config file path
How It Works
Single Table Design
All migration tracking happens within your DynamoDB table using Single Table Design principles:
PK SK version status appliedAt
-----------------------------------------------------------------------
_SCHEMA#VERSION 0.1.0 0.1.0 applied 2025-03-29T10:00:00Z
_SCHEMA#VERSION 0.2.0 0.2.0 applied 2025-03-29T11:00:00Z
_SCHEMA#VERSION#CURRENT _SCHEMA#VERSION 0.2.0 - 2025-03-29T11:00:00ZMigration Context
Every migration receives a context object with:
interface MigrationContext {
client: DynamoDBDocumentClient; // AWS SDK client
tableName: string; // Your table name
tracker: MigrationTracker; // Track schema changes
config: MigrationConfig; // Your config
dynamodb: DynamoDBCommands; // Pre-imported DynamoDB commands
}The dynamodb object includes all common commands:
ScanCommand,QueryCommandGetCommand,PutCommand,UpdateCommand,DeleteCommandBatchGetCommand,BatchWriteCommandTransactWriteCommand,TransactGetCommand
Schema Change Tracking
Track what changed in each migration:
await tracker.recordSchemaChange({
entity: 'User',
changes: {
added: ['email', 'emailVerified'],
removed: ['oldField'],
modified: [{ field: 'status', from: 'string', to: 'enum' }],
},
});Configuration
Config File Options
interface MigrationConfig {
// Required: DynamoDB table name
tableName: string;
// Required: AWS client config
client: {
region: string;
endpoint?: string; // For local DynamoDB
credentials?: {
accessKeyId: string;
secretAccessKey: string;
};
};
// Optional: Migrations directory (default: ./migrations)
migrationsDir?: string;
// Optional: Tracking prefix (default: _SCHEMA#VERSION)
trackingPrefix?: string;
// Optional: GSI name for queries (default: GSI1)
gsi1Name?: string;
}Programmatic Usage
You can also use the migration runner programmatically:
import {
MigrationRunner,
loadConfig,
createDynamoDBClient,
} from '@ftschopp/dynatable-migrations';
const config = await loadConfig();
const client = createDynamoDBClient(config);
const runner = new MigrationRunner(client, config);
// Run migrations
await runner.up();
// Run with options
await runner.up({ limit: 1, dryRun: true });
// Get status
const status = await runner.status();
// Rollback
await runner.down({ steps: 1 });Available Exports
// Core classes
export { MigrationRunner } from './core/runner';
export { MigrationLoader } from './core/loader';
export { DynamoDBMigrationTracker } from './core/tracker';
// Config
export { loadConfig, ConfigLoader } from './core/config';
export { createDynamoDBClient } from './core/client';
// Commands (for programmatic use)
export { createMigration } from './commands/create';
export { runMigrations } from './commands/up';
export { rollbackMigrations } from './commands/down';
export { showStatus } from './commands/status';
export { initProject } from './commands/init';
// Types
export * from './types';Best Practices
- Always write down() functions - Even if you think you won't need to rollback
- Test migrations locally first - Use DynamoDB Local
- Use dry-run mode - Preview changes before applying:
dynatable-migrate up --dry-run - Backup before running - Use DynamoDB point-in-time recovery
- One change per migration - Keep migrations focused
- Don't modify applied migrations - Create a new migration instead
- Use transactions - For multi-step changes that must be atomic
- Use semantic versioning - major for breaking changes, minor for features, patch for fixes
License
MIT
