masterrecord 0.2.36 → 0.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/.claude/settings.local.json +20 -1
- package/Entity/entityModel.js +6 -0
- package/Entity/entityTrackerModel.js +20 -3
- package/Entity/fieldTransformer.js +266 -0
- package/Migrations/migrationMySQLQuery.js +145 -1
- package/Migrations/migrationPostgresQuery.js +402 -0
- package/Migrations/migrationSQLiteQuery.js +145 -1
- package/Migrations/schema.js +131 -28
- package/QueryLanguage/queryMethods.js +193 -15
- package/QueryLanguage/queryParameters.js +136 -0
- package/QueryLanguage/queryScript.js +13 -4
- package/SQLLiteEngine.js +331 -20
- package/context.js +91 -14
- package/docs/INCLUDES_CLARIFICATION.md +202 -0
- package/docs/METHODS_REFERENCE.md +184 -0
- package/docs/MIGRATIONS_GUIDE.md +699 -0
- package/docs/POSTGRESQL_SETUP.md +415 -0
- package/examples/jsonArrayTransformer.js +215 -0
- package/mySQLEngine.js +273 -17
- package/package.json +3 -3
- package/postgresEngine.js +600 -483
- package/postgresSyncConnect.js +209 -0
- package/readme.md +1046 -416
- package/test/anyCommaStringTest.js +237 -0
- package/test/anyMethodTest.js +176 -0
- package/test/findByIdTest.js +227 -0
- package/test/includesFeatureTest.js +183 -0
- package/test/includesTransformTest.js +110 -0
- package/test/newMethodTest.js +330 -0
- package/test/newMethodUnitTest.js +320 -0
- package/test/parameterizedPlaceholderTest.js +159 -0
- package/test/postgresEngineTest.js +463 -0
- package/test/postgresIntegrationTest.js +381 -0
- package/test/securityTest.js +268 -0
- package/test/singleDollarPlaceholderTest.js +238 -0
- package/test/transformerTest.js +287 -0
- package/test/verifyFindById.js +169 -0
- package/test/verifyNewMethod.js +191 -0
|
@@ -0,0 +1,699 @@
|
|
|
1
|
+
# MasterRecord Migrations Guide
|
|
2
|
+
|
|
3
|
+
Complete guide for database migrations and seed data with support for MySQL, SQLite, and PostgreSQL.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
- [Overview](#overview)
|
|
7
|
+
- [Quick Start](#quick-start)
|
|
8
|
+
- [Database Support](#database-support)
|
|
9
|
+
- [Creating Migrations](#creating-migrations)
|
|
10
|
+
- [Seed Data](#seed-data)
|
|
11
|
+
- [Migration Commands](#migration-commands)
|
|
12
|
+
- [Examples](#examples)
|
|
13
|
+
|
|
14
|
+
## Overview
|
|
15
|
+
|
|
16
|
+
MasterRecord migrations allow you to:
|
|
17
|
+
- Create and modify database tables
|
|
18
|
+
- Track schema changes over time
|
|
19
|
+
- Seed initial or test data
|
|
20
|
+
- Roll back changes when needed
|
|
21
|
+
- Work consistently across MySQL, SQLite, and PostgreSQL
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
### 1. Setup Your Context
|
|
26
|
+
|
|
27
|
+
```javascript
|
|
28
|
+
// app/models/context.js
|
|
29
|
+
const context = require('masterrecord/context');
|
|
30
|
+
|
|
31
|
+
class AppContext extends context {
|
|
32
|
+
constructor() {
|
|
33
|
+
super();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
module.exports = AppContext;
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### 2. Create Your First Migration
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# Run from your project root
|
|
44
|
+
masterrecord add-migration InitialCreate context
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
This creates a new migration file in `app/models/db/migrations/`
|
|
48
|
+
|
|
49
|
+
### 3. Define Your Schema
|
|
50
|
+
|
|
51
|
+
```javascript
|
|
52
|
+
// migrations/20250111_InitialCreate.js
|
|
53
|
+
module.exports = {
|
|
54
|
+
up: function(table, schema) {
|
|
55
|
+
// Create Users table
|
|
56
|
+
schema.createTable(table.User);
|
|
57
|
+
|
|
58
|
+
// Create Posts table
|
|
59
|
+
schema.createTable(table.Post);
|
|
60
|
+
|
|
61
|
+
// Add seed data
|
|
62
|
+
schema.seed('User', [
|
|
63
|
+
{ name: 'Admin', email: 'admin@example.com', role: 'admin' },
|
|
64
|
+
{ name: 'User', email: 'user@example.com', role: 'user' }
|
|
65
|
+
]);
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
down: function(table, schema) {
|
|
69
|
+
schema.dropTable(table.Post);
|
|
70
|
+
schema.dropTable(table.User);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 4. Run Migrations
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
masterrecord migrate context
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Database Support
|
|
82
|
+
|
|
83
|
+
### PostgreSQL (NEW!)
|
|
84
|
+
Full PostgreSQL support with:
|
|
85
|
+
- SERIAL/BIGSERIAL for auto-increment
|
|
86
|
+
- Native BOOLEAN type
|
|
87
|
+
- JSON and JSONB support
|
|
88
|
+
- UUID support
|
|
89
|
+
- Parameterized queries ($1, $2, $3...)
|
|
90
|
+
- ON CONFLICT DO NOTHING for idempotent seeds
|
|
91
|
+
|
|
92
|
+
### MySQL
|
|
93
|
+
Complete MySQL support with:
|
|
94
|
+
- AUTO_INCREMENT
|
|
95
|
+
- TINYINT for booleans (0/1)
|
|
96
|
+
- JSON support
|
|
97
|
+
- INSERT IGNORE for idempotent seeds
|
|
98
|
+
|
|
99
|
+
### SQLite
|
|
100
|
+
Full SQLite support with:
|
|
101
|
+
- AUTOINCREMENT
|
|
102
|
+
- INTEGER for booleans (0/1)
|
|
103
|
+
- INSERT OR IGNORE for idempotent seeds
|
|
104
|
+
|
|
105
|
+
## Creating Migrations
|
|
106
|
+
|
|
107
|
+
### Basic Table Creation
|
|
108
|
+
|
|
109
|
+
```javascript
|
|
110
|
+
module.exports = {
|
|
111
|
+
up: function(table, schema) {
|
|
112
|
+
// table.TableName contains the entity definition
|
|
113
|
+
schema.createTable(table.User);
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
down: function(table, schema) {
|
|
117
|
+
schema.dropTable(table.User);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Adding Columns
|
|
123
|
+
|
|
124
|
+
```javascript
|
|
125
|
+
module.exports = {
|
|
126
|
+
up: function(table, schema) {
|
|
127
|
+
schema.addColumn({
|
|
128
|
+
tableName: 'User',
|
|
129
|
+
name: 'phone_number',
|
|
130
|
+
type: 'string'
|
|
131
|
+
});
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
down: function(table, schema) {
|
|
135
|
+
schema.dropColumn({
|
|
136
|
+
tableName: 'User',
|
|
137
|
+
name: 'phone_number'
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Modifying Columns
|
|
144
|
+
|
|
145
|
+
```javascript
|
|
146
|
+
module.exports = {
|
|
147
|
+
up: function(table, schema) {
|
|
148
|
+
schema.alterColumn({
|
|
149
|
+
tableName: 'User',
|
|
150
|
+
table: {
|
|
151
|
+
name: 'age',
|
|
152
|
+
type: 'integer',
|
|
153
|
+
nullable: false,
|
|
154
|
+
default: 0
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
},
|
|
158
|
+
|
|
159
|
+
down: function(table, schema) {
|
|
160
|
+
schema.alterColumn({
|
|
161
|
+
tableName: 'User',
|
|
162
|
+
table: {
|
|
163
|
+
name: 'age',
|
|
164
|
+
type: 'integer',
|
|
165
|
+
nullable: true
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Renaming Columns
|
|
173
|
+
|
|
174
|
+
```javascript
|
|
175
|
+
module.exports = {
|
|
176
|
+
up: function(table, schema) {
|
|
177
|
+
schema.renameColumn({
|
|
178
|
+
tableName: 'User',
|
|
179
|
+
name: 'username',
|
|
180
|
+
newName: 'user_name'
|
|
181
|
+
});
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
down: function(table, schema) {
|
|
185
|
+
schema.renameColumn({
|
|
186
|
+
tableName: 'User',
|
|
187
|
+
name: 'user_name',
|
|
188
|
+
newName: 'username'
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Seed Data
|
|
195
|
+
|
|
196
|
+
### Simple Seeding
|
|
197
|
+
|
|
198
|
+
```javascript
|
|
199
|
+
module.exports = {
|
|
200
|
+
up: function(table, schema) {
|
|
201
|
+
schema.createTable(table.Role);
|
|
202
|
+
|
|
203
|
+
// Seed individual records
|
|
204
|
+
schema.seed('Role', {
|
|
205
|
+
name: 'Admin',
|
|
206
|
+
description: 'Administrator role'
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
schema.seed('Role', {
|
|
210
|
+
name: 'User',
|
|
211
|
+
description: 'Regular user role'
|
|
212
|
+
});
|
|
213
|
+
},
|
|
214
|
+
|
|
215
|
+
down: function(table, schema) {
|
|
216
|
+
schema.dropTable(table.Role);
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Bulk Seeding (More Efficient)
|
|
222
|
+
|
|
223
|
+
```javascript
|
|
224
|
+
module.exports = {
|
|
225
|
+
up: function(table, schema) {
|
|
226
|
+
schema.createTable(table.User);
|
|
227
|
+
|
|
228
|
+
// Bulk insert multiple records at once
|
|
229
|
+
schema.bulkSeed('User', [
|
|
230
|
+
{ name: 'Alice', email: 'alice@example.com', age: 25 },
|
|
231
|
+
{ name: 'Bob', email: 'bob@example.com', age: 30 },
|
|
232
|
+
{ name: 'Charlie', email: 'charlie@example.com', age: 35 },
|
|
233
|
+
{ name: 'Diana', email: 'diana@example.com', age: 28 }
|
|
234
|
+
]);
|
|
235
|
+
},
|
|
236
|
+
|
|
237
|
+
down: function(table, schema) {
|
|
238
|
+
schema.dropTable(table.User);
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Idempotent Seeds
|
|
244
|
+
|
|
245
|
+
Seeds are automatically idempotent (can run multiple times safely):
|
|
246
|
+
|
|
247
|
+
**SQLite**: Uses `INSERT OR IGNORE`
|
|
248
|
+
**MySQL**: Uses `INSERT IGNORE`
|
|
249
|
+
**PostgreSQL**: Uses `INSERT ... ON CONFLICT DO NOTHING`
|
|
250
|
+
|
|
251
|
+
**Note**: Idempotent seeding requires a unique constraint or primary key.
|
|
252
|
+
|
|
253
|
+
### Conditional Seeding
|
|
254
|
+
|
|
255
|
+
```javascript
|
|
256
|
+
module.exports = {
|
|
257
|
+
up: function(table, schema) {
|
|
258
|
+
schema.createTable(table.Setting);
|
|
259
|
+
|
|
260
|
+
// Only seed if running on specific database
|
|
261
|
+
if(schema.context.isPostgres){
|
|
262
|
+
schema.seed('Setting', {
|
|
263
|
+
key: 'postgres_feature',
|
|
264
|
+
value: 'enabled'
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if(schema.context.isMySQL){
|
|
269
|
+
schema.seed('Setting', {
|
|
270
|
+
key: 'mysql_feature',
|
|
271
|
+
value: 'enabled'
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
|
|
276
|
+
down: function(table, schema) {
|
|
277
|
+
schema.dropTable(table.Setting);
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
## Migration Commands
|
|
283
|
+
|
|
284
|
+
### Create a New Migration
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
masterrecord add-migration <MigrationName> <ContextName>
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
Example:
|
|
291
|
+
```bash
|
|
292
|
+
masterrecord add-migration AddUserProfile context
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### Run Migrations
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
masterrecord migrate <ContextName>
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### Check Migration Status
|
|
302
|
+
|
|
303
|
+
Migrations are tracked in the snapshot file:
|
|
304
|
+
```
|
|
305
|
+
app/models/db/migrations/context_contextSnapShot.json
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
## Examples
|
|
309
|
+
|
|
310
|
+
### Example 1: E-Commerce Database
|
|
311
|
+
|
|
312
|
+
```javascript
|
|
313
|
+
// migrations/20250111_CreateECommerce.js
|
|
314
|
+
module.exports = {
|
|
315
|
+
up: function(table, schema) {
|
|
316
|
+
// Create tables
|
|
317
|
+
schema.createTable(table.User);
|
|
318
|
+
schema.createTable(table.Product);
|
|
319
|
+
schema.createTable(table.Order);
|
|
320
|
+
schema.createTable(table.OrderItem);
|
|
321
|
+
|
|
322
|
+
// Seed categories
|
|
323
|
+
schema.bulkSeed('Category', [
|
|
324
|
+
{ name: 'Electronics', slug: 'electronics' },
|
|
325
|
+
{ name: 'Clothing', slug: 'clothing' },
|
|
326
|
+
{ name: 'Books', slug: 'books' }
|
|
327
|
+
]);
|
|
328
|
+
|
|
329
|
+
// Seed admin user
|
|
330
|
+
schema.seed('User', {
|
|
331
|
+
name: 'Admin',
|
|
332
|
+
email: 'admin@shop.com',
|
|
333
|
+
role: 'admin',
|
|
334
|
+
created_at: new Date()
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
// Seed sample products
|
|
338
|
+
schema.bulkSeed('Product', [
|
|
339
|
+
{
|
|
340
|
+
name: 'Laptop',
|
|
341
|
+
price: 999.99,
|
|
342
|
+
category_id: 1,
|
|
343
|
+
stock: 50
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
name: 'T-Shirt',
|
|
347
|
+
price: 19.99,
|
|
348
|
+
category_id: 2,
|
|
349
|
+
stock: 200
|
|
350
|
+
}
|
|
351
|
+
]);
|
|
352
|
+
},
|
|
353
|
+
|
|
354
|
+
down: function(table, schema) {
|
|
355
|
+
schema.dropTable(table.OrderItem);
|
|
356
|
+
schema.dropTable(table.Order);
|
|
357
|
+
schema.dropTable(table.Product);
|
|
358
|
+
schema.dropTable(table.Category);
|
|
359
|
+
schema.dropTable(table.User);
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
### Example 2: Blog with PostgreSQL-Specific Features
|
|
365
|
+
|
|
366
|
+
```javascript
|
|
367
|
+
// migrations/20250111_CreateBlog.js
|
|
368
|
+
module.exports = {
|
|
369
|
+
up: function(table, schema) {
|
|
370
|
+
schema.createTable(table.Author);
|
|
371
|
+
schema.createTable(table.Post);
|
|
372
|
+
schema.createTable(table.Comment);
|
|
373
|
+
|
|
374
|
+
// PostgreSQL-specific: JSON metadata
|
|
375
|
+
if(schema.context.isPostgres){
|
|
376
|
+
schema.addColumn({
|
|
377
|
+
tableName: 'Post',
|
|
378
|
+
name: 'metadata',
|
|
379
|
+
type: 'jsonb' // PostgreSQL binary JSON
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
schema.addColumn({
|
|
383
|
+
tableName: 'Post',
|
|
384
|
+
name: 'post_id',
|
|
385
|
+
type: 'uuid' // PostgreSQL native UUID
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Seed authors
|
|
390
|
+
schema.bulkSeed('Author', [
|
|
391
|
+
{ name: 'John Doe', email: 'john@blog.com', bio: 'Tech blogger' },
|
|
392
|
+
{ name: 'Jane Smith', email: 'jane@blog.com', bio: 'Travel writer' }
|
|
393
|
+
]);
|
|
394
|
+
|
|
395
|
+
// Seed initial posts
|
|
396
|
+
schema.seed('Post', {
|
|
397
|
+
title: 'Welcome to Our Blog',
|
|
398
|
+
content: 'This is our first post!',
|
|
399
|
+
author_id: 1,
|
|
400
|
+
published: true,
|
|
401
|
+
created_at: new Date()
|
|
402
|
+
});
|
|
403
|
+
},
|
|
404
|
+
|
|
405
|
+
down: function(table, schema) {
|
|
406
|
+
schema.dropTable(table.Comment);
|
|
407
|
+
schema.dropTable(table.Post);
|
|
408
|
+
schema.dropTable(table.Author);
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
### Example 3: Multi-Database Migration
|
|
414
|
+
|
|
415
|
+
```javascript
|
|
416
|
+
// migrations/20250111_AddUserPreferences.js
|
|
417
|
+
module.exports = {
|
|
418
|
+
up: function(table, schema) {
|
|
419
|
+
// Works across all databases
|
|
420
|
+
schema.addColumn({
|
|
421
|
+
tableName: 'User',
|
|
422
|
+
name: 'preferences',
|
|
423
|
+
type: 'text' // JSON stored as text for compatibility
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
// Database-specific optimizations
|
|
427
|
+
if(schema.context.isPostgres){
|
|
428
|
+
// PostgreSQL: Use native JSONB for better performance
|
|
429
|
+
schema.alterColumn({
|
|
430
|
+
tableName: 'User',
|
|
431
|
+
table: {
|
|
432
|
+
name: 'preferences',
|
|
433
|
+
type: 'jsonb'
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Seed default preferences
|
|
439
|
+
const defaultPrefs = JSON.stringify({
|
|
440
|
+
theme: 'light',
|
|
441
|
+
notifications: true,
|
|
442
|
+
language: 'en'
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
schema.seed('User', {
|
|
446
|
+
name: 'Demo User',
|
|
447
|
+
email: 'demo@example.com',
|
|
448
|
+
preferences: defaultPrefs
|
|
449
|
+
});
|
|
450
|
+
},
|
|
451
|
+
|
|
452
|
+
down: function(table, schema) {
|
|
453
|
+
schema.dropColumn({
|
|
454
|
+
tableName: 'User',
|
|
455
|
+
name: 'preferences'
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
## Database-Specific Features
|
|
462
|
+
|
|
463
|
+
### PostgreSQL Features
|
|
464
|
+
|
|
465
|
+
```javascript
|
|
466
|
+
module.exports = {
|
|
467
|
+
up: function(table, schema) {
|
|
468
|
+
if(schema.context.isPostgres){
|
|
469
|
+
// JSONB for better JSON performance
|
|
470
|
+
schema.addColumn({
|
|
471
|
+
tableName: 'User',
|
|
472
|
+
name: 'settings',
|
|
473
|
+
type: 'jsonb'
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
// Native UUID support
|
|
477
|
+
schema.addColumn({
|
|
478
|
+
tableName: 'User',
|
|
479
|
+
name: 'uuid',
|
|
480
|
+
type: 'uuid'
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
// Native BOOLEAN type
|
|
484
|
+
schema.addColumn({
|
|
485
|
+
tableName: 'User',
|
|
486
|
+
name: 'active',
|
|
487
|
+
type: 'boolean',
|
|
488
|
+
default: true
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
// BYTEA for binary data
|
|
492
|
+
schema.addColumn({
|
|
493
|
+
tableName: 'User',
|
|
494
|
+
name: 'avatar',
|
|
495
|
+
type: 'binary' // Maps to BYTEA in PostgreSQL
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
},
|
|
499
|
+
|
|
500
|
+
down: function(table, schema) {
|
|
501
|
+
// Cleanup
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
### MySQL Features
|
|
507
|
+
|
|
508
|
+
```javascript
|
|
509
|
+
module.exports = {
|
|
510
|
+
up: function(table, schema) {
|
|
511
|
+
if(schema.context.isMySQL){
|
|
512
|
+
// JSON type
|
|
513
|
+
schema.addColumn({
|
|
514
|
+
tableName: 'User',
|
|
515
|
+
name: 'settings',
|
|
516
|
+
type: 'json'
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
// TINYINT for booleans (0/1)
|
|
520
|
+
schema.addColumn({
|
|
521
|
+
tableName: 'User',
|
|
522
|
+
name: 'active',
|
|
523
|
+
type: 'boolean' // Maps to TINYINT
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
// AUTO_INCREMENT
|
|
527
|
+
// (handled automatically for primary keys)
|
|
528
|
+
}
|
|
529
|
+
},
|
|
530
|
+
|
|
531
|
+
down: function(table, schema) {
|
|
532
|
+
// Cleanup
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
### SQLite Features
|
|
538
|
+
|
|
539
|
+
```javascript
|
|
540
|
+
module.exports = {
|
|
541
|
+
up: function(table, schema) {
|
|
542
|
+
if(schema.context.isSQLite){
|
|
543
|
+
// TEXT for everything
|
|
544
|
+
// INTEGER for booleans (0/1)
|
|
545
|
+
// AUTOINCREMENT for primary keys
|
|
546
|
+
|
|
547
|
+
// SQLite requires table rebuild for certain alterations
|
|
548
|
+
// MasterRecord handles this automatically
|
|
549
|
+
}
|
|
550
|
+
},
|
|
551
|
+
|
|
552
|
+
down: function(table, schema) {
|
|
553
|
+
// Cleanup
|
|
554
|
+
}
|
|
555
|
+
};
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
## Best Practices
|
|
559
|
+
|
|
560
|
+
### 1. Always Provide Down Migrations
|
|
561
|
+
|
|
562
|
+
```javascript
|
|
563
|
+
// ✅ GOOD
|
|
564
|
+
module.exports = {
|
|
565
|
+
up: function(table, schema) {
|
|
566
|
+
schema.createTable(table.User);
|
|
567
|
+
},
|
|
568
|
+
down: function(table, schema) {
|
|
569
|
+
schema.dropTable(table.User);
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
// ❌ BAD
|
|
574
|
+
module.exports = {
|
|
575
|
+
up: function(table, schema) {
|
|
576
|
+
schema.createTable(table.User);
|
|
577
|
+
},
|
|
578
|
+
down: function(table, schema) {
|
|
579
|
+
// Empty - can't rollback!
|
|
580
|
+
}
|
|
581
|
+
};
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
### 2. Use Bulk Seeding for Multiple Records
|
|
585
|
+
|
|
586
|
+
```javascript
|
|
587
|
+
// ✅ GOOD - Single query
|
|
588
|
+
schema.bulkSeed('User', [
|
|
589
|
+
{ name: 'Alice', email: 'alice@example.com' },
|
|
590
|
+
{ name: 'Bob', email: 'bob@example.com' }
|
|
591
|
+
]);
|
|
592
|
+
|
|
593
|
+
// ❌ BAD - Multiple queries
|
|
594
|
+
schema.seed('User', { name: 'Alice', email: 'alice@example.com' });
|
|
595
|
+
schema.seed('User', { name: 'Bob', email: 'bob@example.com' });
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
### 3. Test Migrations on All Databases
|
|
599
|
+
|
|
600
|
+
If your app supports multiple databases, test migrations on each:
|
|
601
|
+
|
|
602
|
+
```bash
|
|
603
|
+
# Test on SQLite
|
|
604
|
+
DATABASE_TYPE=sqlite masterrecord migrate context
|
|
605
|
+
|
|
606
|
+
# Test on MySQL
|
|
607
|
+
DATABASE_TYPE=mysql masterrecord migrate context
|
|
608
|
+
|
|
609
|
+
# Test on PostgreSQL
|
|
610
|
+
DATABASE_TYPE=postgres masterrecord migrate context
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
### 4. Keep Migrations Small and Focused
|
|
614
|
+
|
|
615
|
+
```javascript
|
|
616
|
+
// ✅ GOOD - One logical change
|
|
617
|
+
// Migration: AddUserPhoneNumber
|
|
618
|
+
schema.addColumn({
|
|
619
|
+
tableName: 'User',
|
|
620
|
+
name: 'phone',
|
|
621
|
+
type: 'string'
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
// ❌ BAD - Too many unrelated changes
|
|
625
|
+
schema.addColumn({ tableName: 'User', name: 'phone', type: 'string' });
|
|
626
|
+
schema.addColumn({ tableName: 'Post', name: 'views', type: 'integer' });
|
|
627
|
+
schema.createTable(table.Comment);
|
|
628
|
+
schema.seed('Setting', { key: 'version', value: '2.0' });
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
### 5. Use Timestamps for Migration Names
|
|
632
|
+
|
|
633
|
+
MasterRecord automatically adds timestamps to migration files:
|
|
634
|
+
```
|
|
635
|
+
20250111_143052_CreateUserTable.js
|
|
636
|
+
20250111_150023_AddUserEmail.js
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
This ensures migrations run in the correct order.
|
|
640
|
+
|
|
641
|
+
## Troubleshooting
|
|
642
|
+
|
|
643
|
+
### Migration Not Found
|
|
644
|
+
|
|
645
|
+
**Error**: "Cannot find migration file"
|
|
646
|
+
|
|
647
|
+
**Solution**: Ensure migration is in the correct directory:
|
|
648
|
+
```
|
|
649
|
+
app/models/db/migrations/
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
### Context Not Found
|
|
653
|
+
|
|
654
|
+
**Error**: "Cannot find context"
|
|
655
|
+
|
|
656
|
+
**Solution**: Provide full path or context name:
|
|
657
|
+
```bash
|
|
658
|
+
masterrecord migrate context
|
|
659
|
+
# or
|
|
660
|
+
masterrecord migrate ./app/models/context.js
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
### Seed Data Not Idempotent
|
|
664
|
+
|
|
665
|
+
**Error**: Duplicate key violations on repeated migrations
|
|
666
|
+
|
|
667
|
+
**Solution**: Ensure tables have primary keys or unique constraints:
|
|
668
|
+
```javascript
|
|
669
|
+
schema.createTable(table.User); // User entity must have primary key
|
|
670
|
+
schema.seed('User', { id: 1, name: 'Admin' }); // Include PK in seed
|
|
671
|
+
```
|
|
672
|
+
|
|
673
|
+
### PostgreSQL Permission Errors
|
|
674
|
+
|
|
675
|
+
**Error**: "permission denied for table"
|
|
676
|
+
|
|
677
|
+
**Solution**: Ensure your PostgreSQL user has appropriate permissions:
|
|
678
|
+
```sql
|
|
679
|
+
GRANT ALL PRIVILEGES ON DATABASE mydb TO myuser;
|
|
680
|
+
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO myuser;
|
|
681
|
+
```
|
|
682
|
+
|
|
683
|
+
## Version Compatibility
|
|
684
|
+
|
|
685
|
+
- **MasterRecord**: 0.3.0+
|
|
686
|
+
- **PostgreSQL**: 9.6+ (tested with 12+, 13+, 14+, 15+, 16+)
|
|
687
|
+
- **MySQL**: 5.7+ (tested with 8.0+)
|
|
688
|
+
- **SQLite**: 3.x (any recent version)
|
|
689
|
+
- **Node.js**: 14+ (async/await support required)
|
|
690
|
+
|
|
691
|
+
## Additional Resources
|
|
692
|
+
|
|
693
|
+
- [PostgreSQL Setup Guide](./POSTGRESQL_SETUP.md)
|
|
694
|
+
- [Entity Definitions](./ENTITIES.md)
|
|
695
|
+
- [MasterRecord API Reference](./API_REFERENCE.md)
|
|
696
|
+
|
|
697
|
+
## License
|
|
698
|
+
|
|
699
|
+
MIT License - see LICENSE file for details
|