@datacapy/migrate 0.1.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/LICENSE +29 -0
- package/README.md +658 -0
- package/dist/cli/command-parser.d.ts +18 -0
- package/dist/cli/command-parser.d.ts.map +1 -0
- package/dist/cli/command-parser.js +159 -0
- package/dist/cli/command-parser.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +22 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/runner.d.ts +6 -0
- package/dist/cli/runner.d.ts.map +1 -0
- package/dist/cli/runner.js +141 -0
- package/dist/cli/runner.js.map +1 -0
- package/dist/executor/patch-executor.d.ts +13 -0
- package/dist/executor/patch-executor.d.ts.map +1 -0
- package/dist/executor/patch-executor.js +58 -0
- package/dist/executor/patch-executor.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/interface/context-resolver.d.ts +4 -0
- package/dist/interface/context-resolver.d.ts.map +1 -0
- package/dist/interface/context-resolver.js +3 -0
- package/dist/interface/context-resolver.js.map +1 -0
- package/dist/interface/database-patch.d.ts +8 -0
- package/dist/interface/database-patch.d.ts.map +1 -0
- package/dist/interface/database-patch.js +3 -0
- package/dist/interface/database-patch.js.map +1 -0
- package/dist/interface/index.d.ts +4 -0
- package/dist/interface/index.d.ts.map +1 -0
- package/dist/interface/index.js +20 -0
- package/dist/interface/index.js.map +1 -0
- package/dist/interface/migration-config.d.ts +26 -0
- package/dist/interface/migration-config.d.ts.map +1 -0
- package/dist/interface/migration-config.js +3 -0
- package/dist/interface/migration-config.js.map +1 -0
- package/dist/interface/migration-result.d.ts +31 -0
- package/dist/interface/migration-result.d.ts.map +1 -0
- package/dist/interface/migration-result.js +3 -0
- package/dist/interface/migration-result.js.map +1 -0
- package/dist/logger/migration-logger.d.ts +21 -0
- package/dist/logger/migration-logger.d.ts.map +1 -0
- package/dist/logger/migration-logger.js +78 -0
- package/dist/logger/migration-logger.js.map +1 -0
- package/dist/manager/migration-manager.d.ts +12 -0
- package/dist/manager/migration-manager.d.ts.map +1 -0
- package/dist/manager/migration-manager.js +262 -0
- package/dist/manager/migration-manager.js.map +1 -0
- package/dist/meta/meta-table.d.ts +22 -0
- package/dist/meta/meta-table.d.ts.map +1 -0
- package/dist/meta/meta-table.js +86 -0
- package/dist/meta/meta-table.js.map +1 -0
- package/dist/scanner/patch-scanner.d.ts +11 -0
- package/dist/scanner/patch-scanner.d.ts.map +1 -0
- package/dist/scanner/patch-scanner.js +166 -0
- package/dist/scanner/patch-scanner.js.map +1 -0
- package/dist/version/version-manager.d.ts +11 -0
- package/dist/version/version-manager.d.ts.map +1 -0
- package/dist/version/version-manager.js +90 -0
- package/dist/version/version-manager.js.map +1 -0
- package/package.json +51 -0
- package/tsconfig.json +27 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2016, Kevin Foster
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
* Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
* Neither the name of the copyright holder nor the names of its
|
|
17
|
+
contributors may be used to endorse or promote products derived from
|
|
18
|
+
this software without specific prior written permission.
|
|
19
|
+
|
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
21
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
22
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
23
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
24
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
25
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
26
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
27
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
28
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.md
ADDED
|
@@ -0,0 +1,658 @@
|
|
|
1
|
+
# @datacapy/migrate
|
|
2
|
+
|
|
3
|
+
Database migration tool for @datacapy/om applications. Manage schema changes, data transformations, and database versioning across multiple datasources with transaction safety and rollback support.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Multi-Datasource Support**: Migrate account-level databases, project-specific databases, or any custom datasource
|
|
8
|
+
- **Transaction Safety**: Each patch runs in a transaction with automatic rollback on failure
|
|
9
|
+
- **Version Tracking**: Track applied migrations in a metadata table
|
|
10
|
+
- **Dry Run Mode**: Preview changes without applying them
|
|
11
|
+
- **TypeScript First**: Full TypeScript support with type-safe patch interfaces
|
|
12
|
+
- **Flexible Organisation**: Organise patches by year/month with timestamped versions
|
|
13
|
+
- **Resume Capability**: Automatically resume from last successful patch
|
|
14
|
+
- **CLI Tool**: Simple command-line interface for running migrations
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pnpm add @datacapy/migrate
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
### 1. Create a Migration Config
|
|
25
|
+
|
|
26
|
+
Create `migrate.config.js` in your project root:
|
|
27
|
+
|
|
28
|
+
```javascript
|
|
29
|
+
const modelManager = require("./src/model-manager").default;
|
|
30
|
+
|
|
31
|
+
module.exports = async () => {
|
|
32
|
+
return {
|
|
33
|
+
modelManager, // Your existing ModelManager instance
|
|
34
|
+
patchDirectory: "./migrate",
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### 2. Create Your First Patch
|
|
40
|
+
|
|
41
|
+
Create patches in `migrate/YYYY/MM/YYYY-MM-DD_HHMM_label.ts`:
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
// migrate/2024/02/2024-02-05_1430_add-users-table.ts
|
|
45
|
+
import { DatabasePatchInterface } from "@datacapy/migrate";
|
|
46
|
+
import { ModelManager } from "@datacapy/om";
|
|
47
|
+
|
|
48
|
+
export default class AddUsersTable implements DatabasePatchInterface {
|
|
49
|
+
version = "2024-02-05_1430";
|
|
50
|
+
description = "Add users table";
|
|
51
|
+
dataSourceName = "db"; // Target datasource
|
|
52
|
+
|
|
53
|
+
async update(modelManager: ModelManager): Promise<void> {
|
|
54
|
+
const dataSource = modelManager.getDataSource("db");
|
|
55
|
+
|
|
56
|
+
// Your migration logic here
|
|
57
|
+
await dataSource.insertOne("users", {
|
|
58
|
+
_id: "000000000000000000000001",
|
|
59
|
+
email: "admin@example.com",
|
|
60
|
+
role: "admin",
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 3. Run Migration
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# Migrate account-level database
|
|
70
|
+
pnpm @datacapy/migrate --config ./migrate.config.js --datasource db
|
|
71
|
+
|
|
72
|
+
# Preview changes first
|
|
73
|
+
pnpm @datacapy/migrate --config ./migrate.config.js --datasource db --dry-run
|
|
74
|
+
|
|
75
|
+
# Migrate with verbose output
|
|
76
|
+
pnpm @datacapy/migrate --config ./migrate.config.js --datasource db --verbose
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## CLI Reference
|
|
80
|
+
|
|
81
|
+
### Command Syntax
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
@datacapy/migrate [OPTIONS]
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Required Options
|
|
88
|
+
|
|
89
|
+
- `-c, --config <file>` - Path to migration config file
|
|
90
|
+
- `--datasource <name>` - Target datasource name (e.g., 'db', 'project')
|
|
91
|
+
|
|
92
|
+
### Optional Options
|
|
93
|
+
|
|
94
|
+
- `--context <key=value>` - Context for dynamic datasources (can be specified multiple times)
|
|
95
|
+
- `-d, --patch-dir <dir>` - Patch directory (default: ./migrate)
|
|
96
|
+
- `-t, --target <version>` - Target version to migrate to (default: latest)
|
|
97
|
+
- `--dry-run` - Preview migration without making changes
|
|
98
|
+
- `-v, --verbose` - Enable verbose logging
|
|
99
|
+
- `-h, --help` - Show help message
|
|
100
|
+
- `--version` - Show package version
|
|
101
|
+
|
|
102
|
+
### Examples
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
# Migrate account-level database
|
|
106
|
+
pnpm @datacapy/migrate --config ./migrate.config.js --datasource db
|
|
107
|
+
|
|
108
|
+
# Migrate specific project database (dynamic datasource)
|
|
109
|
+
pnpm @datacapy/migrate --config ./migrate.config.js \\
|
|
110
|
+
--datasource project \\
|
|
111
|
+
--context projectId=abc123
|
|
112
|
+
|
|
113
|
+
# Dry run to preview changes
|
|
114
|
+
pnpm @datacapy/migrate --config ./migrate.config.js --datasource db --dry-run
|
|
115
|
+
|
|
116
|
+
# Migrate to specific version
|
|
117
|
+
pnpm @datacapy/migrate --config ./migrate.config.js \\
|
|
118
|
+
--datasource db \\
|
|
119
|
+
--target 2024-02-05_1430
|
|
120
|
+
|
|
121
|
+
# Verbose output
|
|
122
|
+
pnpm @datacapy/migrate --config ./migrate.config.js --datasource db --verbose
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Configuration
|
|
126
|
+
|
|
127
|
+
### Config File
|
|
128
|
+
|
|
129
|
+
The config file should export an async function that returns a `MigrationConfig` object:
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
import { ModelManager } from "@datacapy/om";
|
|
133
|
+
|
|
134
|
+
export default async () => {
|
|
135
|
+
return {
|
|
136
|
+
modelManager, // Required: Your ModelManager instance
|
|
137
|
+
patchDirectory: "./migrate", // Optional: Patch directory
|
|
138
|
+
verbose: false, // Optional: Enable verbose logging
|
|
139
|
+
};
|
|
140
|
+
};
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Configuration Options
|
|
144
|
+
|
|
145
|
+
- `modelManager`: **Required** - Existing ModelManager instance with configured datasources
|
|
146
|
+
- `dataSourceName`: **Required** - Provided via CLI `--datasource` argument
|
|
147
|
+
- `context`: **Optional** - Provided via CLI `--context` argument for dynamic datasources
|
|
148
|
+
- `patchDirectory`: **Optional** - Directory containing patches (default: './migrate')
|
|
149
|
+
- `targetVersion`: **Optional** - Target version to migrate to (default: latest)
|
|
150
|
+
- `metaTableName`: **Optional** - Name of meta table (default: 'migrationMeta')
|
|
151
|
+
- `dryRun`: **Optional** - Preview mode (default: false)
|
|
152
|
+
- `verbose`: **Optional** - Verbose logging (default: false)
|
|
153
|
+
- `stopOnError`: **Optional** - Stop on first error (default: true)
|
|
154
|
+
- `logger`: **Optional** - Custom logger function
|
|
155
|
+
|
|
156
|
+
## Writing Patches
|
|
157
|
+
|
|
158
|
+
### Patch Interface
|
|
159
|
+
|
|
160
|
+
All patches must implement `DatabasePatchInterface`:
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
export interface DatabasePatchInterface {
|
|
164
|
+
version: string; // Format: YYYY-MM-DD_HHMM
|
|
165
|
+
description: string; // Human-readable description
|
|
166
|
+
dataSourceName: string; // Target datasource: 'db', 'project', etc.
|
|
167
|
+
update(modelManager: ModelManager): Promise<void>;
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Patch File Structure
|
|
172
|
+
|
|
173
|
+
Patches must be organised in a timestamped directory structure:
|
|
174
|
+
|
|
175
|
+
```
|
|
176
|
+
migrate/
|
|
177
|
+
├── 2024/
|
|
178
|
+
│ ├── 01/
|
|
179
|
+
│ │ ├── 2024-01-15_1200_add-users-table.ts
|
|
180
|
+
│ │ └── 2024-01-20_1430_add-user-indexes.ts
|
|
181
|
+
│ └── 02/
|
|
182
|
+
│ ├── 2024-02-05_1430_add-validation.ts
|
|
183
|
+
│ └── 2024-02-10_0900_seed-data.ts
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Example Patches
|
|
187
|
+
|
|
188
|
+
#### Simple Table Creation
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
import { DatabasePatchInterface } from "@datacapy/migrate";
|
|
192
|
+
import { ModelManager } from "@datacapy/om";
|
|
193
|
+
|
|
194
|
+
export default class AddUsersTable implements DatabasePatchInterface {
|
|
195
|
+
version = "2024-02-05_1430";
|
|
196
|
+
description = "Add users table";
|
|
197
|
+
dataSourceName = "db";
|
|
198
|
+
|
|
199
|
+
async update(modelManager: ModelManager): Promise<void> {
|
|
200
|
+
const db = modelManager.getDataSource("db");
|
|
201
|
+
|
|
202
|
+
// Insert initial record to create table
|
|
203
|
+
await db.insertOne("users", {
|
|
204
|
+
_id: "000000000000000000000001",
|
|
205
|
+
email: "admin@example.com",
|
|
206
|
+
role: "admin",
|
|
207
|
+
createdAt: new Date(),
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
#### Adding Indexes
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
export default class AddUserIndexes implements DatabasePatchInterface {
|
|
217
|
+
version = "2024-02-06_1000";
|
|
218
|
+
description = "Add indexes to users table";
|
|
219
|
+
dataSourceName = "db";
|
|
220
|
+
|
|
221
|
+
async update(modelManager: ModelManager): Promise<void> {
|
|
222
|
+
const db = modelManager.getDataSource("db");
|
|
223
|
+
|
|
224
|
+
// Create unique index on email
|
|
225
|
+
await db.createIndex(
|
|
226
|
+
"users",
|
|
227
|
+
{ email: 1 },
|
|
228
|
+
{
|
|
229
|
+
name: "idx_users_email",
|
|
230
|
+
unique: true,
|
|
231
|
+
},
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
// Create index on createdAt
|
|
235
|
+
await db.createIndex(
|
|
236
|
+
"users",
|
|
237
|
+
{ createdAt: -1 },
|
|
238
|
+
{
|
|
239
|
+
name: "idx_users_created",
|
|
240
|
+
},
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
#### Using Repositories
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
export default class SeedDefaultRoles implements DatabasePatchInterface {
|
|
250
|
+
version = "2024-02-07_1400";
|
|
251
|
+
description = "Seed default user roles";
|
|
252
|
+
dataSourceName = "db";
|
|
253
|
+
|
|
254
|
+
async update(modelManager: ModelManager): Promise<void> {
|
|
255
|
+
// Access repo through ModelManager
|
|
256
|
+
const roleRepo = modelManager.getRepo("role");
|
|
257
|
+
|
|
258
|
+
// Check if already seeded
|
|
259
|
+
const count = await roleRepo.count();
|
|
260
|
+
if (count > 0) {
|
|
261
|
+
console.log("Roles already exist, skipping seed");
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Insert default roles
|
|
266
|
+
await roleRepo.insertMany([
|
|
267
|
+
{ name: "admin", permissions: ["*"] },
|
|
268
|
+
{ name: "editor", permissions: ["read", "write"] },
|
|
269
|
+
{ name: "viewer", permissions: ["read"] },
|
|
270
|
+
]);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
#### Project-Specific Migration
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
278
|
+
export default class AddProjectStatus implements DatabasePatchInterface {
|
|
279
|
+
version = "2024-02-08_1000";
|
|
280
|
+
description = "Add status field to surveys";
|
|
281
|
+
dataSourceName = "project"; // Targets dynamic project datasource
|
|
282
|
+
|
|
283
|
+
async update(modelManager: ModelManager): Promise<void> {
|
|
284
|
+
// Get project datasource (resolved via CLI --context)
|
|
285
|
+
const projectDS = modelManager.getDataSource("project");
|
|
286
|
+
|
|
287
|
+
// Update all surveys in this project
|
|
288
|
+
await projectDS.updateMany(
|
|
289
|
+
"surveys",
|
|
290
|
+
{},
|
|
291
|
+
{
|
|
292
|
+
$set: { status: "draft" },
|
|
293
|
+
},
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
## Multi-Datasource Migrations
|
|
300
|
+
|
|
301
|
+
@datacapy/migrate supports migrating multiple datasources independently:
|
|
302
|
+
|
|
303
|
+
### Account-Level Migrations
|
|
304
|
+
|
|
305
|
+
Migrate the main account database:
|
|
306
|
+
|
|
307
|
+
```bash
|
|
308
|
+
pnpm @datacapy/migrate --config ./migrate.config.js --datasource db
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Project-Level Migrations
|
|
312
|
+
|
|
313
|
+
Migrate a specific project's database:
|
|
314
|
+
|
|
315
|
+
```bash
|
|
316
|
+
pnpm @datacapy/migrate --config ./migrate.config.js \\
|
|
317
|
+
--datasource project \\
|
|
318
|
+
--context projectId=abc123
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### Migrating Multiple Projects
|
|
322
|
+
|
|
323
|
+
```bash
|
|
324
|
+
# Get all project IDs, then migrate each
|
|
325
|
+
for projectId in $(get-project-ids); do
|
|
326
|
+
echo "Migrating project $projectId..."
|
|
327
|
+
pnpm @datacapy/migrate --config ./migrate.config.js \\
|
|
328
|
+
--datasource project \\
|
|
329
|
+
--context projectId=$projectId
|
|
330
|
+
done
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
## Version Format
|
|
334
|
+
|
|
335
|
+
Versions use the format `YYYY-MM-DD_HHMM`:
|
|
336
|
+
|
|
337
|
+
- **YYYY**: 4-digit year
|
|
338
|
+
- **MM**: 2-digit month (01-12)
|
|
339
|
+
- **DD**: 2-digit day (01-31)
|
|
340
|
+
- **HHMM**: 4-digit time (0000-2359)
|
|
341
|
+
|
|
342
|
+
Examples:
|
|
343
|
+
|
|
344
|
+
- `2024-02-05_1430` - February 5, 2024 at 2:30 PM
|
|
345
|
+
- `2024-12-31_2359` - December 31, 2024 at 11:59 PM
|
|
346
|
+
|
|
347
|
+
**Benefits:**
|
|
348
|
+
|
|
349
|
+
- Natural chronological ordering
|
|
350
|
+
- Easy to generate: `const version = new Date().toISOString().slice(0, 16).replace('T', '_').replace(':', '')`
|
|
351
|
+
- Eliminates merge conflicts (timestamps are unique)
|
|
352
|
+
- Human-readable
|
|
353
|
+
|
|
354
|
+
## Transaction Safety
|
|
355
|
+
|
|
356
|
+
### Automatic Transactions
|
|
357
|
+
|
|
358
|
+
Each patch runs in a transaction with automatic rollback on failure:
|
|
359
|
+
|
|
360
|
+
```typescript
|
|
361
|
+
// This patch will rollback if any operation fails
|
|
362
|
+
export default class SafeMigration implements DatabasePatchInterface {
|
|
363
|
+
version = "2024-02-09_1000";
|
|
364
|
+
description = "Safe migration with automatic rollback";
|
|
365
|
+
dataSourceName = "db";
|
|
366
|
+
|
|
367
|
+
async update(modelManager: ModelManager): Promise<void> {
|
|
368
|
+
const db = modelManager.getDataSource("db");
|
|
369
|
+
|
|
370
|
+
// All these operations are in a transaction
|
|
371
|
+
await db.insertOne("users", { email: "user1@example.com" });
|
|
372
|
+
await db.insertOne("users", { email: "user2@example.com" });
|
|
373
|
+
|
|
374
|
+
// If this fails, both inserts are rolled back
|
|
375
|
+
await db.createIndex("users", { email: 1 }, { unique: true });
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### Resume Capability
|
|
381
|
+
|
|
382
|
+
If a migration fails:
|
|
383
|
+
|
|
384
|
+
1. All changes from the failed patch are rolled back
|
|
385
|
+
2. Successfully applied patches remain applied
|
|
386
|
+
3. Running the migration again resumes from where it stopped
|
|
387
|
+
|
|
388
|
+
```bash
|
|
389
|
+
# First run - patches 1 and 2 succeed, patch 3 fails
|
|
390
|
+
$ pnpm @datacapy/migrate --config ./migrate.config.js --datasource db
|
|
391
|
+
# ✓ Patch 1 applied
|
|
392
|
+
# ✓ Patch 2 applied
|
|
393
|
+
# ✗ Patch 3 failed - rolled back
|
|
394
|
+
|
|
395
|
+
# Fix the issue in patch 3 and run again
|
|
396
|
+
$ pnpm @datacapy/migrate --config ./migrate.config.js --datasource db
|
|
397
|
+
# Skipping patch 1 (already applied)
|
|
398
|
+
# Skipping patch 2 (already applied)
|
|
399
|
+
# ✓ Patch 3 applied
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
## Metadata Tracking
|
|
403
|
+
|
|
404
|
+
Migration status is tracked in a `migrationMeta` table (customisable via `metaTableName`):
|
|
405
|
+
|
|
406
|
+
```javascript
|
|
407
|
+
{
|
|
408
|
+
version: '2024-02-05_1430',
|
|
409
|
+
description: 'Add users table',
|
|
410
|
+
appliedAt: new Date('2024-02-05T14:30:00Z'),
|
|
411
|
+
duration: 150 // milliseconds
|
|
412
|
+
}
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
### Querying Migration Status
|
|
416
|
+
|
|
417
|
+
```typescript
|
|
418
|
+
import { MetaTable } from "@datacapy/migrate";
|
|
419
|
+
|
|
420
|
+
// Get current database version
|
|
421
|
+
const metaTable = new MetaTable(dataSource, "migrationMeta");
|
|
422
|
+
const currentVersion = await metaTable.getCurrentVersion();
|
|
423
|
+
console.log(`Current version: ${currentVersion}`);
|
|
424
|
+
|
|
425
|
+
// Get all applied patches
|
|
426
|
+
const patches = await metaTable.getAppliedPatches();
|
|
427
|
+
patches.forEach((p) => {
|
|
428
|
+
console.log(`${p.version}: ${p.description} (applied ${p.appliedAt})`);
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
// Check if specific patch was applied
|
|
432
|
+
const isApplied = await metaTable.isPatchApplied("2024-02-05_1430");
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
## Programmatic Usage
|
|
436
|
+
|
|
437
|
+
You can also use @datacapy/migrate programmatically:
|
|
438
|
+
|
|
439
|
+
```typescript
|
|
440
|
+
import { MigrationManager } from "@datacapy/migrate";
|
|
441
|
+
import modelManager from "./src/model-manager";
|
|
442
|
+
|
|
443
|
+
const config = {
|
|
444
|
+
modelManager,
|
|
445
|
+
dataSourceName: "db",
|
|
446
|
+
patchDirectory: "./migrate",
|
|
447
|
+
verbose: true,
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
const manager = new MigrationManager(config);
|
|
451
|
+
const result = await manager.migrate();
|
|
452
|
+
|
|
453
|
+
console.log(`Applied ${result.successCount} patches`);
|
|
454
|
+
console.log(`Current version: ${result.currentVersion}`);
|
|
455
|
+
|
|
456
|
+
if (result.failedCount > 0) {
|
|
457
|
+
console.error("Migration failed!");
|
|
458
|
+
result.patchResults.forEach((r) => {
|
|
459
|
+
if (r.status === "failed") {
|
|
460
|
+
console.error(`${r.version}: ${r.error?.message}`);
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
## Best Practices
|
|
467
|
+
|
|
468
|
+
### 1. Always Use Dry Run First
|
|
469
|
+
|
|
470
|
+
```bash
|
|
471
|
+
pnpm @datacapy/migrate --config ./migrate.config.js --datasource db --dry-run
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
### 2. Commit Patches to Version Control
|
|
475
|
+
|
|
476
|
+
Patches should be committed to Git for team coordination and deployment automation.
|
|
477
|
+
|
|
478
|
+
### 3. Keep Patches Atomic
|
|
479
|
+
|
|
480
|
+
Each patch should do one thing and be reversible if needed:
|
|
481
|
+
|
|
482
|
+
```typescript
|
|
483
|
+
// Good - Single, clear purpose
|
|
484
|
+
export default class AddUserEmailIndex implements DatabasePatchInterface {
|
|
485
|
+
version = "2024-02-10_1000";
|
|
486
|
+
description = "Add index on users.email for faster lookups";
|
|
487
|
+
dataSourceName = "db";
|
|
488
|
+
// ...
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Avoid - Multiple unrelated changes
|
|
492
|
+
export default class MiscChanges implements DatabasePatchInterface {
|
|
493
|
+
version = "2024-02-10_1100";
|
|
494
|
+
description = "Add indexes, update roles, and seed data";
|
|
495
|
+
dataSourceName = "db";
|
|
496
|
+
// Too much in one patch!
|
|
497
|
+
}
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
### 4. Test Patches Locally First
|
|
501
|
+
|
|
502
|
+
Use DataSourceMock to test patches:
|
|
503
|
+
|
|
504
|
+
```typescript
|
|
505
|
+
import { DataSourceMock } from "@datacapy/om";
|
|
506
|
+
import AddUsersTable from "./2024-02-05_1430_add-users-table";
|
|
507
|
+
|
|
508
|
+
describe("AddUsersTable patch", () => {
|
|
509
|
+
it("should create users table", async () => {
|
|
510
|
+
const mockDS = new DataSourceMock({});
|
|
511
|
+
const mockMM = new ModelManager({});
|
|
512
|
+
mockMM.addDataSource("db", mockDS);
|
|
513
|
+
|
|
514
|
+
const patch = new AddUsersTable();
|
|
515
|
+
await patch.update(mockMM);
|
|
516
|
+
|
|
517
|
+
// Verify patch worked
|
|
518
|
+
expect(mockDS.dataInsert).toHaveLength(1);
|
|
519
|
+
});
|
|
520
|
+
});
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
### 5. Use Descriptive Patch Names
|
|
524
|
+
|
|
525
|
+
```
|
|
526
|
+
✓ 2024-02-05_1430_add-users-table.ts
|
|
527
|
+
✓ 2024-02-06_1000_add-user-email-index.ts
|
|
528
|
+
✓ 2024-02-07_1400_seed-default-roles.ts
|
|
529
|
+
|
|
530
|
+
✗ 2024-02-05_1430_patch1.ts
|
|
531
|
+
✗ 2024-02-06_1000_update.ts
|
|
532
|
+
✗ 2024-02-07_1400_fix.ts
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
### 6. Handle Idempotency
|
|
536
|
+
|
|
537
|
+
Patches should be safe to run multiple times:
|
|
538
|
+
|
|
539
|
+
```typescript
|
|
540
|
+
export default class SeedData implements DatabasePatchInterface {
|
|
541
|
+
version = "2024-02-11_1000";
|
|
542
|
+
description = "Seed initial data";
|
|
543
|
+
dataSourceName = "db";
|
|
544
|
+
|
|
545
|
+
async update(modelManager: ModelManager): Promise<void> {
|
|
546
|
+
const repo = modelManager.getRepo("setting");
|
|
547
|
+
|
|
548
|
+
// Check if already seeded
|
|
549
|
+
const exists = await repo.findOne({ key: "app.initialized" });
|
|
550
|
+
if (exists) {
|
|
551
|
+
console.log("Already initialized, skipping");
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// Safe to seed
|
|
556
|
+
await repo.insertOne({ key: "app.initialized", value: true });
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
## Troubleshooting
|
|
562
|
+
|
|
563
|
+
### Migration Fails with "Datasource not found"
|
|
564
|
+
|
|
565
|
+
**Problem:** Cannot find datasource 'project'
|
|
566
|
+
|
|
567
|
+
**Solution:** Dynamic datasources need context:
|
|
568
|
+
|
|
569
|
+
```bash
|
|
570
|
+
pnpm @datacapy/migrate --config ./migrate.config.js \\
|
|
571
|
+
--datasource project \\
|
|
572
|
+
--context projectId=abc123
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
### Patch Throws "Duplicate key error"
|
|
576
|
+
|
|
577
|
+
**Problem:** Patch already applied, trying to re-apply
|
|
578
|
+
|
|
579
|
+
**Solution:** Check meta table, patch may have been applied in previous run:
|
|
580
|
+
|
|
581
|
+
```typescript
|
|
582
|
+
const metaTable = new MetaTable(dataSource);
|
|
583
|
+
const isApplied = await metaTable.isPatchApplied("2024-02-05_1430");
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
### Invalid Version Format Error
|
|
587
|
+
|
|
588
|
+
**Problem:** Version doesn't match `YYYY-MM-DD_HHMM` format
|
|
589
|
+
|
|
590
|
+
**Solution:** Ensure version and filename match exactly:
|
|
591
|
+
|
|
592
|
+
```typescript
|
|
593
|
+
// Filename: 2024-02-05_1430_add-table.ts
|
|
594
|
+
export default class AddTable implements DatabasePatchInterface {
|
|
595
|
+
version = "2024-02-05_1430"; // Must match filename
|
|
596
|
+
// ...
|
|
597
|
+
}
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
## API Reference
|
|
601
|
+
|
|
602
|
+
### MigrationManager
|
|
603
|
+
|
|
604
|
+
```typescript
|
|
605
|
+
class MigrationManager {
|
|
606
|
+
constructor(config: MigrationConfig);
|
|
607
|
+
migrate(): Promise<MigrationResult>;
|
|
608
|
+
}
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
### MetaTable
|
|
612
|
+
|
|
613
|
+
```typescript
|
|
614
|
+
class MetaTable {
|
|
615
|
+
constructor(dataSource: DataSourceInterface, tableName?: string);
|
|
616
|
+
initialize(): Promise<void>;
|
|
617
|
+
getCurrentVersion(): Promise<string>;
|
|
618
|
+
recordPatch(
|
|
619
|
+
version: string,
|
|
620
|
+
description: string,
|
|
621
|
+
duration?: number,
|
|
622
|
+
): Promise<void>;
|
|
623
|
+
getAppliedPatches(): Promise<MetaRecord[]>;
|
|
624
|
+
isPatchApplied(version: string): Promise<boolean>;
|
|
625
|
+
}
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
### VersionManager
|
|
629
|
+
|
|
630
|
+
```typescript
|
|
631
|
+
class VersionManager {
|
|
632
|
+
static parseVersion(version: string): number;
|
|
633
|
+
static compareVersions(v1: string, v2: string): number;
|
|
634
|
+
static sortVersions(versions: string[]): string[];
|
|
635
|
+
static isValidVersion(version: string): boolean;
|
|
636
|
+
static generateVersion(date?: Date): string;
|
|
637
|
+
static getLatestVersion(versions: string[]): string | undefined;
|
|
638
|
+
}
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
## Documentation
|
|
642
|
+
|
|
643
|
+
- **[Architecture](./docs/architecture/index.md)** - Migration system internals and technical implementation
|
|
644
|
+
- **[Best Practices](./docs/best-practices/index.md)** - Guidelines for writing safe, maintainable migrations
|
|
645
|
+
- **[Advanced Usage](./docs/advanced-usage/index.md)** - Advanced patterns, troubleshooting, and deployment
|
|
646
|
+
|
|
647
|
+
## Contributing
|
|
648
|
+
|
|
649
|
+
Contributions are welcome! Please open an issue or submit a pull request on GitHub.
|
|
650
|
+
|
|
651
|
+
## License
|
|
652
|
+
|
|
653
|
+
BSD-3-Clause
|
|
654
|
+
|
|
655
|
+
## Support
|
|
656
|
+
|
|
657
|
+
- Documentation: https://github.com/datacapy/datacapy
|
|
658
|
+
- Issues: https://github.com/datacapy/datacapy/issues
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface CliArguments {
|
|
2
|
+
config?: string;
|
|
3
|
+
datasource?: string;
|
|
4
|
+
context: Record<string, string>;
|
|
5
|
+
contextLookup?: string;
|
|
6
|
+
patchDirectory?: string;
|
|
7
|
+
targetVersion?: string;
|
|
8
|
+
dryRun: boolean;
|
|
9
|
+
verbose: boolean;
|
|
10
|
+
help: boolean;
|
|
11
|
+
version: boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare class CommandParser {
|
|
14
|
+
static parse(args: string[]): CliArguments;
|
|
15
|
+
static validate(args: CliArguments): void;
|
|
16
|
+
static getHelpText(): string;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=command-parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command-parser.d.ts","sourceRoot":"","sources":["../../src/cli/command-parser.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,qBAAa,aAAa;IAOxB,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,YAAY;IAqF1C,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI;IAoBzC,MAAM,CAAC,WAAW,IAAI,MAAM;CAgF7B"}
|