@ftschopp/dynatable-migrations 1.2.6 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.md +12 -0
- package/dist/cli.js +21 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/create.js +1 -1
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/down.d.ts +1 -1
- package/dist/commands/down.d.ts.map +1 -1
- package/dist/commands/down.js +61 -1
- package/dist/commands/down.js.map +1 -1
- package/dist/commands/unlock.d.ts +8 -0
- package/dist/commands/unlock.d.ts.map +1 -0
- package/dist/commands/unlock.js +87 -0
- package/dist/commands/unlock.js.map +1 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +14 -1
- package/dist/core/config.js.map +1 -1
- package/dist/core/loader.d.ts +22 -1
- package/dist/core/loader.d.ts.map +1 -1
- package/dist/core/loader.js +56 -6
- package/dist/core/loader.js.map +1 -1
- package/dist/core/runner.d.ts.map +1 -1
- package/dist/core/runner.js +29 -8
- package/dist/core/runner.js.map +1 -1
- package/dist/core/tracker.d.ts +18 -3
- package/dist/core/tracker.d.ts.map +1 -1
- package/dist/core/tracker.js +60 -23
- package/dist/core/tracker.js.map +1 -1
- package/dist/types/index.d.ts +7 -2
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +35 -4
- package/.gitkeep +0 -0
- package/jest.config.js +0 -8
- package/migrations/.gitkeep +0 -0
- package/src/cli-utils.test.ts +0 -45
- package/src/cli-utils.ts +0 -18
- package/src/cli.ts +0 -110
- package/src/commands/create.ts +0 -111
- package/src/commands/down.ts +0 -26
- package/src/commands/init.ts +0 -37
- package/src/commands/status.ts +0 -75
- package/src/commands/up.ts +0 -26
- package/src/core/client.ts +0 -24
- package/src/core/config.ts +0 -140
- package/src/core/errors.ts +0 -55
- package/src/core/loader.ts +0 -256
- package/src/core/lock-heartbeat.test.ts +0 -78
- package/src/core/lock-heartbeat.ts +0 -48
- package/src/core/runner.test.ts +0 -51
- package/src/core/runner.ts +0 -315
- package/src/core/semver.test.ts +0 -33
- package/src/core/semver.ts +0 -26
- package/src/core/tracker.test.ts +0 -281
- package/src/core/tracker.ts +0 -559
- package/src/index.ts +0 -25
- package/src/templates/migration.ts +0 -92
- package/src/types/index.ts +0 -220
- package/tsconfig.json +0 -19
package/src/commands/create.ts
DELETED
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import * as fs from 'fs';
|
|
2
|
-
import * as path from 'path';
|
|
3
|
-
import { MigrationLoader } from '../core/loader';
|
|
4
|
-
import { generateMigrationTemplate } from '../templates/migration';
|
|
5
|
-
|
|
6
|
-
type VersionBumpType = 'major' | 'minor' | 'patch';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Get next version based on bump type
|
|
10
|
-
*/
|
|
11
|
-
async function getNextVersion(loader: MigrationLoader, bumpType: VersionBumpType): Promise<string> {
|
|
12
|
-
const migrations = await loader['loadMigrations']();
|
|
13
|
-
|
|
14
|
-
const lastMigration = migrations.length > 0 ? migrations[migrations.length - 1] : null;
|
|
15
|
-
const lastVersion = lastMigration?.version;
|
|
16
|
-
const parts = lastVersion?.split('.');
|
|
17
|
-
|
|
18
|
-
if (!parts || parts.length !== 3 || !parts[0] || !parts[1] || !parts[2]) {
|
|
19
|
-
switch (bumpType) {
|
|
20
|
-
case 'major':
|
|
21
|
-
return '1.0.0';
|
|
22
|
-
case 'minor':
|
|
23
|
-
return '0.1.0';
|
|
24
|
-
case 'patch':
|
|
25
|
-
return '0.0.1';
|
|
26
|
-
default:
|
|
27
|
-
return '0.1.0';
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const major = parseInt(parts[0], 10);
|
|
32
|
-
const minor = parseInt(parts[1], 10);
|
|
33
|
-
const patch = parseInt(parts[2], 10);
|
|
34
|
-
|
|
35
|
-
switch (bumpType) {
|
|
36
|
-
case 'major':
|
|
37
|
-
return `${major + 1}.0.0`;
|
|
38
|
-
case 'minor':
|
|
39
|
-
return `${major}.${minor + 1}.0`;
|
|
40
|
-
case 'patch':
|
|
41
|
-
return `${major}.${minor}.${patch + 1}`;
|
|
42
|
-
default:
|
|
43
|
-
return `${major}.${minor}.${patch + 1}`;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export async function createMigration(
|
|
48
|
-
name: string,
|
|
49
|
-
migrationsDir: string = './migrations',
|
|
50
|
-
bumpType?: string,
|
|
51
|
-
explicitVersion?: string
|
|
52
|
-
): Promise<void> {
|
|
53
|
-
// Ensure migrations directory exists
|
|
54
|
-
if (!fs.existsSync(migrationsDir)) {
|
|
55
|
-
fs.mkdirSync(migrationsDir, { recursive: true });
|
|
56
|
-
console.log(`📁 Created migrations directory: ${migrationsDir}`);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Generate version
|
|
60
|
-
const loader = new MigrationLoader(migrationsDir);
|
|
61
|
-
let version: string;
|
|
62
|
-
|
|
63
|
-
if (explicitVersion) {
|
|
64
|
-
// Validate explicit version is semver
|
|
65
|
-
if (!/^\d+\.\d+\.\d+$/.test(explicitVersion)) {
|
|
66
|
-
throw new Error(
|
|
67
|
-
`Invalid version format: ${explicitVersion}. Expected semver format (e.g., 1.0.0)`
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
version = explicitVersion;
|
|
71
|
-
} else if (bumpType) {
|
|
72
|
-
// Validate bump type
|
|
73
|
-
const validTypes: VersionBumpType[] = ['major', 'minor', 'patch'];
|
|
74
|
-
if (!validTypes.includes(bumpType as VersionBumpType)) {
|
|
75
|
-
throw new Error(`Invalid bump type: ${bumpType}. Expected: major, minor, or patch`);
|
|
76
|
-
}
|
|
77
|
-
version = await getNextVersion(loader, bumpType as VersionBumpType);
|
|
78
|
-
} else {
|
|
79
|
-
// Default: increment patch
|
|
80
|
-
version = await getNextVersion(loader, 'patch');
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Sanitize name (replace spaces and special chars with underscores)
|
|
84
|
-
const sanitizedName = name.replace(/[^a-zA-Z0-9]/g, '_').toLowerCase();
|
|
85
|
-
|
|
86
|
-
// Generate file name
|
|
87
|
-
const fileName = `${version}_${sanitizedName}.ts`;
|
|
88
|
-
const filePath = path.join(migrationsDir, fileName);
|
|
89
|
-
|
|
90
|
-
// Check if file already exists
|
|
91
|
-
if (fs.existsSync(filePath)) {
|
|
92
|
-
throw new Error(`Migration file already exists: ${filePath}`);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Generate migration content
|
|
96
|
-
const content = generateMigrationTemplate(version, sanitizedName);
|
|
97
|
-
|
|
98
|
-
// Write file
|
|
99
|
-
fs.writeFileSync(filePath, content, 'utf-8');
|
|
100
|
-
|
|
101
|
-
// Determine version type for informational message
|
|
102
|
-
const versionType = bumpType ? bumpType.toUpperCase() : explicitVersion ? 'EXPLICIT' : 'PATCH';
|
|
103
|
-
|
|
104
|
-
console.log(`\n✅ Created migration: ${fileName}`);
|
|
105
|
-
console.log(` Path: ${filePath}`);
|
|
106
|
-
console.log(` Version: ${version} (${versionType})\n`);
|
|
107
|
-
console.log(`Next steps:`);
|
|
108
|
-
console.log(` 1. Edit ${fileName}`);
|
|
109
|
-
console.log(` 2. Implement up() and down() functions`);
|
|
110
|
-
console.log(` 3. Run: dynatable-migrate up\n`);
|
|
111
|
-
}
|
package/src/commands/down.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { MigrationRunner } from '../core/runner';
|
|
2
|
-
import { createDynamoDBClient } from '../core/client';
|
|
3
|
-
import { MigrationConfig } from '../types';
|
|
4
|
-
|
|
5
|
-
export async function rollbackMigrations(
|
|
6
|
-
config: MigrationConfig,
|
|
7
|
-
steps: number = 1,
|
|
8
|
-
dryRun: boolean = false
|
|
9
|
-
): Promise<void> {
|
|
10
|
-
const client = createDynamoDBClient(config);
|
|
11
|
-
const runner = new MigrationRunner(client, config);
|
|
12
|
-
|
|
13
|
-
try {
|
|
14
|
-
const rolledBack = await runner.down(steps, dryRun);
|
|
15
|
-
|
|
16
|
-
if (!dryRun && rolledBack.length > 0) {
|
|
17
|
-
console.log(`\n🎉 Successfully rolled back ${rolledBack.length} migration(s)`);
|
|
18
|
-
|
|
19
|
-
const currentVersion = await runner.getCurrentVersion();
|
|
20
|
-
console.log(`📌 Current version: ${currentVersion}\n`);
|
|
21
|
-
}
|
|
22
|
-
} catch (error: any) {
|
|
23
|
-
console.error(`\n❌ Rollback failed: ${error.message}\n`);
|
|
24
|
-
process.exit(1);
|
|
25
|
-
}
|
|
26
|
-
}
|
package/src/commands/init.ts
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import * as fs from 'fs';
|
|
2
|
-
import * as path from 'path';
|
|
3
|
-
import { ConfigLoader } from '../core/config';
|
|
4
|
-
|
|
5
|
-
export async function initProject(): Promise<void> {
|
|
6
|
-
const cwd = process.cwd();
|
|
7
|
-
|
|
8
|
-
console.log('\n🚀 Initializing DynamoDB migrations\n');
|
|
9
|
-
|
|
10
|
-
// Create migrations directory
|
|
11
|
-
const migrationsDir = path.join(cwd, 'migrations');
|
|
12
|
-
if (!fs.existsSync(migrationsDir)) {
|
|
13
|
-
fs.mkdirSync(migrationsDir, { recursive: true });
|
|
14
|
-
console.log(`✅ Created: ${migrationsDir}/`);
|
|
15
|
-
|
|
16
|
-
// Create .gitkeep
|
|
17
|
-
fs.writeFileSync(path.join(migrationsDir, '.gitkeep'), '');
|
|
18
|
-
} else {
|
|
19
|
-
console.log(`⚠️ Directory already exists: ${migrationsDir}/`);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// Create config file
|
|
23
|
-
const configPath = path.join(cwd, 'dynatable.config.js');
|
|
24
|
-
if (!fs.existsSync(configPath)) {
|
|
25
|
-
ConfigLoader.createDefaultConfig(configPath);
|
|
26
|
-
} else {
|
|
27
|
-
console.log(`⚠️ Config already exists: ${configPath}`);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
console.log('\n✅ Initialization complete!\n');
|
|
31
|
-
console.log('Next steps:');
|
|
32
|
-
console.log(' 1. Edit dynatable.config.js with your DynamoDB settings');
|
|
33
|
-
console.log(' 2. Create your first migration:');
|
|
34
|
-
console.log(' dynatable-migrate create add_user_email');
|
|
35
|
-
console.log(' 3. Run migrations:');
|
|
36
|
-
console.log(' dynatable-migrate up\n');
|
|
37
|
-
}
|
package/src/commands/status.ts
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { MigrationRunner } from '../core/runner';
|
|
2
|
-
import { createDynamoDBClient } from '../core/client';
|
|
3
|
-
import { MigrationConfig } from '../types';
|
|
4
|
-
|
|
5
|
-
export async function showStatus(config: MigrationConfig): Promise<void> {
|
|
6
|
-
const client = createDynamoDBClient(config);
|
|
7
|
-
const runner = new MigrationRunner(client, config);
|
|
8
|
-
|
|
9
|
-
try {
|
|
10
|
-
const statuses = await runner.status();
|
|
11
|
-
const currentVersion = await runner.getCurrentVersion();
|
|
12
|
-
|
|
13
|
-
console.log(`\n📊 Migration Status\n`);
|
|
14
|
-
console.log(`Table: ${config.tableName}`);
|
|
15
|
-
console.log(`Current version: ${currentVersion || 'v0000 (no migrations)'}`);
|
|
16
|
-
console.log(`Migrations directory: ${config.migrationsDir || './migrations'}\n`);
|
|
17
|
-
|
|
18
|
-
if (statuses.length === 0) {
|
|
19
|
-
console.log('No migrations found.\n');
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// Group by status
|
|
24
|
-
const pending = statuses.filter((s) => s.status === 'pending');
|
|
25
|
-
const applied = statuses.filter((s) => s.status === 'applied');
|
|
26
|
-
const failed = statuses.filter((s) => s.status === 'failed');
|
|
27
|
-
const rolledBack = statuses.filter((s) => s.status === 'rolled_back');
|
|
28
|
-
|
|
29
|
-
// Show applied migrations
|
|
30
|
-
if (applied.length > 0) {
|
|
31
|
-
console.log(`✅ Applied (${applied.length}):`);
|
|
32
|
-
for (const status of applied) {
|
|
33
|
-
const date = status.appliedAt ? new Date(status.appliedAt).toLocaleString() : '';
|
|
34
|
-
console.log(` ${status.version} - ${status.name} (${date})`);
|
|
35
|
-
}
|
|
36
|
-
console.log();
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Show pending migrations
|
|
40
|
-
if (pending.length > 0) {
|
|
41
|
-
console.log(`⏳ Pending (${pending.length}):`);
|
|
42
|
-
for (const status of pending) {
|
|
43
|
-
console.log(` ${status.version} - ${status.name}`);
|
|
44
|
-
}
|
|
45
|
-
console.log();
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Show rolled back migrations
|
|
49
|
-
if (rolledBack.length > 0) {
|
|
50
|
-
console.log(`⬅️ Rolled Back (${rolledBack.length}):`);
|
|
51
|
-
for (const status of rolledBack) {
|
|
52
|
-
console.log(` ${status.version} - ${status.name}`);
|
|
53
|
-
}
|
|
54
|
-
console.log();
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Show failed migrations
|
|
58
|
-
if (failed.length > 0) {
|
|
59
|
-
console.log(`❌ Failed (${failed.length}):`);
|
|
60
|
-
for (const status of failed) {
|
|
61
|
-
console.log(` ${status.version} - ${status.name}`);
|
|
62
|
-
if (status.error) {
|
|
63
|
-
console.log(` Error: ${status.error}`);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
console.log();
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Summary
|
|
70
|
-
console.log(`Total: ${statuses.length} migration(s)\n`);
|
|
71
|
-
} catch (error: any) {
|
|
72
|
-
console.error(`\n❌ Failed to get status: ${error.message}\n`);
|
|
73
|
-
process.exit(1);
|
|
74
|
-
}
|
|
75
|
-
}
|
package/src/commands/up.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { MigrationRunner } from '../core/runner';
|
|
2
|
-
import { createDynamoDBClient } from '../core/client';
|
|
3
|
-
import { MigrationConfig } from '../types';
|
|
4
|
-
|
|
5
|
-
export async function runMigrations(
|
|
6
|
-
config: MigrationConfig,
|
|
7
|
-
limit?: number,
|
|
8
|
-
dryRun: boolean = false
|
|
9
|
-
): Promise<void> {
|
|
10
|
-
const client = createDynamoDBClient(config);
|
|
11
|
-
const runner = new MigrationRunner(client, config);
|
|
12
|
-
|
|
13
|
-
try {
|
|
14
|
-
const executed = await runner.up({ limit, dryRun });
|
|
15
|
-
|
|
16
|
-
if (!dryRun && executed.length > 0) {
|
|
17
|
-
console.log(`\n🎉 Successfully applied ${executed.length} migration(s)`);
|
|
18
|
-
|
|
19
|
-
const currentVersion = await runner.getCurrentVersion();
|
|
20
|
-
console.log(`📌 Current version: ${currentVersion}\n`);
|
|
21
|
-
}
|
|
22
|
-
} catch (error: any) {
|
|
23
|
-
console.error(`\n❌ Migration failed: ${error.message}\n`);
|
|
24
|
-
process.exit(1);
|
|
25
|
-
}
|
|
26
|
-
}
|
package/src/core/client.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
|
|
2
|
-
import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';
|
|
3
|
-
import { MigrationConfig } from '../types';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Create a DynamoDB Document Client from config
|
|
7
|
-
*/
|
|
8
|
-
export function createDynamoDBClient(config: MigrationConfig): DynamoDBDocumentClient {
|
|
9
|
-
const ddbClient = new DynamoDBClient({
|
|
10
|
-
region: config.client.region,
|
|
11
|
-
endpoint: config.client.endpoint,
|
|
12
|
-
credentials: config.client.credentials,
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
return DynamoDBDocumentClient.from(ddbClient, {
|
|
16
|
-
marshallOptions: {
|
|
17
|
-
removeUndefinedValues: true,
|
|
18
|
-
convertClassInstanceToMap: true,
|
|
19
|
-
},
|
|
20
|
-
unmarshallOptions: {
|
|
21
|
-
wrapNumbers: false,
|
|
22
|
-
},
|
|
23
|
-
});
|
|
24
|
-
}
|
package/src/core/config.ts
DELETED
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
import * as fs from 'fs';
|
|
2
|
-
import * as path from 'path';
|
|
3
|
-
import { MigrationConfig } from '../types';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Load migration configuration
|
|
7
|
-
*/
|
|
8
|
-
export class ConfigLoader {
|
|
9
|
-
private configPath: string;
|
|
10
|
-
|
|
11
|
-
constructor(configPath?: string) {
|
|
12
|
-
this.configPath = configPath || this.findConfigFile() || 'dynatable.config.js';
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Find config file in current directory or parent directories
|
|
17
|
-
*/
|
|
18
|
-
private findConfigFile(): string | null {
|
|
19
|
-
const configFileNames = [
|
|
20
|
-
'dynatable.config.js',
|
|
21
|
-
'dynatable.config.json',
|
|
22
|
-
'.dynatablerc.js',
|
|
23
|
-
'.dynatablerc.json',
|
|
24
|
-
];
|
|
25
|
-
|
|
26
|
-
let currentDir = process.cwd();
|
|
27
|
-
const root = path.parse(currentDir).root;
|
|
28
|
-
|
|
29
|
-
while (currentDir !== root) {
|
|
30
|
-
for (const fileName of configFileNames) {
|
|
31
|
-
const filePath = path.join(currentDir, fileName);
|
|
32
|
-
if (fs.existsSync(filePath)) {
|
|
33
|
-
return filePath;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
currentDir = path.dirname(currentDir);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return null;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Load configuration from file
|
|
45
|
-
*/
|
|
46
|
-
async load(): Promise<MigrationConfig> {
|
|
47
|
-
if (!fs.existsSync(this.configPath)) {
|
|
48
|
-
throw new Error(
|
|
49
|
-
`Configuration file not found: ${this.configPath}\n\nPlease create a dynatable.config.js file in your project root.`
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
try {
|
|
54
|
-
let config: MigrationConfig;
|
|
55
|
-
|
|
56
|
-
if (this.configPath.endsWith('.json')) {
|
|
57
|
-
const content = fs.readFileSync(this.configPath, 'utf-8');
|
|
58
|
-
config = JSON.parse(content);
|
|
59
|
-
} else {
|
|
60
|
-
// JavaScript config file
|
|
61
|
-
const module = await import(this.configPath);
|
|
62
|
-
config = module.default || module;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return this.validateAndNormalizeConfig(config);
|
|
66
|
-
} catch (error: any) {
|
|
67
|
-
throw new Error(`Failed to load configuration: ${error.message}`);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Validate and normalize configuration
|
|
73
|
-
*/
|
|
74
|
-
private validateAndNormalizeConfig(config: Partial<MigrationConfig>): MigrationConfig {
|
|
75
|
-
if (!config.tableName) {
|
|
76
|
-
throw new Error("Configuration error: 'tableName' is required");
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (!config.client?.region) {
|
|
80
|
-
throw new Error("Configuration error: 'client.region' is required");
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return {
|
|
84
|
-
tableName: config.tableName,
|
|
85
|
-
client: {
|
|
86
|
-
region: config.client.region,
|
|
87
|
-
endpoint: config.client.endpoint,
|
|
88
|
-
credentials: config.client.credentials,
|
|
89
|
-
},
|
|
90
|
-
migrationsDir: config.migrationsDir || './migrations',
|
|
91
|
-
trackingPrefix: config.trackingPrefix || '_SCHEMA#VERSION',
|
|
92
|
-
gsi1Name: config.gsi1Name || 'GSI1',
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Create default config file
|
|
98
|
-
*/
|
|
99
|
-
static createDefaultConfig(targetPath: string): void {
|
|
100
|
-
const defaultConfig = `module.exports = {
|
|
101
|
-
// DynamoDB table name
|
|
102
|
-
tableName: "MyTable",
|
|
103
|
-
|
|
104
|
-
// DynamoDB client configuration
|
|
105
|
-
client: {
|
|
106
|
-
region: "us-east-1",
|
|
107
|
-
|
|
108
|
-
// Optional: For local DynamoDB
|
|
109
|
-
endpoint: "http://localhost:8000",
|
|
110
|
-
|
|
111
|
-
// Optional: Credentials (use AWS_PROFILE or IAM role in production)
|
|
112
|
-
credentials: {
|
|
113
|
-
accessKeyId: "local",
|
|
114
|
-
secretAccessKey: "local",
|
|
115
|
-
},
|
|
116
|
-
},
|
|
117
|
-
|
|
118
|
-
// Optional: Migrations directory (default: ./migrations)
|
|
119
|
-
migrationsDir: "./migrations",
|
|
120
|
-
|
|
121
|
-
// Optional: Tracking prefix in single table (default: _SCHEMA#VERSION)
|
|
122
|
-
trackingPrefix: "_SCHEMA#VERSION",
|
|
123
|
-
|
|
124
|
-
// Optional: GSI name for tracking (default: GSI1)
|
|
125
|
-
gsi1Name: "GSI1",
|
|
126
|
-
};
|
|
127
|
-
`;
|
|
128
|
-
|
|
129
|
-
fs.writeFileSync(targetPath, defaultConfig, 'utf-8');
|
|
130
|
-
console.log(`✅ Created config file: ${targetPath}`);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Get config from environment or file
|
|
136
|
-
*/
|
|
137
|
-
export async function loadConfig(configPath?: string): Promise<MigrationConfig> {
|
|
138
|
-
const loader = new ConfigLoader(configPath);
|
|
139
|
-
return loader.load();
|
|
140
|
-
}
|
package/src/core/errors.ts
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Thrown by `markAsApplied` when the version already has a tracking record
|
|
3
|
-
* in a non-applied state (e.g. `failed`, `rolled_back`) and therefore
|
|
4
|
-
* cannot be silently treated as an idempotent re-apply. Surfaces the
|
|
5
|
-
* existing state so the caller can decide whether to retry, recover, or
|
|
6
|
-
* roll back.
|
|
7
|
-
*/
|
|
8
|
-
export class MigrationAlreadyAppliedError extends Error {
|
|
9
|
-
public readonly version: string;
|
|
10
|
-
public readonly currentStatus: string | undefined;
|
|
11
|
-
|
|
12
|
-
constructor(version: string, currentStatus: string | undefined) {
|
|
13
|
-
super(
|
|
14
|
-
`Migration "${version}" already has a tracking record in state ` +
|
|
15
|
-
`"${currentStatus ?? 'unknown'}" — cannot mark as applied. ` +
|
|
16
|
-
`If you want to re-apply this version, roll it back first ` +
|
|
17
|
-
`(or recover the failed run) and try again.`
|
|
18
|
-
);
|
|
19
|
-
this.name = 'MigrationAlreadyAppliedError';
|
|
20
|
-
this.version = version;
|
|
21
|
-
this.currentStatus = currentStatus;
|
|
22
|
-
if (typeof (Error as unknown as { captureStackTrace?: unknown }).captureStackTrace === 'function') {
|
|
23
|
-
(
|
|
24
|
-
Error as unknown as { captureStackTrace: (target: object, ctor: unknown) => void }
|
|
25
|
-
).captureStackTrace(this, MigrationAlreadyAppliedError);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Thrown when a tracker write is cancelled because the migration lock was
|
|
32
|
-
* taken by another process between `acquireLock()` and the write itself
|
|
33
|
-
* (e.g. the original lock TTL expired and a second worker took over).
|
|
34
|
-
*
|
|
35
|
-
* The transaction was rolled back atomically — no partial state was
|
|
36
|
-
* written. The safe action is to stop the current `up()` / `down()` run.
|
|
37
|
-
*/
|
|
38
|
-
export class MigrationLockLostError extends Error {
|
|
39
|
-
public readonly version: string;
|
|
40
|
-
|
|
41
|
-
constructor(version: string) {
|
|
42
|
-
super(
|
|
43
|
-
`Migration lock was lost during markAsApplied("${version}"). ` +
|
|
44
|
-
`Another worker may have taken over. The transaction was rolled ` +
|
|
45
|
-
`back atomically; no partial state was written.`
|
|
46
|
-
);
|
|
47
|
-
this.name = 'MigrationLockLostError';
|
|
48
|
-
this.version = version;
|
|
49
|
-
if (typeof (Error as unknown as { captureStackTrace?: unknown }).captureStackTrace === 'function') {
|
|
50
|
-
(
|
|
51
|
-
Error as unknown as { captureStackTrace: (target: object, ctor: unknown) => void }
|
|
52
|
-
).captureStackTrace(this, MigrationLockLostError);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|