@ftschopp/dynatable-migrations 1.0.0
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/.gitkeep +0 -0
- package/CHANGELOG.md +13 -0
- package/README.md +421 -0
- package/USAGE.md +557 -0
- package/bin/dynatable-migrate.js +30 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +104 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/create.d.ts +2 -0
- package/dist/commands/create.d.ts.map +1 -0
- package/dist/commands/create.js +123 -0
- package/dist/commands/create.js.map +1 -0
- package/dist/commands/down.d.ts +3 -0
- package/dist/commands/down.d.ts.map +1 -0
- package/dist/commands/down.js +37 -0
- package/dist/commands/down.js.map +1 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +70 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/status.d.ts +3 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +84 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/up.d.ts +3 -0
- package/dist/commands/up.d.ts.map +1 -0
- package/dist/commands/up.js +37 -0
- package/dist/commands/up.js.map +1 -0
- package/dist/core/config.d.ts +29 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +160 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/loader.d.ts +47 -0
- package/dist/core/loader.d.ts.map +1 -0
- package/dist/core/loader.js +226 -0
- package/dist/core/loader.js.map +1 -0
- package/dist/core/runner.d.ts +38 -0
- package/dist/core/runner.d.ts.map +1 -0
- package/dist/core/runner.js +166 -0
- package/dist/core/runner.js.map +1 -0
- package/dist/core/tracker.d.ts +19 -0
- package/dist/core/tracker.d.ts.map +1 -0
- package/dist/core/tracker.js +172 -0
- package/dist/core/tracker.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +48 -0
- package/dist/index.js.map +1 -0
- package/dist/templates/migration.d.ts +5 -0
- package/dist/templates/migration.d.ts.map +1 -0
- package/dist/templates/migration.js +96 -0
- package/dist/templates/migration.js.map +1 -0
- package/dist/types/index.d.ts +159 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -0
- package/migrations/.gitkeep +0 -0
- package/package.json +43 -0
- package/src/cli.ts +106 -0
- package/src/commands/create.ts +110 -0
- package/src/commands/down.ts +42 -0
- package/src/commands/init.ts +37 -0
- package/src/commands/status.ts +92 -0
- package/src/commands/up.ts +39 -0
- package/src/core/config.ts +140 -0
- package/src/core/loader.ts +233 -0
- package/src/core/runner.ts +223 -0
- package/src/core/tracker.ts +219 -0
- package/src/index.ts +23 -0
- package/src/templates/migration.ts +92 -0
- package/src/types/index.ts +196 -0
- package/tsconfig.json +19 -0
package/USAGE.md
ADDED
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
# Usage Guide - @ftschopp/dynatable-migrations
|
|
2
|
+
|
|
3
|
+
## Quick Start Guide
|
|
4
|
+
|
|
5
|
+
### 1. Install in your project
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @ftschopp/dynatable-migrations
|
|
9
|
+
# or
|
|
10
|
+
yarn add @ftschopp/dynatable-migrations
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### 2. Initialize migrations
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx dynatable-migrate init
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
This creates:
|
|
20
|
+
|
|
21
|
+
- `migrations/` directory
|
|
22
|
+
- `dynatable.config.js` configuration file
|
|
23
|
+
|
|
24
|
+
### 3. Configure DynamoDB connection
|
|
25
|
+
|
|
26
|
+
Edit `dynatable.config.js`:
|
|
27
|
+
|
|
28
|
+
**For Local DynamoDB:**
|
|
29
|
+
|
|
30
|
+
```javascript
|
|
31
|
+
module.exports = {
|
|
32
|
+
tableName: 'MyTable',
|
|
33
|
+
client: {
|
|
34
|
+
region: 'us-east-1',
|
|
35
|
+
endpoint: 'http://localhost:8000',
|
|
36
|
+
credentials: {
|
|
37
|
+
accessKeyId: 'local',
|
|
38
|
+
secretAccessKey: 'local',
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**For AWS DynamoDB:**
|
|
45
|
+
|
|
46
|
+
```javascript
|
|
47
|
+
module.exports = {
|
|
48
|
+
tableName: 'MyTable',
|
|
49
|
+
client: {
|
|
50
|
+
region: 'us-east-1',
|
|
51
|
+
// Uses AWS_PROFILE or IAM role automatically
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 4. Create your first migration
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npx dynatable-migrate create add_user_email
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Creates: `migrations/v0001_add_user_email.ts`
|
|
63
|
+
|
|
64
|
+
### 5. Edit the migration
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
import { Migration } from '@ftschopp/dynatable-migrations';
|
|
68
|
+
|
|
69
|
+
export const migration: Migration = {
|
|
70
|
+
version: 'v0001',
|
|
71
|
+
name: 'add_user_email',
|
|
72
|
+
|
|
73
|
+
async up({ client, tableName, tracker }) {
|
|
74
|
+
// Your migration code here
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
async down({ client, tableName, dynamodb }) {
|
|
78
|
+
// Rollback code here
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### 6. Run migrations
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# Check status first
|
|
87
|
+
npx dynatable-migrate status
|
|
88
|
+
|
|
89
|
+
# Apply all pending migrations
|
|
90
|
+
npx dynatable-migrate up
|
|
91
|
+
|
|
92
|
+
# Rollback last migration
|
|
93
|
+
npx dynatable-migrate down
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Common Migration Patterns
|
|
97
|
+
|
|
98
|
+
### Pattern 1: Add Field to Existing Items
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
export const migration: Migration = {
|
|
102
|
+
version: 'v0001',
|
|
103
|
+
name: 'add_user_bio',
|
|
104
|
+
|
|
105
|
+
async up({ client, tableName }) {
|
|
106
|
+
const { ScanCommand, UpdateCommand } = dynamodb;
|
|
107
|
+
|
|
108
|
+
// Find all users
|
|
109
|
+
const result = await client.send(
|
|
110
|
+
new ScanCommand({
|
|
111
|
+
TableName: tableName,
|
|
112
|
+
FilterExpression: 'begins_with(PK, :pk)',
|
|
113
|
+
ExpressionAttributeValues: { ':pk': 'USER#' },
|
|
114
|
+
})
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
// Add bio field with default value
|
|
118
|
+
for (const user of result.Items || []) {
|
|
119
|
+
await client.send(
|
|
120
|
+
new UpdateCommand({
|
|
121
|
+
TableName: tableName,
|
|
122
|
+
Key: { PK: user.PK, SK: user.SK },
|
|
123
|
+
UpdateExpression: 'SET bio = :bio',
|
|
124
|
+
ExpressionAttributeValues: { ':bio': '' },
|
|
125
|
+
})
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
async down({ client, tableName, dynamodb }) {
|
|
131
|
+
// Remove the field
|
|
132
|
+
const { ScanCommand, UpdateCommand } = dynamodb;
|
|
133
|
+
|
|
134
|
+
const result = await client.send(
|
|
135
|
+
new ScanCommand({
|
|
136
|
+
TableName: tableName,
|
|
137
|
+
FilterExpression: 'begins_with(PK, :pk)',
|
|
138
|
+
ExpressionAttributeValues: { ':pk': 'USER#' },
|
|
139
|
+
})
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
for (const user of result.Items || []) {
|
|
143
|
+
await client.send(
|
|
144
|
+
new UpdateCommand({
|
|
145
|
+
TableName: tableName,
|
|
146
|
+
Key: { PK: user.PK, SK: user.SK },
|
|
147
|
+
UpdateExpression: 'REMOVE bio',
|
|
148
|
+
})
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Pattern 2: Transform Data
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
export const migration: Migration = {
|
|
159
|
+
version: 'v0002',
|
|
160
|
+
name: 'normalize_usernames',
|
|
161
|
+
|
|
162
|
+
async up({ client, tableName }) {
|
|
163
|
+
const { ScanCommand, UpdateCommand } = dynamodb;
|
|
164
|
+
|
|
165
|
+
const result = await client.send(
|
|
166
|
+
new ScanCommand({
|
|
167
|
+
TableName: tableName,
|
|
168
|
+
FilterExpression: 'begins_with(PK, :pk)',
|
|
169
|
+
ExpressionAttributeValues: { ':pk': 'USER#' },
|
|
170
|
+
})
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
for (const user of result.Items || []) {
|
|
174
|
+
const normalizedUsername = user.username.toLowerCase();
|
|
175
|
+
|
|
176
|
+
if (user.username !== normalizedUsername) {
|
|
177
|
+
await client.send(
|
|
178
|
+
new UpdateCommand({
|
|
179
|
+
TableName: tableName,
|
|
180
|
+
Key: { PK: user.PK, SK: user.SK },
|
|
181
|
+
UpdateExpression: 'SET username = :username',
|
|
182
|
+
ExpressionAttributeValues: { ':username': normalizedUsername },
|
|
183
|
+
})
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
|
|
189
|
+
async down({ client, tableName, dynamodb }) {
|
|
190
|
+
console.log('Cannot revert username normalization');
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Pattern 3: Change Key Structure (Advanced)
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
export const migration: Migration = {
|
|
199
|
+
version: 'v0003',
|
|
200
|
+
name: 'change_photo_sort_key',
|
|
201
|
+
|
|
202
|
+
async up({ client, tableName }) {
|
|
203
|
+
const { ScanCommand, TransactWriteCommand } = dynamodb;
|
|
204
|
+
|
|
205
|
+
const result = await client.send(
|
|
206
|
+
new ScanCommand({
|
|
207
|
+
TableName: tableName,
|
|
208
|
+
FilterExpression: 'begins_with(SK, :sk)',
|
|
209
|
+
ExpressionAttributeValues: { ':sk': 'PHOTO#' },
|
|
210
|
+
})
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
// Batch process to avoid rate limits
|
|
214
|
+
const batchSize = 25; // DynamoDB transaction limit
|
|
215
|
+
const items = result.Items || [];
|
|
216
|
+
|
|
217
|
+
for (let i = 0; i < items.length; i += batchSize) {
|
|
218
|
+
const batch = items.slice(i, i + batchSize);
|
|
219
|
+
const transactItems = [];
|
|
220
|
+
|
|
221
|
+
for (const photo of batch) {
|
|
222
|
+
const timestamp = new Date(photo.createdAt).getTime();
|
|
223
|
+
const photoId = photo.SK.replace('PHOTO#', '');
|
|
224
|
+
|
|
225
|
+
transactItems.push(
|
|
226
|
+
{
|
|
227
|
+
Delete: {
|
|
228
|
+
TableName: tableName,
|
|
229
|
+
Key: { PK: photo.PK, SK: photo.SK },
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
Put: {
|
|
234
|
+
TableName: tableName,
|
|
235
|
+
Item: {
|
|
236
|
+
...photo,
|
|
237
|
+
SK: `PHOTO#${timestamp}#${photoId}`,
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
}
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
await client.send(
|
|
245
|
+
new TransactWriteCommand({
|
|
246
|
+
TransactItems: transactItems,
|
|
247
|
+
})
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
|
|
252
|
+
async down({ client, tableName, dynamodb }) {
|
|
253
|
+
// Similar logic but reverse
|
|
254
|
+
},
|
|
255
|
+
};
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Pattern 4: Add New Entity Type (No Data Migration)
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
export const migration: Migration = {
|
|
262
|
+
version: 'v0004',
|
|
263
|
+
name: 'add_notification_entity',
|
|
264
|
+
|
|
265
|
+
schema: {
|
|
266
|
+
Notification: {
|
|
267
|
+
key: {
|
|
268
|
+
PK: 'USER#${userId}',
|
|
269
|
+
SK: 'NOTIFICATION#${notificationId}',
|
|
270
|
+
},
|
|
271
|
+
attributes: {
|
|
272
|
+
userId: { type: String, required: true },
|
|
273
|
+
notificationId: { type: String, generate: 'ulid' },
|
|
274
|
+
message: { type: String, required: true },
|
|
275
|
+
read: { type: Boolean, default: false },
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
},
|
|
279
|
+
|
|
280
|
+
async up({ tracker }) {
|
|
281
|
+
// In Single Table Design, adding a new entity type
|
|
282
|
+
// doesn't require data migration, just schema documentation
|
|
283
|
+
await tracker.recordSchemaChange({
|
|
284
|
+
entity: 'Notification',
|
|
285
|
+
changes: {
|
|
286
|
+
added: ['userId', 'notificationId', 'message', 'read'],
|
|
287
|
+
},
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
console.log('✅ Notification entity added to schema');
|
|
291
|
+
},
|
|
292
|
+
|
|
293
|
+
async down({ client, tableName, dynamodb }) {
|
|
294
|
+
// Delete all notifications if rolling back
|
|
295
|
+
const { ScanCommand, DeleteCommand } = dynamodb;
|
|
296
|
+
|
|
297
|
+
const result = await client.send(
|
|
298
|
+
new ScanCommand({
|
|
299
|
+
TableName: tableName,
|
|
300
|
+
FilterExpression: 'begins_with(SK, :sk)',
|
|
301
|
+
ExpressionAttributeValues: { ':sk': 'NOTIFICATION#' },
|
|
302
|
+
})
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
for (const notification of result.Items || []) {
|
|
306
|
+
await client.send(
|
|
307
|
+
new DeleteCommand({
|
|
308
|
+
TableName: tableName,
|
|
309
|
+
Key: { PK: notification.PK, SK: notification.SK },
|
|
310
|
+
})
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
},
|
|
314
|
+
};
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
## CLI Commands Reference
|
|
318
|
+
|
|
319
|
+
### `dynatable-migrate init`
|
|
320
|
+
|
|
321
|
+
Initialize migrations in current project.
|
|
322
|
+
|
|
323
|
+
### `dynatable-migrate create <name>`
|
|
324
|
+
|
|
325
|
+
Create a new migration file.
|
|
326
|
+
|
|
327
|
+
**Options:**
|
|
328
|
+
|
|
329
|
+
- `-c, --config <path>` - Custom config file path
|
|
330
|
+
|
|
331
|
+
**Example:**
|
|
332
|
+
|
|
333
|
+
```bash
|
|
334
|
+
dynatable-migrate create add_user_profile
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### `dynatable-migrate up`
|
|
338
|
+
|
|
339
|
+
Run all pending migrations.
|
|
340
|
+
|
|
341
|
+
**Options:**
|
|
342
|
+
|
|
343
|
+
- `-c, --config <path>` - Custom config file path
|
|
344
|
+
- `-l, --limit <number>` - Limit migrations to run
|
|
345
|
+
|
|
346
|
+
**Examples:**
|
|
347
|
+
|
|
348
|
+
```bash
|
|
349
|
+
# Run all pending
|
|
350
|
+
dynatable-migrate up
|
|
351
|
+
|
|
352
|
+
# Run only next migration
|
|
353
|
+
dynatable-migrate up --limit 1
|
|
354
|
+
|
|
355
|
+
# Use custom config
|
|
356
|
+
dynatable-migrate up --config ./my-config.js
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### `dynatable-migrate down`
|
|
360
|
+
|
|
361
|
+
Rollback migrations.
|
|
362
|
+
|
|
363
|
+
**Options:**
|
|
364
|
+
|
|
365
|
+
- `-c, --config <path>` - Custom config file path
|
|
366
|
+
- `-s, --steps <number>` - Number of migrations to rollback (default: 1)
|
|
367
|
+
|
|
368
|
+
**Examples:**
|
|
369
|
+
|
|
370
|
+
```bash
|
|
371
|
+
# Rollback last migration
|
|
372
|
+
dynatable-migrate down
|
|
373
|
+
|
|
374
|
+
# Rollback last 3 migrations
|
|
375
|
+
dynatable-migrate down --steps 3
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
### `dynatable-migrate status`
|
|
379
|
+
|
|
380
|
+
Show migration status.
|
|
381
|
+
|
|
382
|
+
**Example:**
|
|
383
|
+
|
|
384
|
+
```bash
|
|
385
|
+
dynatable-migrate status
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
**Output:**
|
|
389
|
+
|
|
390
|
+
```
|
|
391
|
+
📊 Migration Status
|
|
392
|
+
|
|
393
|
+
Table: InstagramClone
|
|
394
|
+
Current version: v0002
|
|
395
|
+
Migrations directory: ./migrations
|
|
396
|
+
|
|
397
|
+
✅ Applied (2):
|
|
398
|
+
v0001 - add_user_email (2025-03-29 10:00:00)
|
|
399
|
+
v0002 - normalize_usernames (2025-03-29 11:00:00)
|
|
400
|
+
|
|
401
|
+
⏳ Pending (1):
|
|
402
|
+
v0003 - change_photo_sort_key
|
|
403
|
+
|
|
404
|
+
Total: 3 migration(s)
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
## Integration with package.json
|
|
408
|
+
|
|
409
|
+
Add scripts to your `package.json`:
|
|
410
|
+
|
|
411
|
+
```json
|
|
412
|
+
{
|
|
413
|
+
"scripts": {
|
|
414
|
+
"migrate": "dynatable-migrate",
|
|
415
|
+
"migrate:create": "dynatable-migrate create",
|
|
416
|
+
"migrate:up": "dynatable-migrate up",
|
|
417
|
+
"migrate:down": "dynatable-migrate down",
|
|
418
|
+
"migrate:status": "dynatable-migrate status"
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
Then use:
|
|
424
|
+
|
|
425
|
+
```bash
|
|
426
|
+
npm run migrate:status
|
|
427
|
+
npm run migrate:create add_feature
|
|
428
|
+
npm run migrate:up
|
|
429
|
+
npm run migrate:down
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
## Best Practices
|
|
433
|
+
|
|
434
|
+
### 1. Always Write Down Functions
|
|
435
|
+
|
|
436
|
+
Even if you think you won't need to rollback, always implement the `down()` function.
|
|
437
|
+
|
|
438
|
+
### 2. Test Locally First
|
|
439
|
+
|
|
440
|
+
Use DynamoDB Local to test migrations before running on production:
|
|
441
|
+
|
|
442
|
+
```bash
|
|
443
|
+
docker run -p 8000:8000 amazon/dynamodb-local
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
### 3. Backup Before Running
|
|
447
|
+
|
|
448
|
+
Enable point-in-time recovery on your production tables.
|
|
449
|
+
|
|
450
|
+
### 4. Keep Migrations Focused
|
|
451
|
+
|
|
452
|
+
One migration = one change. Don't combine multiple unrelated changes.
|
|
453
|
+
|
|
454
|
+
### 5. Never Modify Applied Migrations
|
|
455
|
+
|
|
456
|
+
Once a migration is applied, don't change it. Create a new migration instead.
|
|
457
|
+
|
|
458
|
+
### 6. Use Transactions for Atomic Operations
|
|
459
|
+
|
|
460
|
+
For multi-step changes that must succeed or fail together:
|
|
461
|
+
|
|
462
|
+
```typescript
|
|
463
|
+
await client.send(new TransactWriteCommand({
|
|
464
|
+
TransactItems: [
|
|
465
|
+
{ Put: { ... } },
|
|
466
|
+
{ Update: { ... } },
|
|
467
|
+
{ Delete: { ... } }
|
|
468
|
+
]
|
|
469
|
+
}));
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
### 7. Handle Large Datasets Carefully
|
|
473
|
+
|
|
474
|
+
For tables with many items:
|
|
475
|
+
|
|
476
|
+
- Process in batches
|
|
477
|
+
- Add delays between batches to avoid throttling
|
|
478
|
+
- Consider using DynamoDB Streams for background processing
|
|
479
|
+
|
|
480
|
+
### 8. Document Your Changes
|
|
481
|
+
|
|
482
|
+
Use the `schema` field to document what changed:
|
|
483
|
+
|
|
484
|
+
```typescript
|
|
485
|
+
export const migration: Migration = {
|
|
486
|
+
schema: {
|
|
487
|
+
User: {
|
|
488
|
+
attributes: {
|
|
489
|
+
// ... document your schema here
|
|
490
|
+
},
|
|
491
|
+
},
|
|
492
|
+
},
|
|
493
|
+
// ...
|
|
494
|
+
};
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
## Troubleshooting
|
|
498
|
+
|
|
499
|
+
### Migration Failed Mid-Execution
|
|
500
|
+
|
|
501
|
+
If a migration fails partway through:
|
|
502
|
+
|
|
503
|
+
1. Check the error message
|
|
504
|
+
2. Fix the issue manually in DynamoDB if needed
|
|
505
|
+
3. Fix the migration code
|
|
506
|
+
4. The migration will be marked as "failed" - you may need to manually update the status or create a fix migration
|
|
507
|
+
|
|
508
|
+
### Can't Find Migration File
|
|
509
|
+
|
|
510
|
+
Make sure:
|
|
511
|
+
|
|
512
|
+
- Migration file follows naming convention: `v0001_name.ts`
|
|
513
|
+
- File is in the `migrationsDir` specified in config
|
|
514
|
+
- File exports a `migration` object
|
|
515
|
+
|
|
516
|
+
### Permission Errors
|
|
517
|
+
|
|
518
|
+
Ensure your AWS credentials have permissions for:
|
|
519
|
+
|
|
520
|
+
- `dynamodb:Scan`
|
|
521
|
+
- `dynamodb:Query`
|
|
522
|
+
- `dynamodb:GetItem`
|
|
523
|
+
- `dynamodb:PutItem`
|
|
524
|
+
- `dynamodb:UpdateItem`
|
|
525
|
+
- `dynamodb:DeleteItem`
|
|
526
|
+
|
|
527
|
+
## Advanced: Programmatic Usage
|
|
528
|
+
|
|
529
|
+
You can also use the migration runner in your code:
|
|
530
|
+
|
|
531
|
+
```typescript
|
|
532
|
+
import { MigrationRunner, loadConfig } from '@ftschopp/dynatable-migrations';
|
|
533
|
+
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
|
|
534
|
+
import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';
|
|
535
|
+
|
|
536
|
+
async function runMigrations() {
|
|
537
|
+
const config = await loadConfig();
|
|
538
|
+
|
|
539
|
+
const ddbClient = new DynamoDBClient({
|
|
540
|
+
region: config.client.region,
|
|
541
|
+
endpoint: config.client.endpoint,
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
const client = DynamoDBDocumentClient.from(ddbClient);
|
|
545
|
+
const runner = new MigrationRunner(client, config);
|
|
546
|
+
|
|
547
|
+
// Get status
|
|
548
|
+
const status = await runner.status();
|
|
549
|
+
console.log('Migration status:', status);
|
|
550
|
+
|
|
551
|
+
// Run migrations
|
|
552
|
+
await runner.up();
|
|
553
|
+
|
|
554
|
+
// Rollback
|
|
555
|
+
await runner.down(1);
|
|
556
|
+
}
|
|
557
|
+
```
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* DynamoDB Migration CLI
|
|
5
|
+
* Executable entry point
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Register ts-node for TypeScript support in development
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
|
|
12
|
+
// Check if we're running from source (development) or compiled (production)
|
|
13
|
+
const sourceFile = path.join(__dirname, '../src/cli.ts');
|
|
14
|
+
const compiledFile = path.join(__dirname, '../dist/cli.js');
|
|
15
|
+
|
|
16
|
+
if (fs.existsSync(compiledFile)) {
|
|
17
|
+
// Production: use compiled JavaScript
|
|
18
|
+
require(compiledFile);
|
|
19
|
+
} else if (fs.existsSync(sourceFile)) {
|
|
20
|
+
// Development: use ts-node to run TypeScript directly
|
|
21
|
+
require('ts-node').register({
|
|
22
|
+
project: path.join(__dirname, '../tsconfig.json'),
|
|
23
|
+
transpileOnly: true,
|
|
24
|
+
});
|
|
25
|
+
require(sourceFile);
|
|
26
|
+
} else {
|
|
27
|
+
console.error('Error: CLI files not found. Please build the project first.');
|
|
28
|
+
console.error('Run: npm run build');
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const commander_1 = require("commander");
|
|
5
|
+
const config_1 = require("./core/config");
|
|
6
|
+
const create_1 = require("./commands/create");
|
|
7
|
+
const up_1 = require("./commands/up");
|
|
8
|
+
const down_1 = require("./commands/down");
|
|
9
|
+
const status_1 = require("./commands/status");
|
|
10
|
+
const init_1 = require("./commands/init");
|
|
11
|
+
const program = new commander_1.Command();
|
|
12
|
+
program
|
|
13
|
+
.name('dynatable-migrate')
|
|
14
|
+
.description('DynamoDB migration tool for single table design')
|
|
15
|
+
.version('0.1.0');
|
|
16
|
+
// Init command
|
|
17
|
+
program
|
|
18
|
+
.command('init')
|
|
19
|
+
.description('Initialize migrations in current project')
|
|
20
|
+
.action(async () => {
|
|
21
|
+
try {
|
|
22
|
+
await (0, init_1.initProject)();
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
console.error(`Error: ${error.message}`);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
// Create command
|
|
30
|
+
program
|
|
31
|
+
.command('create <name>')
|
|
32
|
+
.description('Create a new migration file')
|
|
33
|
+
.option('-c, --config <path>', 'Path to config file')
|
|
34
|
+
.option('-t, --type <type>', 'Version bump type: major, minor, or patch (default: patch)')
|
|
35
|
+
.option('-e, --explicit <version>', 'Explicit version (e.g., 2.0.0)')
|
|
36
|
+
.action(async (name, options) => {
|
|
37
|
+
try {
|
|
38
|
+
let migrationsDir = './migrations';
|
|
39
|
+
// Try to load config to get migrations directory
|
|
40
|
+
try {
|
|
41
|
+
const config = await (0, config_1.loadConfig)(options.config);
|
|
42
|
+
migrationsDir = config.migrationsDir || './migrations';
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
// If config doesn't exist, use default
|
|
46
|
+
}
|
|
47
|
+
await (0, create_1.createMigration)(name, migrationsDir, options.type, options.explicit);
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
console.error(`Error: ${error.message}`);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
// Up command
|
|
55
|
+
program
|
|
56
|
+
.command('up')
|
|
57
|
+
.description('Run pending migrations')
|
|
58
|
+
.option('-c, --config <path>', 'Path to config file')
|
|
59
|
+
.option('-l, --limit <number>', 'Limit number of migrations to run')
|
|
60
|
+
.action(async (options) => {
|
|
61
|
+
try {
|
|
62
|
+
const config = await (0, config_1.loadConfig)(options.config);
|
|
63
|
+
const limit = options.limit ? parseInt(options.limit, 10) : undefined;
|
|
64
|
+
await (0, up_1.runMigrations)(config, limit);
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
console.error(`Error: ${error.message}`);
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
// Down command
|
|
72
|
+
program
|
|
73
|
+
.command('down')
|
|
74
|
+
.description('Rollback migrations')
|
|
75
|
+
.option('-c, --config <path>', 'Path to config file')
|
|
76
|
+
.option('-s, --steps <number>', 'Number of migrations to rollback', '1')
|
|
77
|
+
.action(async (options) => {
|
|
78
|
+
try {
|
|
79
|
+
const config = await (0, config_1.loadConfig)(options.config);
|
|
80
|
+
const steps = parseInt(options.steps, 10);
|
|
81
|
+
await (0, down_1.rollbackMigrations)(config, steps);
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
console.error(`Error: ${error.message}`);
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
// Status command
|
|
89
|
+
program
|
|
90
|
+
.command('status')
|
|
91
|
+
.description('Show migration status')
|
|
92
|
+
.option('-c, --config <path>', 'Path to config file')
|
|
93
|
+
.action(async (options) => {
|
|
94
|
+
try {
|
|
95
|
+
const config = await (0, config_1.loadConfig)(options.config);
|
|
96
|
+
await (0, status_1.showStatus)(config);
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
console.error(`Error: ${error.message}`);
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
program.parse(process.argv);
|
|
104
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";;;AAEA,yCAAoC;AACpC,0CAA2C;AAC3C,8CAAoD;AACpD,sCAA8C;AAC9C,0CAAqD;AACrD,8CAA+C;AAC/C,0CAA8C;AAE9C,MAAM,OAAO,GAAG,IAAI,mBAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,mBAAmB,CAAC;KACzB,WAAW,CAAC,iDAAiD,CAAC;KAC9D,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,eAAe;AACf,OAAO;KACJ,OAAO,CAAC,MAAM,CAAC;KACf,WAAW,CAAC,0CAA0C,CAAC;KACvD,MAAM,CAAC,KAAK,IAAI,EAAE;IACjB,IAAI,CAAC;QACH,MAAM,IAAA,kBAAW,GAAE,CAAC;IACtB,CAAC;IAAC,OAAO,KAAU,EAAE,CAAC;QACpB,OAAO,CAAC,KAAK,CAAC,UAAU,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QACzC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC,CAAC,CAAC;AAEL,iBAAiB;AACjB,OAAO;KACJ,OAAO,CAAC,eAAe,CAAC;KACxB,WAAW,CAAC,6BAA6B,CAAC;KAC1C,MAAM,CAAC,qBAAqB,EAAE,qBAAqB,CAAC;KACpD,MAAM,CAAC,mBAAmB,EAAE,4DAA4D,CAAC;KACzF,MAAM,CAAC,0BAA0B,EAAE,gCAAgC,CAAC;KACpE,MAAM,CAAC,KAAK,EAAE,IAAY,EAAE,OAAO,EAAE,EAAE;IACtC,IAAI,CAAC;QACH,IAAI,aAAa,GAAG,cAAc,CAAC;QAEnC,iDAAiD;QACjD,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAA,mBAAU,EAAC,OAAO,CAAC,MAAM,CAAC,CAAC;YAChD,aAAa,GAAG,MAAM,CAAC,aAAa,IAAI,cAAc,CAAC;QACzD,CAAC;QAAC,MAAM,CAAC;YACP,uCAAuC;QACzC,CAAC;QAED,MAAM,IAAA,wBAAe,EAAC,IAAI,EAAE,aAAa,EAAE,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC7E,CAAC;IAAC,OAAO,KAAU,EAAE,CAAC;QACpB,OAAO,CAAC,KAAK,CAAC,UAAU,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QACzC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC,CAAC,CAAC;AAEL,aAAa;AACb,OAAO;KACJ,OAAO,CAAC,IAAI,CAAC;KACb,WAAW,CAAC,wBAAwB,CAAC;KACrC,MAAM,CAAC,qBAAqB,EAAE,qBAAqB,CAAC;KACpD,MAAM,CAAC,sBAAsB,EAAE,mCAAmC,CAAC;KACnE,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;IACxB,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,IAAA,mBAAU,EAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAChD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACtE,MAAM,IAAA,kBAAa,EAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACrC,CAAC;IAAC,OAAO,KAAU,EAAE,CAAC;QACpB,OAAO,CAAC,KAAK,CAAC,UAAU,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QACzC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC,CAAC,CAAC;AAEL,eAAe;AACf,OAAO;KACJ,OAAO,CAAC,MAAM,CAAC;KACf,WAAW,CAAC,qBAAqB,CAAC;KAClC,MAAM,CAAC,qBAAqB,EAAE,qBAAqB,CAAC;KACpD,MAAM,CAAC,sBAAsB,EAAE,kCAAkC,EAAE,GAAG,CAAC;KACvE,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;IACxB,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,IAAA,mBAAU,EAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAChD,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QAC1C,MAAM,IAAA,yBAAkB,EAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAC1C,CAAC;IAAC,OAAO,KAAU,EAAE,CAAC;QACpB,OAAO,CAAC,KAAK,CAAC,UAAU,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QACzC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC,CAAC,CAAC;AAEL,iBAAiB;AACjB,OAAO;KACJ,OAAO,CAAC,QAAQ,CAAC;KACjB,WAAW,CAAC,uBAAuB,CAAC;KACpC,MAAM,CAAC,qBAAqB,EAAE,qBAAqB,CAAC;KACpD,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;IACxB,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,IAAA,mBAAU,EAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAChD,MAAM,IAAA,mBAAU,EAAC,MAAM,CAAC,CAAC;IAC3B,CAAC;IAAC,OAAO,KAAU,EAAE,CAAC;QACpB,OAAO,CAAC,KAAK,CAAC,UAAU,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QACzC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC,CAAC,CAAC;AAEL,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/commands/create.ts"],"names":[],"mappings":"AA6CA,wBAAsB,eAAe,CACnC,IAAI,EAAE,MAAM,EACZ,aAAa,GAAE,MAAuB,EACtC,QAAQ,CAAC,EAAE,MAAM,EACjB,eAAe,CAAC,EAAE,MAAM,GACvB,OAAO,CAAC,IAAI,CAAC,CA2Df"}
|