@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.
Files changed (73) hide show
  1. package/.gitkeep +0 -0
  2. package/CHANGELOG.md +13 -0
  3. package/README.md +421 -0
  4. package/USAGE.md +557 -0
  5. package/bin/dynatable-migrate.js +30 -0
  6. package/dist/cli.d.ts +3 -0
  7. package/dist/cli.d.ts.map +1 -0
  8. package/dist/cli.js +104 -0
  9. package/dist/cli.js.map +1 -0
  10. package/dist/commands/create.d.ts +2 -0
  11. package/dist/commands/create.d.ts.map +1 -0
  12. package/dist/commands/create.js +123 -0
  13. package/dist/commands/create.js.map +1 -0
  14. package/dist/commands/down.d.ts +3 -0
  15. package/dist/commands/down.d.ts.map +1 -0
  16. package/dist/commands/down.js +37 -0
  17. package/dist/commands/down.js.map +1 -0
  18. package/dist/commands/init.d.ts +2 -0
  19. package/dist/commands/init.d.ts.map +1 -0
  20. package/dist/commands/init.js +70 -0
  21. package/dist/commands/init.js.map +1 -0
  22. package/dist/commands/status.d.ts +3 -0
  23. package/dist/commands/status.d.ts.map +1 -0
  24. package/dist/commands/status.js +84 -0
  25. package/dist/commands/status.js.map +1 -0
  26. package/dist/commands/up.d.ts +3 -0
  27. package/dist/commands/up.d.ts.map +1 -0
  28. package/dist/commands/up.js +37 -0
  29. package/dist/commands/up.js.map +1 -0
  30. package/dist/core/config.d.ts +29 -0
  31. package/dist/core/config.d.ts.map +1 -0
  32. package/dist/core/config.js +160 -0
  33. package/dist/core/config.js.map +1 -0
  34. package/dist/core/loader.d.ts +47 -0
  35. package/dist/core/loader.d.ts.map +1 -0
  36. package/dist/core/loader.js +226 -0
  37. package/dist/core/loader.js.map +1 -0
  38. package/dist/core/runner.d.ts +38 -0
  39. package/dist/core/runner.d.ts.map +1 -0
  40. package/dist/core/runner.js +166 -0
  41. package/dist/core/runner.js.map +1 -0
  42. package/dist/core/tracker.d.ts +19 -0
  43. package/dist/core/tracker.d.ts.map +1 -0
  44. package/dist/core/tracker.js +172 -0
  45. package/dist/core/tracker.js.map +1 -0
  46. package/dist/index.d.ts +16 -0
  47. package/dist/index.d.ts.map +1 -0
  48. package/dist/index.js +48 -0
  49. package/dist/index.js.map +1 -0
  50. package/dist/templates/migration.d.ts +5 -0
  51. package/dist/templates/migration.d.ts.map +1 -0
  52. package/dist/templates/migration.js +96 -0
  53. package/dist/templates/migration.js.map +1 -0
  54. package/dist/types/index.d.ts +159 -0
  55. package/dist/types/index.d.ts.map +1 -0
  56. package/dist/types/index.js +3 -0
  57. package/dist/types/index.js.map +1 -0
  58. package/migrations/.gitkeep +0 -0
  59. package/package.json +43 -0
  60. package/src/cli.ts +106 -0
  61. package/src/commands/create.ts +110 -0
  62. package/src/commands/down.ts +42 -0
  63. package/src/commands/init.ts +37 -0
  64. package/src/commands/status.ts +92 -0
  65. package/src/commands/up.ts +39 -0
  66. package/src/core/config.ts +140 -0
  67. package/src/core/loader.ts +233 -0
  68. package/src/core/runner.ts +223 -0
  69. package/src/core/tracker.ts +219 -0
  70. package/src/index.ts +23 -0
  71. package/src/templates/migration.ts +92 -0
  72. package/src/types/index.ts +196 -0
  73. package/tsconfig.json +19 -0
package/.gitkeep ADDED
File without changes
package/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ # @ftschopp/dynatable-migrations 1.0.0 (2026-01-10)
2
+
3
+
4
+ ### Features
5
+
6
+ * major initial release ([e282548](https://github.com/ftschopp/dynatable/commit/e28254895a40fdc26bab1dde2c634f663857ec16))
7
+
8
+ # Changelog
9
+
10
+ All notable changes to this project will be documented in this file.
11
+
12
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
13
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
package/README.md ADDED
@@ -0,0 +1,421 @@
1
+ # @ftschopp/dynatable-migrations
2
+
3
+ DynamoDB migration tool for single table design with schema versioning.
4
+
5
+ ## Features
6
+
7
+ - 🚀 **Single Table Design** - Built specifically for DynamoDB single table design patterns
8
+ - 📊 **Schema Versioning** - Track schema evolution over time within your DynamoDB table
9
+ - 🔄 **Up/Down Migrations** - Support for both applying and rolling back migrations
10
+ - 📝 **Migration History** - All migration records stored in your DynamoDB table using Single Table Design
11
+ - 🎯 **TypeScript First** - Full TypeScript support with type safety
12
+ - 🛠️ **CLI Tool** - Easy-to-use command-line interface
13
+ - 📦 **Zero Dependencies** - Only requires AWS SDK
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install @ftschopp/dynatable-migrations
19
+ # or
20
+ yarn add @ftschopp/dynatable-migrations
21
+ ```
22
+
23
+ ## Quick Start
24
+
25
+ ### 1. Initialize
26
+
27
+ Create migration structure in your project:
28
+
29
+ ```bash
30
+ npx dynatable-migrate init
31
+ ```
32
+
33
+ This creates:
34
+
35
+ - `migrations/` directory for your migration files
36
+ - `dynatable.config.js` configuration file
37
+
38
+ ### 2. Configure
39
+
40
+ Edit `dynatable.config.js`:
41
+
42
+ ```javascript
43
+ module.exports = {
44
+ tableName: 'MyTable',
45
+ client: {
46
+ region: 'us-east-1',
47
+ // For local DynamoDB
48
+ endpoint: 'http://localhost:8000',
49
+ credentials: {
50
+ accessKeyId: 'local',
51
+ secretAccessKey: 'local',
52
+ },
53
+ },
54
+ migrationsDir: './migrations',
55
+ };
56
+ ```
57
+
58
+ ### 3. Create Migration
59
+
60
+ ```bash
61
+ npx dynatable-migrate create add_user_email
62
+ ```
63
+
64
+ This creates a file like `migrations/v0001_add_user_email.ts`
65
+
66
+ ### 4. Edit Migration
67
+
68
+ ```typescript
69
+ import { Migration } from '@ftschopp/dynatable-migrations';
70
+
71
+ export const migration: Migration = {
72
+ version: 'v0001',
73
+ name: 'add_user_email',
74
+ description: 'Add email field to User entity',
75
+
76
+ async up({ client, tableName, dynamodb }) {
77
+ const { ScanCommand, UpdateCommand } = dynamodb;
78
+
79
+ // Scan all users
80
+ const result = await client.send(
81
+ new ScanCommand({
82
+ TableName: tableName,
83
+ FilterExpression: 'begins_with(PK, :pk)',
84
+ ExpressionAttributeValues: { ':pk': 'USER#' },
85
+ })
86
+ );
87
+
88
+ // Add email field to each user
89
+ for (const item of result.Items || []) {
90
+ await client.send(
91
+ new UpdateCommand({
92
+ TableName: tableName,
93
+ Key: { PK: item.PK, SK: item.SK },
94
+ UpdateExpression: 'SET email = :email, emailVerified = :verified',
95
+ ExpressionAttributeValues: {
96
+ ':email': null,
97
+ ':verified': false,
98
+ },
99
+ })
100
+ );
101
+ }
102
+ },
103
+
104
+ async down({ client, tableName, dynamodb }) {
105
+ const { ScanCommand, UpdateCommand } = dynamodb;
106
+
107
+ const result = await client.send(
108
+ new ScanCommand({
109
+ TableName: tableName,
110
+ FilterExpression: 'begins_with(PK, :pk)',
111
+ ExpressionAttributeValues: { ':pk': 'USER#' },
112
+ })
113
+ );
114
+
115
+ for (const item of result.Items || []) {
116
+ await client.send(
117
+ new UpdateCommand({
118
+ TableName: tableName,
119
+ Key: { PK: item.PK, SK: item.SK },
120
+ UpdateExpression: 'REMOVE email, emailVerified',
121
+ })
122
+ );
123
+ }
124
+ },
125
+ };
126
+ ```
127
+
128
+ ### 5. Run Migrations
129
+
130
+ ```bash
131
+ # Apply all pending migrations
132
+ npx dynatable-migrate up
133
+
134
+ # Apply only 1 migration
135
+ npx dynatable-migrate up --limit 1
136
+
137
+ # Check status
138
+ npx dynatable-migrate status
139
+
140
+ # Rollback last migration
141
+ npx dynatable-migrate down
142
+
143
+ # Rollback last 2 migrations
144
+ npx dynatable-migrate down --steps 2
145
+ ```
146
+
147
+ ## CLI Commands
148
+
149
+ ### `init`
150
+
151
+ Initialize migrations in your project.
152
+
153
+ ```bash
154
+ dynatable-migrate init
155
+ ```
156
+
157
+ ### `create <name>`
158
+
159
+ Create a new migration file.
160
+
161
+ ```bash
162
+ dynatable-migrate create add_user_profile
163
+ ```
164
+
165
+ Options:
166
+
167
+ - `-c, --config <path>` - Custom config file path
168
+
169
+ ### `up`
170
+
171
+ Run pending migrations.
172
+
173
+ ```bash
174
+ dynatable-migrate up
175
+ ```
176
+
177
+ Options:
178
+
179
+ - `-c, --config <path>` - Custom config file path
180
+ - `-l, --limit <number>` - Limit number of migrations to run
181
+
182
+ ### `down`
183
+
184
+ Rollback migrations.
185
+
186
+ ```bash
187
+ dynatable-migrate down
188
+ ```
189
+
190
+ Options:
191
+
192
+ - `-c, --config <path>` - Custom config file path
193
+ - `-s, --steps <number>` - Number of migrations to rollback (default: 1)
194
+
195
+ ### `status`
196
+
197
+ Show migration status.
198
+
199
+ ```bash
200
+ dynatable-migrate status
201
+ ```
202
+
203
+ Options:
204
+
205
+ - `-c, --config <path>` - Custom config file path
206
+
207
+ ## How It Works
208
+
209
+ ### Single Table Design
210
+
211
+ All migration tracking happens **within your DynamoDB table** using Single Table Design principles:
212
+
213
+ ```
214
+ PK SK version status appliedAt
215
+ -----------------------------------------------------------------------
216
+ _SCHEMA#VERSION v0001 v0001 applied 2025-03-29T10:00:00Z
217
+ _SCHEMA#VERSION v0002 v0002 applied 2025-03-29T11:00:00Z
218
+ _SCHEMA#VERSION#CURRENT _SCHEMA#VERSION v0002 - 2025-03-29T11:00:00Z
219
+ ```
220
+
221
+ A GSI (GSI1) is used to quickly find the current version:
222
+
223
+ ```
224
+ GSI1PK GSI1SK
225
+ ---------------------------
226
+ _SCHEMA#CURRENT v0002
227
+ ```
228
+
229
+ ### Migration Context
230
+
231
+ Every migration receives a context object with:
232
+
233
+ ```typescript
234
+ interface MigrationContext {
235
+ client: DynamoDBDocumentClient; // AWS SDK client
236
+ tableName: string; // Your table name
237
+ tracker: MigrationTracker; // Track schema changes
238
+ config: MigrationConfig; // Your config
239
+ }
240
+ ```
241
+
242
+ ### Schema Change Tracking
243
+
244
+ Track what changed in each migration:
245
+
246
+ ```typescript
247
+ await tracker.recordSchemaChange({
248
+ entity: 'User',
249
+ changes: {
250
+ added: ['email', 'emailVerified'],
251
+ removed: ['oldField'],
252
+ modified: [{ field: 'status', from: 'string', to: 'enum' }],
253
+ },
254
+ });
255
+ ```
256
+
257
+ ## Migration Examples
258
+
259
+ ### Add New Entity Type
260
+
261
+ ```typescript
262
+ export const migration: Migration = {
263
+ version: 'v0003',
264
+ name: 'add_comment_entity',
265
+
266
+ async up({ client, tableName }) {
267
+ // In Single Table Design, you typically don't need to do anything
268
+ // Just document the schema change
269
+ console.log('Comment entity added to schema');
270
+ },
271
+
272
+ async down({ client, tableName }) {
273
+ // Delete all comments if rolling back
274
+ const { ScanCommand, DeleteCommand } = await import('@aws-sdk/lib-dynamodb');
275
+
276
+ const result = await client.send(
277
+ new ScanCommand({
278
+ TableName: tableName,
279
+ FilterExpression: 'begins_with(PK, :pk)',
280
+ ExpressionAttributeValues: { ':pk': 'COMMENT#' },
281
+ })
282
+ );
283
+
284
+ for (const item of result.Items || []) {
285
+ await client.send(
286
+ new DeleteCommand({
287
+ TableName: tableName,
288
+ Key: { PK: item.PK, SK: item.SK },
289
+ })
290
+ );
291
+ }
292
+ },
293
+ };
294
+ ```
295
+
296
+ ### Change Key Structure
297
+
298
+ ```typescript
299
+ export const migration: Migration = {
300
+ version: 'v0004',
301
+ name: 'change_photo_sort_key',
302
+
303
+ async up({ client, tableName }) {
304
+ // Change from SK: "PHOTO#${id}" to SK: "PHOTO#${timestamp}#${id}"
305
+ const { ScanCommand, TransactWriteCommand } = await import('@aws-sdk/lib-dynamodb');
306
+
307
+ const result = await client.send(
308
+ new ScanCommand({
309
+ TableName: tableName,
310
+ FilterExpression: 'begins_with(SK, :sk)',
311
+ ExpressionAttributeValues: { ':sk': 'PHOTO#' },
312
+ })
313
+ );
314
+
315
+ for (const photo of result.Items || []) {
316
+ const timestamp = new Date(photo.createdAt).getTime();
317
+ const photoId = photo.SK.replace('PHOTO#', '');
318
+
319
+ await client.send(
320
+ new TransactWriteCommand({
321
+ TransactItems: [
322
+ {
323
+ Delete: {
324
+ TableName: tableName,
325
+ Key: { PK: photo.PK, SK: photo.SK },
326
+ },
327
+ },
328
+ {
329
+ Put: {
330
+ TableName: tableName,
331
+ Item: {
332
+ ...photo,
333
+ SK: `PHOTO#${timestamp}#${photoId}`,
334
+ },
335
+ },
336
+ },
337
+ ],
338
+ })
339
+ );
340
+ }
341
+ },
342
+
343
+ async down({ client, tableName }) {
344
+ // Reverse the change
345
+ // ... similar logic but reverse
346
+ },
347
+ };
348
+ ```
349
+
350
+ ## Configuration
351
+
352
+ ### Config File Options
353
+
354
+ ```typescript
355
+ interface MigrationConfig {
356
+ // Required: DynamoDB table name
357
+ tableName: string;
358
+
359
+ // Required: AWS client config
360
+ client: {
361
+ region: string;
362
+ endpoint?: string; // For local DynamoDB
363
+ credentials?: {
364
+ accessKeyId: string;
365
+ secretAccessKey: string;
366
+ };
367
+ };
368
+
369
+ // Optional: Migrations directory (default: ./migrations)
370
+ migrationsDir?: string;
371
+
372
+ // Optional: Tracking prefix (default: _SCHEMA#VERSION)
373
+ trackingPrefix?: string;
374
+
375
+ // Optional: GSI name (default: GSI1)
376
+ gsi1Name?: string;
377
+ }
378
+ ```
379
+
380
+ ## Programmatic Usage
381
+
382
+ You can also use the migration runner programmatically:
383
+
384
+ ```typescript
385
+ import { MigrationRunner, loadConfig } from '@ftschopp/dynatable-migrations';
386
+ import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
387
+ import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';
388
+
389
+ const config = await loadConfig();
390
+ const client = DynamoDBDocumentClient.from(
391
+ new DynamoDBClient({
392
+ region: config.client.region,
393
+ endpoint: config.client.endpoint,
394
+ })
395
+ );
396
+
397
+ const runner = new MigrationRunner(client, config);
398
+
399
+ // Run migrations
400
+ await runner.up();
401
+
402
+ // Get status
403
+ const status = await runner.status();
404
+
405
+ // Rollback
406
+ await runner.down(1);
407
+ ```
408
+
409
+ ## Best Practices
410
+
411
+ 1. **Always write down() functions** - Even if you think you won't need to rollback
412
+ 2. **Test migrations locally first** - Use DynamoDB Local
413
+ 3. **Backup before running** - Use DynamoDB point-in-time recovery
414
+ 4. **One change per migration** - Keep migrations focused
415
+ 5. **Don't modify applied migrations** - Create a new migration instead
416
+ 6. **Use transactions** - For multi-step changes that must be atomic
417
+ 7. **Document schema changes** - Use the schema snapshot field
418
+
419
+ ## License
420
+
421
+ MIT