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.
Files changed (38) hide show
  1. package/.claude/settings.local.json +20 -1
  2. package/Entity/entityModel.js +6 -0
  3. package/Entity/entityTrackerModel.js +20 -3
  4. package/Entity/fieldTransformer.js +266 -0
  5. package/Migrations/migrationMySQLQuery.js +145 -1
  6. package/Migrations/migrationPostgresQuery.js +402 -0
  7. package/Migrations/migrationSQLiteQuery.js +145 -1
  8. package/Migrations/schema.js +131 -28
  9. package/QueryLanguage/queryMethods.js +193 -15
  10. package/QueryLanguage/queryParameters.js +136 -0
  11. package/QueryLanguage/queryScript.js +13 -4
  12. package/SQLLiteEngine.js +331 -20
  13. package/context.js +91 -14
  14. package/docs/INCLUDES_CLARIFICATION.md +202 -0
  15. package/docs/METHODS_REFERENCE.md +184 -0
  16. package/docs/MIGRATIONS_GUIDE.md +699 -0
  17. package/docs/POSTGRESQL_SETUP.md +415 -0
  18. package/examples/jsonArrayTransformer.js +215 -0
  19. package/mySQLEngine.js +273 -17
  20. package/package.json +3 -3
  21. package/postgresEngine.js +600 -483
  22. package/postgresSyncConnect.js +209 -0
  23. package/readme.md +1046 -416
  24. package/test/anyCommaStringTest.js +237 -0
  25. package/test/anyMethodTest.js +176 -0
  26. package/test/findByIdTest.js +227 -0
  27. package/test/includesFeatureTest.js +183 -0
  28. package/test/includesTransformTest.js +110 -0
  29. package/test/newMethodTest.js +330 -0
  30. package/test/newMethodUnitTest.js +320 -0
  31. package/test/parameterizedPlaceholderTest.js +159 -0
  32. package/test/postgresEngineTest.js +463 -0
  33. package/test/postgresIntegrationTest.js +381 -0
  34. package/test/securityTest.js +268 -0
  35. package/test/singleDollarPlaceholderTest.js +238 -0
  36. package/test/transformerTest.js +287 -0
  37. package/test/verifyFindById.js +169 -0
  38. 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