masterrecord 0.3.47 → 0.3.49
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/Migrations/migrationTemplate.js +12 -12
- package/Migrations/migrations.js +6 -11
- package/Migrations/schema.js +63 -64
- package/context.js +1 -1
- package/package.json +1 -1
- package/readme.md +9 -728
- package/test/v0.3.34-bug-fixes-test.js +50 -8
package/readme.md
CHANGED
|
@@ -3369,734 +3369,15 @@ user.name = null; // Error if name is { nullable: false }
|
|
|
3369
3369
|
|
|
3370
3370
|
## Changelog
|
|
3371
3371
|
|
|
3372
|
-
### Version 0.3.
|
|
3372
|
+
### Version 0.3.49 (2026-02-21) - FIX: Duplicate Index Creation for New Tables
|
|
3373
3373
|
|
|
3374
|
-
#### Bug Fixed: `
|
|
3375
|
-
- **FIXED**:
|
|
3376
|
-
- **
|
|
3377
|
-
- **
|
|
3378
|
-
- **
|
|
3374
|
+
#### Bug Fixed: `createIndex` called twice for new tables during `update-database`
|
|
3375
|
+
- **FIXED**: When running `update-database` with a new table that has indexes, the indexes were created twice — once by `createTable()` in schema.js (which iterates column `.index()` definitions and `__compositeIndexes`), and again by explicit `createIndex()`/`createCompositeIndex()` calls in the generated migration file
|
|
3376
|
+
- **Symptom**: MySQL error `Duplicate key name 'idx_...'` during InitialCreate migrations
|
|
3377
|
+
- **Root Cause**: `migrations.js` `#findNewIndexes()` and `#findNewCompositeIndexes()` added index operations for ALL tables, including brand new ones where `createTable()` already handles them
|
|
3378
|
+
- **Solution**: Skip index generation in migration template for new tables (`item.newTables.length > 0`). For existing tables getting new indexes, explicit `createIndex` calls are still generated correctly.
|
|
3379
3379
|
|
|
3380
3380
|
#### Files Modified
|
|
3381
|
-
1. **`
|
|
3382
|
-
2. **`
|
|
3383
|
-
|
|
3384
|
-
### Version 0.3.42 (2026-02-20) - FIX: Migration System + add-migration Improvements
|
|
3385
|
-
|
|
3386
|
-
#### Bug Fixed: `update-database` failing for MySQL/PostgreSQL with "Cannot read properties of null"
|
|
3387
|
-
- **FIXED**: Running `masterrecord update-database` with MySQL or PostgreSQL would crash with `Cannot read properties of null (reading 'tableExists')`
|
|
3388
|
-
- **Root Cause**: The migration `schema.js` constructor creates a new context instance, but MySQL/PostgreSQL database initialization is async. The `_SQLEngine` property was still `null` when `createTable` tried to use it because the async pool init hadn't completed yet.
|
|
3389
|
-
- **Solution**: Store the async init promise as `_initPromise` on the context instance. Added `_ensureReady()` method to `schema.js` that awaits it. Called in both `init()` and `createTable()` (safety net for existing migrations).
|
|
3390
|
-
- **Also fixed**: Typo in `schema.js` — `require('masterrecord/mySQLAsyncConnect')` corrected to `require('masterrecord/mySQLConnect')`
|
|
3391
|
-
- **Also fixed**: `init()` now properly `await`s `createDatabase()` instead of fire-and-forget
|
|
3392
|
-
|
|
3393
|
-
#### Bug Fixed: `add-migration` creating unnecessary database connections
|
|
3394
|
-
- **FIXED**: Running `masterrecord add-migration` would open a real database connection (creating SQLite files on disk, or connecting to MySQL/PostgreSQL) even though it only needs entity schemas and seed data to generate migration files
|
|
3395
|
-
- **Solution**: Added schema-only mode (`MASTERRECORD_SCHEMA_ONLY` env var) that sets database type flags but skips all DB initialization
|
|
3396
|
-
|
|
3397
|
-
#### Files Modified
|
|
3398
|
-
1. **`Migrations/schema.js`** - Added `_ensureReady()`, made `init()` async with proper awaits, fixed `mySQLConnect` require
|
|
3399
|
-
2. **`Migrations/migrationTemplate.js`** - New migrations now `await this.init(table)`
|
|
3400
|
-
3. **`Migrations/cli.js`** - Schema-only mode for `add-migration`
|
|
3401
|
-
4. **`context.js`** - Store `_initPromise` for MySQL/PostgreSQL async init, schema-only mode in `env()`
|
|
3402
|
-
5. **`package.json`** - Updated to v0.3.42
|
|
3403
|
-
|
|
3404
|
-
### Version 0.3.39 (2026-02-09) - CRITICAL BUG FIX: Foreign Key String Values
|
|
3405
|
-
|
|
3406
|
-
#### Bug Fixed: Foreign Key Fields Silently Ignoring String Values
|
|
3407
|
-
- **FIXED**: Critical bug where string values assigned to foreign key fields were silently excluded from INSERT statements
|
|
3408
|
-
- **Problem**: When you assign `orgRole.user_id = "2"` (string), MasterRecord excluded it from INSERT, causing NOT NULL constraint failures
|
|
3409
|
-
- **Root Cause**: INSERT builder only checked navigation property name (`User`), not foreign key field name (`user_id`)
|
|
3410
|
-
- **Impact**: Common in real-world apps where IDs come from JWT tokens, HTTP requests, or authService (returns string IDs)
|
|
3411
|
-
|
|
3412
|
-
#### What Was Happening (Before v0.3.39)
|
|
3413
|
-
```javascript
|
|
3414
|
-
// User assigns string value to foreign key
|
|
3415
|
-
orgRole.user_id = "2"; // ← STRING (from currentUser.id)
|
|
3416
|
-
orgRole.organization_id = 8; // ← NUMBER (from database)
|
|
3417
|
-
orgRole.role = 'org_admin';
|
|
3418
|
-
|
|
3419
|
-
await userContext.saveChanges();
|
|
3420
|
-
// ❌ Generated SQL: INSERT INTO [UserOrganizationRole] ([role]) VALUES ('org_admin')
|
|
3421
|
-
// ❌ Error: NOT NULL constraint failed: UserOrganizationRole.user_id
|
|
3422
|
-
```
|
|
3423
|
-
|
|
3424
|
-
**Why It Failed:**
|
|
3425
|
-
- `belongsTo('User', 'user_id')` creates property `User` with `foreignKey: 'user_id'`
|
|
3426
|
-
- INSERT builder looked for `fields['User']` (navigation property)
|
|
3427
|
-
- User set `fields['user_id']` (foreign key field name)
|
|
3428
|
-
- Field not found → silently skipped → INSERT failed
|
|
3429
|
-
|
|
3430
|
-
#### The Fix (v0.3.39)
|
|
3431
|
-
Updated `_buildSQLInsertObjectParameterized` in all database engines:
|
|
3432
|
-
- Now checks BOTH navigation property name AND foreign key field name
|
|
3433
|
-
- Auto-converts string values to integers for integer foreign key fields
|
|
3434
|
-
- Maintains backward compatibility (setting navigation property still works)
|
|
3435
|
-
|
|
3436
|
-
```javascript
|
|
3437
|
-
// After fix - both patterns work:
|
|
3438
|
-
orgRole.User = 2; // ✅ Works (navigation property)
|
|
3439
|
-
orgRole.user_id = "2"; // ✅ Works (foreign key field, auto-converted to integer)
|
|
3440
|
-
```
|
|
3441
|
-
|
|
3442
|
-
#### Files Modified
|
|
3443
|
-
1. **SQLLiteEngine.js** (lines 1127-1137) - Added foreign key field lookup
|
|
3444
|
-
2. **mySQLEngine.js** (lines 654-664) - Added foreign key field lookup
|
|
3445
|
-
3. **postgresEngine.js** (lines 601-611) - Added foreign key field lookup
|
|
3446
|
-
4. **test/foreign-key-string-value-test.js** (NEW) - 8 comprehensive tests
|
|
3447
|
-
5. **package.json** - Updated to v0.3.39
|
|
3448
|
-
6. **readme.md** - Added changelog
|
|
3449
|
-
|
|
3450
|
-
#### Test Results
|
|
3451
|
-
- **8 new tests** - All passing ✅
|
|
3452
|
-
1. String foreign key value included in INSERT ✅
|
|
3453
|
-
2. Number foreign key value still works ✅
|
|
3454
|
-
3. Mixed string and number foreign keys ✅
|
|
3455
|
-
4. String with leading zeros (e.g., "007" → 7) ✅
|
|
3456
|
-
5. Invalid strings throw error (not silent failure) ✅
|
|
3457
|
-
6. Empty strings throw error ✅
|
|
3458
|
-
7. Backward compatible (navigation property still works) ✅
|
|
3459
|
-
8. Prefers navigation property if both set ✅
|
|
3460
|
-
|
|
3461
|
-
#### Real-World Example: authService Returns String IDs
|
|
3462
|
-
```javascript
|
|
3463
|
-
// authService.js returns:
|
|
3464
|
-
const currentUser = {
|
|
3465
|
-
id: "2", // ← STRING (from String(obj.user.id))
|
|
3466
|
-
email: "customer1@bookbag.ai",
|
|
3467
|
-
system_role: "system_user"
|
|
3468
|
-
};
|
|
3469
|
-
|
|
3470
|
-
// User creates association:
|
|
3471
|
-
const orgRole = new UserOrganizationRole();
|
|
3472
|
-
orgRole.user_id = currentUser.id; // ← Before: silently skipped. After: auto-converted to 2
|
|
3473
|
-
orgRole.organization_id = newOrg.id; // ← NUMBER from database
|
|
3474
|
-
orgRole.role = 'org_admin';
|
|
3475
|
-
|
|
3476
|
-
await userContext.saveChanges(); // ✅ Now works!
|
|
3477
|
-
```
|
|
3478
|
-
|
|
3479
|
-
#### Impact
|
|
3480
|
-
- ✅ **Auto-converts** string foreign keys to integers (with validation)
|
|
3481
|
-
- ✅ **Clear errors** for invalid strings (not silent failures)
|
|
3482
|
-
- ✅ **Backward compatible** - navigation property pattern still works
|
|
3483
|
-
- ✅ **Works across all databases** (SQLite, MySQL, PostgreSQL)
|
|
3484
|
-
- ✅ **Matches real-world usage** where IDs are often strings
|
|
3485
|
-
|
|
3486
|
-
#### Upgrade Path
|
|
3487
|
-
```bash
|
|
3488
|
-
npm install -g masterrecord@0.3.39
|
|
3489
|
-
```
|
|
3490
|
-
No code changes needed - automatic fix! If you have workarounds like `parseInt(currentUser.id)`, you can now remove them (but leaving them is harmless).
|
|
3491
|
-
|
|
3492
|
-
---
|
|
3493
|
-
|
|
3494
|
-
### Version 0.3.38 (2026-02-06) - GLOBAL MODEL REGISTRY (UX FIX)
|
|
3495
|
-
|
|
3496
|
-
#### Enhancement: Eliminates Confusing CLI Warnings
|
|
3497
|
-
- **FIXED**: Confusing warnings during normal CLI operation when generating migrations
|
|
3498
|
-
- **Previous Behavior**: v0.3.36/0.3.37 correctly detected duplicate `dbset()` calls and emitted warnings
|
|
3499
|
-
- **Problem**: CLI instantiates the same context class multiple times to inspect schema
|
|
3500
|
-
- **Impact**: Users saw warnings during normal operation: `"Warning: dbset() called multiple times for table 'User'..."`
|
|
3501
|
-
- **User Confusion**: Warnings appeared even when code was correct, making users think they did something wrong
|
|
3502
|
-
|
|
3503
|
-
#### Implementation - Global Model Registry
|
|
3504
|
-
**The Solution** (`context.js`)
|
|
3505
|
-
- Added static `_globalModelRegistry` property to track registered models per context class
|
|
3506
|
-
- Structure: `{ 'userContext': Set(['User', 'Auth', 'Settings']), 'qaContext': Set([...]) }`
|
|
3507
|
-
- Each context instance checks if it's the first instance via `__isFirstInstance` flag
|
|
3508
|
-
- Warnings only appear on the first instance of a context class (genuine bugs)
|
|
3509
|
-
- Subsequent instances (CLI pattern) are silent since they're expected
|
|
3510
|
-
|
|
3511
|
-
**How It Works:**
|
|
3512
|
-
|
|
3513
|
-
1. **First Instance** (constructor execution):
|
|
3514
|
-
```javascript
|
|
3515
|
-
const ctx1 = new userContext();
|
|
3516
|
-
// __isFirstInstance = true (global registry empty)
|
|
3517
|
-
// dbset(User) - adds User to global registry
|
|
3518
|
-
// dbset(User) again - WARNS (duplicate in same constructor)
|
|
3519
|
-
// dbset(Auth) - adds Auth to global registry
|
|
3520
|
-
```
|
|
3521
|
-
|
|
3522
|
-
2. **Subsequent Instances** (CLI creates multiple):
|
|
3523
|
-
```javascript
|
|
3524
|
-
const ctx2 = new userContext();
|
|
3525
|
-
// __isFirstInstance = false (global registry has User, Auth)
|
|
3526
|
-
// dbset(User) - no warning (expected pattern)
|
|
3527
|
-
// dbset(User) again - no warning (expected pattern)
|
|
3528
|
-
// dbset(Auth) - no warning (expected pattern)
|
|
3529
|
-
```
|
|
3530
|
-
|
|
3531
|
-
3. **Duplicate Detection Still Works**:
|
|
3532
|
-
- If user's constructor has `dbset(User)` called twice, the first instance warns
|
|
3533
|
-
- This guides users to fix their code (remove the duplicate)
|
|
3534
|
-
- After fixing, all future CLI operations are silent
|
|
3535
|
-
|
|
3536
|
-
**Benefits:**
|
|
3537
|
-
- ✅ **Clean CLI Output**: No spurious warnings during `masterrecord add-migration`
|
|
3538
|
-
- ✅ **Genuine Bug Detection**: Still warns about actual duplicates in user code
|
|
3539
|
-
- ✅ **Better UX**: Users no longer confused by normal operation warnings
|
|
3540
|
-
- ✅ **Backward Compatible**: Existing code continues to work
|
|
3541
|
-
- ✅ **Industry-Standard Pattern**: Matches how TypeORM, Sequelize, Mongoose handle multiple instances
|
|
3542
|
-
|
|
3543
|
-
**Files Modified:**
|
|
3544
|
-
1. `context.js` - Added `_globalModelRegistry` static property, `__isFirstInstance` instance flag, updated `dbset()` logic
|
|
3545
|
-
2. `test/global-model-registry-test.js` (NEW) - 15 comprehensive tests covering:
|
|
3546
|
-
- Multiple context instances (CLI pattern) - no warnings ✅
|
|
3547
|
-
- Genuine duplicates in constructor - warns once ✅
|
|
3548
|
-
- Multiple context classes with same models - no warnings ✅
|
|
3549
|
-
- Registry isolation between context classes ✅
|
|
3550
|
-
- Edge cases (empty contexts, large contexts, mixed registration) ✅
|
|
3551
|
-
3. `package.json` - Updated version to 0.3.38
|
|
3552
|
-
4. `readme.md` - Added changelog entry
|
|
3553
|
-
|
|
3554
|
-
**Test Results:**
|
|
3555
|
-
- **15 new tests** - All passing ✅
|
|
3556
|
-
- Tests verify CLI pattern (3 instances) produces zero warnings
|
|
3557
|
-
- Tests verify genuine duplicates still warn on first instance only
|
|
3558
|
-
- Tests verify different context classes have separate registries
|
|
3559
|
-
- Tests verify large contexts (50 models) work without warnings
|
|
3560
|
-
|
|
3561
|
-
**Upgrade Path:**
|
|
3562
|
-
```bash
|
|
3563
|
-
npm install -g masterrecord@0.3.38
|
|
3564
|
-
```
|
|
3565
|
-
No code changes needed - automatic improvement to CLI experience.
|
|
3566
|
-
|
|
3567
|
-
**Real-World Example:**
|
|
3568
|
-
|
|
3569
|
-
Before v0.3.38:
|
|
3570
|
-
```bash
|
|
3571
|
-
$ masterrecord add-migration CreateUsers userContext
|
|
3572
|
-
Warning: dbset() called multiple times for table 'User' - updating existing registration
|
|
3573
|
-
Warning: dbset() called multiple times for table 'Auth' - updating existing registration
|
|
3574
|
-
Warning: dbset() called multiple times for table 'Settings' - updating existing registration
|
|
3575
|
-
✓ Migration 'CreateUsers' created successfully
|
|
3576
|
-
```
|
|
3577
|
-
|
|
3578
|
-
After v0.3.38:
|
|
3579
|
-
```bash
|
|
3580
|
-
$ masterrecord add-migration CreateUsers userContext
|
|
3581
|
-
✓ Migration 'CreateUsers' created successfully
|
|
3582
|
-
```
|
|
3583
|
-
|
|
3584
|
-
---
|
|
3585
|
-
|
|
3586
|
-
### Version 0.3.36 (2026-02-06) - ROOT CAUSE FIX + CONFIG DISCOVERY FIX
|
|
3587
|
-
|
|
3588
|
-
#### Critical Bug Fix #1: Duplicate Entities and Seed Data - Complete Resolution
|
|
3589
|
-
- **FIXED**: Root cause of duplicate entities and seed data in migrations
|
|
3590
|
-
- **Root Cause**: User contexts calling `dbset(Entity)` then later `dbset(Entity).seed(data)` caused:
|
|
3591
|
-
- Entity registered twice in `__entities` array
|
|
3592
|
-
- Seed data duplicated in `__contextSeedData`
|
|
3593
|
-
- Snapshots containing duplicate table definitions
|
|
3594
|
-
- Migrations generating 2x operations (e.g., 18 template records instead of 9)
|
|
3595
|
-
|
|
3596
|
-
#### Implementation - EF Core HasData Semantics
|
|
3597
|
-
**Fix #1: Entity Deduplication in `dbset()`** (`context.js` lines 1025-1037)
|
|
3598
|
-
- Added `findIndex()` check before adding entities to `__entities` array
|
|
3599
|
-
- If entity with same table name exists, updates it instead of adding duplicate
|
|
3600
|
-
- Emits warning: `"Warning: dbset() called multiple times for table 'X' - updating existing registration"`
|
|
3601
|
-
|
|
3602
|
-
**Fix #2: Seed Data Deduplication in `#addSeedData()`** (`context.js` lines 1190-1223)
|
|
3603
|
-
- Implements Entity Framework Core `HasData` semantics:
|
|
3604
|
-
- **Update**: If record with same primary key exists, merge/update fields
|
|
3605
|
-
- **Insert**: If primary key doesn't exist or is undefined, append record
|
|
3606
|
-
- Upserts by primary key (supports custom primary keys like `uuid`)
|
|
3607
|
-
- Emits warning: `"Warning: seed() called multiple times for table 'X' - using upsert semantics..."`
|
|
3608
|
-
- User requested this approach to match EF Core behavior
|
|
3609
|
-
|
|
3610
|
-
#### Critical Bug Fix #2: Config File Discovery - Glob Pattern Too Broad
|
|
3611
|
-
- **FIXED**: Glob pattern was matching non-environment config files
|
|
3612
|
-
- **Root Cause**: Pattern `${rootFolder}/**/*{env.${envType},${envType}}.json` matched ANY file ending with `.${envType}.json`
|
|
3613
|
-
- **Impact**: When multiple files matched (e.g., `free-audit-page.development.json` and `env.development.json`), glob returned them alphabetically and the wrong file was loaded first
|
|
3614
|
-
- **Real-World Example**:
|
|
3615
|
-
- User had `config/environments/free-audit-page.development.json` (no context configs)
|
|
3616
|
-
- User had `config/environments/env.development.json` (has userContext config)
|
|
3617
|
-
- Glob found both, returned `free-audit-page.development.json` first (alphabetically)
|
|
3618
|
-
- Migration command failed: "Configuration missing settings for context 'userContext'"
|
|
3619
|
-
- **Fix**: Split into two specific patterns with priority:
|
|
3620
|
-
1. `**/env.${envType}.json` (preferred - exact match)
|
|
3621
|
-
2. `**/${envType}.json` (fallback - exact match)
|
|
3622
|
-
- **Result**: Only matches actual environment config files, not arbitrary files
|
|
3623
|
-
|
|
3624
|
-
**Fix #3: Config File Priority Pattern** (`context.js` lines 445-470)
|
|
3625
|
-
- Changed from single broad pattern to prioritized specific patterns
|
|
3626
|
-
- Tries `env.<envType>.json` first (most specific)
|
|
3627
|
-
- Falls back to `<envType>.json` (less specific)
|
|
3628
|
-
- Prevents false positives from files like `my-config.development.json`
|
|
3629
|
-
|
|
3630
|
-
#### Technical Details
|
|
3631
|
-
**Files Modified:**
|
|
3632
|
-
1. `context.js` - Added deduplication logic in `dbset()` and `#addSeedData()`
|
|
3633
|
-
2. `context.js` - Fixed glob pattern for config file discovery (lines 445-470)
|
|
3634
|
-
3. `test/entity-deduplication-test.js` (NEW) - 5 tests for entity deduplication
|
|
3635
|
-
4. `test/seed-deduplication-test.js` (NEW) - 8 tests for EF Core seed semantics
|
|
3636
|
-
5. `test/qa-context-pattern-test.js` (NEW) - 7 tests for real-world patterns
|
|
3637
|
-
6. `test/config-glob-pattern-test.js` (NEW) - 7 tests for config file discovery
|
|
3638
|
-
7. `package.json` - Updated version to 0.3.36
|
|
3639
|
-
8. `readme.md` - Added changelog and documentation
|
|
3640
|
-
|
|
3641
|
-
**Test Results:**
|
|
3642
|
-
- **27 new tests** - All passing ✅
|
|
3643
|
-
- 20 tests for duplicate entity/seed data fixes
|
|
3644
|
-
- 7 tests for config file discovery fix
|
|
3645
|
-
- Tests cover the exact qaContext pattern (lines 58 + 207) that caused the bug
|
|
3646
|
-
- Tests verify 9 seeds stay as 9 (not 18), Settings stay as 2 (not 4)
|
|
3647
|
-
- Tests verify glob pattern only matches environment config files
|
|
3648
|
-
|
|
3649
|
-
#### Upgrade Path
|
|
3650
|
-
1. **Update to v0.3.36**: `npm install -g masterrecord@0.3.36`
|
|
3651
|
-
2. **If you have duplicate data in your database from older versions**:
|
|
3652
|
-
- Manually remove duplicate records (check by primary key)
|
|
3653
|
-
- Delete existing snapshots: `rm db/migrations/*_contextSnapShot.json`
|
|
3654
|
-
- Regenerate migrations: `masterrecord add-migration YourContext "clean-regenerate"`
|
|
3655
|
-
3. **Future migrations**: Will automatically deduplicate entities and seed data
|
|
3656
|
-
|
|
3657
|
-
#### Why This Fix is Comprehensive
|
|
3658
|
-
- **Root Cause Fix**: Prevents duplicates from ever being created at registration time
|
|
3659
|
-
- **EF Core Semantics**: Industry-standard seed data pattern with idempotent operations
|
|
3660
|
-
- **Defense-in-Depth**: Multiple layers of protection ensure data integrity
|
|
3661
|
-
|
|
3662
|
-
#### Impact
|
|
3663
|
-
- ✅ Entities registered only once even with multiple `dbset()` calls
|
|
3664
|
-
- ✅ Seed data uses EF Core semantics (upsert by primary key)
|
|
3665
|
-
- ✅ Warning messages guide users to fix their code patterns
|
|
3666
|
-
- ✅ Config file discovery now specific and predictable (only matches env.*.json and *.json)
|
|
3667
|
-
- ✅ Migrations no longer fail when non-environment config files exist
|
|
3668
|
-
- ✅ Backward compatible - existing single `dbset()` calls work as before
|
|
3669
|
-
|
|
3670
|
-
## Version Compatibility
|
|
3671
|
-
|
|
3672
|
-
| Component | Version | Notes |
|
|
3673
|
-
|---------------|---------------|------------------------------------------|
|
|
3674
|
-
| MasterRecord | 0.3.36 | Current version - root cause fix for duplicate entities/seeds |
|
|
3675
|
-
| Node.js | 14+ | Async/await support required |
|
|
3676
|
-
| PostgreSQL | 9.6+ (12+) | Tested with 12, 13, 14, 15, 16 |
|
|
3677
|
-
| MySQL | 5.7+ (8.0+) | Tested with 8.0+ |
|
|
3678
|
-
| SQLite | 3.x | Any recent version |
|
|
3679
|
-
| pg | 8.17.2+ | PostgreSQL driver (async) |
|
|
3680
|
-
| mysql2 | 3.11.5+ | MySQL driver (async with connection pooling) |
|
|
3681
|
-
| better-sqlite3| 12.6.2+ | SQLite driver (wrapped with async API) |
|
|
3682
|
-
|
|
3683
|
-
## Documentation
|
|
3684
|
-
|
|
3685
|
-
- [PostgreSQL Setup Guide](./docs/POSTGRESQL_SETUP.md) - Complete PostgreSQL configuration
|
|
3686
|
-
- [Migrations Guide](./docs/MIGRATIONS_GUIDE.md) - Detailed migration tutorial
|
|
3687
|
-
- [Methods Reference](./docs/METHODS_REFERENCE.md) - Complete API reference
|
|
3688
|
-
- [Field Transformers](./docs/FIELD_TRANSFORMERS.md) - Custom type handling
|
|
3689
|
-
|
|
3690
|
-
## Contributing
|
|
3691
|
-
|
|
3692
|
-
Contributions are welcome! Please:
|
|
3693
|
-
|
|
3694
|
-
1. Fork the repository
|
|
3695
|
-
2. Create a feature branch
|
|
3696
|
-
3. Make your changes with tests
|
|
3697
|
-
4. Submit a pull request
|
|
3698
|
-
|
|
3699
|
-
### Running Tests
|
|
3700
|
-
|
|
3701
|
-
```bash
|
|
3702
|
-
# PostgreSQL engine tests
|
|
3703
|
-
node test/postgresEngineTest.js
|
|
3704
|
-
|
|
3705
|
-
# Integration tests (requires database)
|
|
3706
|
-
node test/postgresIntegrationTest.js
|
|
3707
|
-
|
|
3708
|
-
# All tests
|
|
3709
|
-
npm test
|
|
3710
|
-
```
|
|
3711
|
-
|
|
3712
|
-
## License
|
|
3713
|
-
|
|
3714
|
-
MIT License - see [LICENSE](LICENSE) file for details.
|
|
3715
|
-
|
|
3716
|
-
## Credits
|
|
3717
|
-
|
|
3718
|
-
Created by Alexander Rich
|
|
3719
|
-
|
|
3720
|
-
## Support
|
|
3721
|
-
|
|
3722
|
-
- GitHub Issues: [Report bugs or request features](https://github.com/Tailor/MasterRecord/issues)
|
|
3723
|
-
- npm: [masterrecord](https://www.npmjs.com/package/masterrecord)
|
|
3724
|
-
|
|
3725
|
-
---
|
|
3726
|
-
|
|
3727
|
-
## Recent Improvements
|
|
3728
|
-
|
|
3729
|
-
### v0.3.30 - Mature ORM Features (Latest)
|
|
3730
|
-
|
|
3731
|
-
MasterRecord is now feature-complete with lifecycle hooks, validation, and bulk operations - matching the capabilities of mature ORMs like Sequelize, TypeORM, and Prisma.
|
|
3732
|
-
|
|
3733
|
-
**🎯 Entity Serialization:**
|
|
3734
|
-
- ✅ **`.toObject()`** - Convert entities to plain JavaScript objects with circular reference protection
|
|
3735
|
-
- ✅ **`.toJSON()`** - Automatic JSON.stringify() compatibility for Express responses
|
|
3736
|
-
- ✅ **Circular Reference Handling** - Prevents infinite loops from bidirectional relationships
|
|
3737
|
-
- ✅ **Depth Control** - Configurable relationship traversal depth
|
|
3738
|
-
|
|
3739
|
-
**🎯 Active Record Pattern:**
|
|
3740
|
-
- ✅ **`.delete()`** - Entities can delete themselves (`await user.delete()`)
|
|
3741
|
-
- ✅ **`.reload()`** - Refresh entity from database, discard unsaved changes
|
|
3742
|
-
- ✅ **`.clone()`** - Create entity copies for duplication (excludes primary key)
|
|
3743
|
-
- ✅ **`.save()`** - Already existed, now part of complete Active Record pattern
|
|
3744
|
-
|
|
3745
|
-
**🎯 Query Helpers:**
|
|
3746
|
-
- ✅ **`.first()`** - Get first record ordered by primary key
|
|
3747
|
-
- ✅ **`.last()`** - Get last record ordered by primary key descending
|
|
3748
|
-
- ✅ **`.exists()`** - Check if any records match query (returns boolean)
|
|
3749
|
-
- ✅ **`.pluck(field)`** - Extract single column values as array
|
|
3750
|
-
|
|
3751
|
-
**🎯 Lifecycle Hooks:**
|
|
3752
|
-
- ✅ **`beforeSave()`** - Execute before insert or update (e.g., hash passwords)
|
|
3753
|
-
- ✅ **`afterSave()`** - Execute after successful save (e.g., logging)
|
|
3754
|
-
- ✅ **`beforeDelete()`** - Execute before deletion (can prevent deletion)
|
|
3755
|
-
- ✅ **`afterDelete()`** - Execute after deletion (e.g., cleanup)
|
|
3756
|
-
- ✅ **Hook Execution Order** - Guaranteed execution order with error handling
|
|
3757
|
-
- ✅ **Async Support** - Hooks can be async for database operations
|
|
3758
|
-
|
|
3759
|
-
**🎯 Business Logic Validation:**
|
|
3760
|
-
- ✅ **`.required()`** - Field must have a value
|
|
3761
|
-
- ✅ **`.email()`** - Must be valid email format
|
|
3762
|
-
- ✅ **`.minLength()` / `.maxLength()`** - String length constraints
|
|
3763
|
-
- ✅ **`.min()` / `.max()`** - Numeric value constraints
|
|
3764
|
-
- ✅ **`.pattern()`** - Must match regex pattern
|
|
3765
|
-
- ✅ **`.custom()`** - Custom validation functions
|
|
3766
|
-
- ✅ **Chainable Validators** - Multiple validators per field
|
|
3767
|
-
- ✅ **Immediate Validation** - Errors thrown on property assignment
|
|
3768
|
-
|
|
3769
|
-
**🎯 Bulk Operations API:**
|
|
3770
|
-
- ✅ **`bulkCreate()`** - Create multiple entities efficiently in one transaction
|
|
3771
|
-
- ✅ **`bulkUpdate()`** - Update multiple entities by primary key
|
|
3772
|
-
- ✅ **`bulkDelete()`** - Delete multiple entities by primary key
|
|
3773
|
-
- ✅ **Lifecycle Hook Support** - Hooks execute for each entity in bulk operations
|
|
3774
|
-
- ✅ **Auto-Increment IDs** - IDs properly assigned after bulk inserts
|
|
3775
|
-
|
|
3776
|
-
**🎯 Critical Bug Fixes:**
|
|
3777
|
-
- ✅ **Auto-Increment ID Bug Fixed** - IDs now correctly set on entities after insert (SQLite, MySQL, PostgreSQL)
|
|
3778
|
-
- ✅ **Lifecycle Hook Isolation** - Hooks excluded from SQL queries and INSERT/UPDATE operations
|
|
3779
|
-
- ✅ **Circular Reference Prevention** - WeakSet-based tracking prevents infinite loops
|
|
3780
|
-
|
|
3781
|
-
**Example Usage:**
|
|
3782
|
-
|
|
3783
|
-
```javascript
|
|
3784
|
-
// Entity serialization
|
|
3785
|
-
const user = await db.User.findById(1);
|
|
3786
|
-
const plain = user.toObject({ includeRelationships: true, depth: 2 });
|
|
3787
|
-
res.json(user); // Works automatically with toJSON()
|
|
3788
|
-
|
|
3789
|
-
// Active Record pattern
|
|
3790
|
-
await user.delete(); // Entity deletes itself
|
|
3791
|
-
await user.reload(); // Discard changes
|
|
3792
|
-
const copy = user.clone(); // Duplicate entity
|
|
3793
|
-
|
|
3794
|
-
// Query helpers
|
|
3795
|
-
const first = await db.User.first();
|
|
3796
|
-
const exists = await db.User.where(u => u.email == $$, 'test@test.com').exists();
|
|
3797
|
-
const emails = await db.User.where(u => u.status == $$, 'active').pluck('email');
|
|
3798
|
-
|
|
3799
|
-
// Lifecycle hooks
|
|
3800
|
-
class User {
|
|
3801
|
-
beforeSave() {
|
|
3802
|
-
if (this.__dirtyFields.includes('password')) {
|
|
3803
|
-
this.password = bcrypt.hashSync(this.password, 10);
|
|
3804
|
-
}
|
|
3805
|
-
this.updated_at = new Date();
|
|
3806
|
-
}
|
|
3807
|
-
|
|
3808
|
-
beforeDelete() {
|
|
3809
|
-
if (this.role === 'admin') {
|
|
3810
|
-
throw new Error('Cannot delete admin user');
|
|
3811
|
-
}
|
|
3812
|
-
}
|
|
3813
|
-
}
|
|
3814
|
-
|
|
3815
|
-
// Business validation
|
|
3816
|
-
class User {
|
|
3817
|
-
email(db) {
|
|
3818
|
-
db.string()
|
|
3819
|
-
.required('Email is required')
|
|
3820
|
-
.email('Must be a valid email address');
|
|
3821
|
-
}
|
|
3822
|
-
|
|
3823
|
-
age(db) {
|
|
3824
|
-
db.integer()
|
|
3825
|
-
.min(18, 'Must be at least 18 years old')
|
|
3826
|
-
.max(120);
|
|
3827
|
-
}
|
|
3828
|
-
}
|
|
3829
|
-
|
|
3830
|
-
// Bulk operations
|
|
3831
|
-
const users = await db.bulkCreate('User', [
|
|
3832
|
-
{ name: 'Alice', email: 'alice@example.com' },
|
|
3833
|
-
{ name: 'Bob', email: 'bob@example.com' },
|
|
3834
|
-
{ name: 'Charlie', email: 'charlie@example.com' }
|
|
3835
|
-
]);
|
|
3836
|
-
console.log(users.map(u => u.id)); // [1, 2, 3] - IDs assigned
|
|
3837
|
-
|
|
3838
|
-
await db.bulkUpdate('User', [
|
|
3839
|
-
{ id: 1, status: 'inactive' },
|
|
3840
|
-
{ id: 2, status: 'inactive' }
|
|
3841
|
-
]);
|
|
3842
|
-
|
|
3843
|
-
await db.bulkDelete('User', [3, 5, 7]);
|
|
3844
|
-
```
|
|
3845
|
-
|
|
3846
|
-
---
|
|
3847
|
-
|
|
3848
|
-
### v0.3.13 - FAANG Engineering Standards
|
|
3849
|
-
|
|
3850
|
-
MasterRecord has been upgraded to meet **FAANG engineering standards** (Google/Meta/Amazon) with critical bug fixes and performance improvements:
|
|
3851
|
-
|
|
3852
|
-
### Migration System Fixes (v0.3.13)
|
|
3853
|
-
|
|
3854
|
-
**Critical Path Bug Fixed:**
|
|
3855
|
-
- ✅ **Duplicate db/migrations Path Fixed** - Resolved bug where snapshot files were created with duplicate nested paths
|
|
3856
|
-
- **Before**: `/components/qa/app/models/db/migrations/db/migrations/qacontext_contextSnapShot.json` ❌
|
|
3857
|
-
- **After**: `/components/qa/app/models/db/migrations/qacontext_contextSnapShot.json` ✅
|
|
3858
|
-
- **Root Cause**: Incorrect glob API usage in `findContext` method (migrations.js:169-181)
|
|
3859
|
-
- **Fix**: Changed to use relative pattern + options object + `path.resolve()` for guaranteed absolute paths
|
|
3860
|
-
- ✅ **Smart Path Resolution** - Added `pathUtils.js` with intelligent path detection
|
|
3861
|
-
- ✅ **Prevents update-database-restart Failures** - Snapshot files now always created in the correct location
|
|
3862
|
-
- ✅ **Cross-Platform Support** - Works correctly on Windows and Unix-based systems
|
|
3863
|
-
|
|
3864
|
-
**Running Migrations - Important Notes:**
|
|
3865
|
-
- **Don't move migration files** - Leave them in their generated location (e.g., `/components/qa/db/migrations/`)
|
|
3866
|
-
- **Two ways to run migrations:**
|
|
3867
|
-
1. **From anywhere** - Run MasterRecord CLI from your project root, it will find migrations automatically:
|
|
3868
|
-
```bash
|
|
3869
|
-
npx masterrecord enable-migrations components/qa/app/models/qaContext
|
|
3870
|
-
npx masterrecord update-database components/qa/app/models/qaContext
|
|
3871
|
-
```
|
|
3872
|
-
2. **From migration directory** - cd into the specific migration area and run CLI there:
|
|
3873
|
-
```bash
|
|
3874
|
-
cd components/qa/db/migrations
|
|
3875
|
-
masterrecord enable-migrations qacontext
|
|
3876
|
-
```
|
|
3877
|
-
- MasterRecord uses intelligent path resolution to locate migrations regardless of where you run the command
|
|
3878
|
-
|
|
3879
|
-
### Core Improvements (context.js)
|
|
3880
|
-
|
|
3881
|
-
**Critical Fixes:**
|
|
3882
|
-
- ✅ **PostgreSQL Async Bug Fixed** - Resolved race condition where database returned before initialization completed
|
|
3883
|
-
- ✅ **Collision-Safe Entity Tracking** - Replaced random IDs with sequential IDs (zero collision risk)
|
|
3884
|
-
- ✅ **Input Validation** - Added validation to `dbset()` to prevent crashes and SQL injection
|
|
3885
|
-
- ✅ **Better Error Logging** - Configuration errors now logged with full context for debugging
|
|
3886
|
-
|
|
3887
|
-
**Code Quality:**
|
|
3888
|
-
- Modern JavaScript with `const`/`let` (no more `var`)
|
|
3889
|
-
- Comprehensive JSDoc documentation
|
|
3890
|
-
- Consistent code style following Google/Meta standards
|
|
3891
|
-
- Better error messages with actionable context
|
|
3892
|
-
|
|
3893
|
-
**Performance:**
|
|
3894
|
-
- Entity tracking: O(n) → O(1) lookups (100x faster)
|
|
3895
|
-
- Batch operations optimized for bulk inserts/updates/deletes
|
|
3896
|
-
|
|
3897
|
-
### Cascade Deletion Improvements (deleteManager.js)
|
|
3898
|
-
|
|
3899
|
-
**Critical Fixes:**
|
|
3900
|
-
- ✅ **Proper Error Handling** - Now throws Error objects (not strings) with full context
|
|
3901
|
-
- ✅ **Input Validation** - Validates entities before processing to prevent crashes
|
|
3902
|
-
- ✅ **Null Safety** - Handles null entities and arrays safely with clear error messages
|
|
3903
|
-
|
|
3904
|
-
**Code Quality:**
|
|
3905
|
-
- Refactored into smaller, focused methods (`_deleteSingleEntity`, `_deleteMultipleEntities`)
|
|
3906
|
-
- Constants for relationship types (no magic strings)
|
|
3907
|
-
- Comprehensive JSDoc documentation
|
|
3908
|
-
- Improved error messages that guide developers to solutions
|
|
3909
|
-
- Removed duplicate code between single/array handling
|
|
3910
|
-
|
|
3911
|
-
**Best Practices:**
|
|
3912
|
-
```javascript
|
|
3913
|
-
// Example: Cascade deletion with proper error handling
|
|
3914
|
-
const user = db.User.findById(123);
|
|
3915
|
-
db.User.remove(user);
|
|
3916
|
-
|
|
3917
|
-
try {
|
|
3918
|
-
db.saveChanges(); // Cascades to related entities
|
|
3919
|
-
} catch (error) {
|
|
3920
|
-
console.error('Deletion failed:', error.message);
|
|
3921
|
-
// Error: "Cannot delete User: required relationship 'Profile' is null.
|
|
3922
|
-
// Set nullable: true if this is intentional."
|
|
3923
|
-
}
|
|
3924
|
-
```
|
|
3925
|
-
|
|
3926
|
-
### Insert Manager Improvements (v0.3.13)
|
|
3927
|
-
|
|
3928
|
-
**Security Fixes:**
|
|
3929
|
-
- ✅ **SQL Injection Prevention** - Added identifier validation for dynamic query construction
|
|
3930
|
-
- Dynamic SQL identifiers are now validated with regex: `/^[a-zA-Z_][a-zA-Z0-9_]*$/`
|
|
3931
|
-
- Prevents malicious identifiers from breaking out of parameterized queries
|
|
3932
|
-
- Affects: hasOne relationship hydration (insertManager.js:181-186)
|
|
3933
|
-
- ✅ **Proper Error Objects** - All errors now throw Error instances with stack traces
|
|
3934
|
-
- Custom error classes: `InsertManagerError`, `RelationshipError`
|
|
3935
|
-
- Includes context for debugging (entity names, relationship info, available entities)
|
|
3936
|
-
- Before: `throw 'Relationship "..." could not be found'` (no stack trace)
|
|
3937
|
-
- After: `throw new RelationshipError(message, relationshipName, context)` (full stack)
|
|
3938
|
-
- ✅ **Error Logging** - Silent catch blocks now log warnings instead of suppressing errors
|
|
3939
|
-
- Hydration errors are logged but don't crash the insert operation
|
|
3940
|
-
- Console warnings include: property, error message, and child ID for debugging
|
|
3941
|
-
|
|
3942
|
-
**Performance:**
|
|
3943
|
-
- ✅ **50% Code Reduction** - Eliminated 50+ lines of duplicate code
|
|
3944
|
-
- hasMany and hasManyThrough shared nearly identical logic (89-110 vs 119-139)
|
|
3945
|
-
- Extracted to unified `_processArrayRelationship()` method
|
|
3946
|
-
- Reduces maintenance burden and bug surface area
|
|
3947
|
-
- ✅ **Entity Resolution Optimization** - Fallback entity resolution extracted and reusable
|
|
3948
|
-
- Triple fallback pattern (exact match → capitalized → property name) now in `_resolveEntityWithFallback()`
|
|
3949
|
-
- Can be cached or optimized in future without code duplication
|
|
3950
|
-
- ✅ **Loop Optimization** - Replaced for...in loops with for...of and Object.keys()
|
|
3951
|
-
- Prevents prototype chain pollution bugs
|
|
3952
|
-
- More predictable iteration behavior
|
|
3953
|
-
- Follows modern JavaScript best practices
|
|
3954
|
-
|
|
3955
|
-
**Code Quality:**
|
|
3956
|
-
- ✅ **Modern JavaScript** - All 24 `var` declarations replaced with `const`/`let`
|
|
3957
|
-
- Lines replaced: 3, 4, 20, 26, 30, 33, 34, 47, 48, 63, 64, 66, 149, 160, 161, 163, 164, 167, 168, 170, 184, 185, 200
|
|
3958
|
-
- Removed jQuery-style `$that` variable (lines 20, 160) by using arrow functions and `this`
|
|
3959
|
-
- Improved readability and follows ES6+ standards
|
|
3960
|
-
- ✅ **Comprehensive JSDoc** - Full documentation for all methods and class
|
|
3961
|
-
- Class-level documentation with usage examples
|
|
3962
|
-
- Method documentation with parameter types, return types, and @throws annotations
|
|
3963
|
-
- Private method markers (`@private`) to indicate internal APIs
|
|
3964
|
-
- ✅ **Constants Extraction** - Magic strings/numbers extracted to named constants
|
|
3965
|
-
- `TIMESTAMP_FIELDS.CREATED_AT` / `TIMESTAMP_FIELDS.UPDATED_AT` (instead of 'created_at', 'updated_at')
|
|
3966
|
-
- `RELATIONSHIP_TYPES.HAS_MANY`, `HAS_MANY_THROUGH`, `BELONGS_TO`, `HAS_ONE`
|
|
3967
|
-
- `MIN_OBJECT_KEYS = 0` for length comparisons
|
|
3968
|
-
- Easier to refactor and understand intent
|
|
3969
|
-
- ✅ **Strict Mode** - Added `'use strict';` at top of file
|
|
3970
|
-
- Catches common coding mistakes at runtime
|
|
3971
|
-
- Prevents accidental global variable creation
|
|
3972
|
-
- Better performance in modern JavaScript engines
|
|
3973
|
-
|
|
3974
|
-
**Before/After Example:**
|
|
3975
|
-
```javascript
|
|
3976
|
-
// BEFORE (v0.0.15) - vulnerable and duplicated:
|
|
3977
|
-
if(entityProperty.type === "hasMany"){
|
|
3978
|
-
if(tools.checkIfArrayLike(propertyModel)){
|
|
3979
|
-
const propertyKeys = Object.keys(propertyModel);
|
|
3980
|
-
for (const propertykey of propertyKeys) {
|
|
3981
|
-
let targetName = entityProperty.foreignTable || property;
|
|
3982
|
-
let resolved = tools.getEntity(targetName, $that._allEntities)
|
|
3983
|
-
|| tools.getEntity(tools.capitalize(targetName), $that._allEntities)
|
|
3984
|
-
|| tools.getEntity(property, $that._allEntities);
|
|
3985
|
-
if(!resolved){
|
|
3986
|
-
throw `Relationship entity for '${property}' could not be resolved`; // ❌ String throw
|
|
3987
|
-
}
|
|
3988
|
-
// ... 20 more lines
|
|
3989
|
-
}
|
|
3990
|
-
}
|
|
3991
|
-
}
|
|
3992
|
-
// ... 50 lines later, nearly identical code for hasManyThrough
|
|
3993
|
-
|
|
3994
|
-
// AFTER (v0.3.13) - secure and DRY:
|
|
3995
|
-
if (entityProperty.type === RELATIONSHIP_TYPES.HAS_MANY) {
|
|
3996
|
-
this._processArrayRelationship(propertyModel, entityProperty, property, currentModel, SQL, RELATIONSHIP_TYPES.HAS_MANY);
|
|
3997
|
-
}
|
|
3998
|
-
|
|
3999
|
-
if (entityProperty.type === RELATIONSHIP_TYPES.HAS_MANY_THROUGH) {
|
|
4000
|
-
this._processArrayRelationship(propertyModel, entityProperty, property, currentModel, SQL, RELATIONSHIP_TYPES.HAS_MANY_THROUGH);
|
|
4001
|
-
}
|
|
4002
|
-
|
|
4003
|
-
// Unified method with proper error handling:
|
|
4004
|
-
_processArrayRelationship(propertyModel, entityProperty, property, currentModel, SQL, relationshipType) {
|
|
4005
|
-
const resolved = this._resolveEntityWithFallback(property, targetName);
|
|
4006
|
-
if (!resolved) {
|
|
4007
|
-
throw new RelationshipError(
|
|
4008
|
-
`Relationship entity for '${property}' could not be resolved`,
|
|
4009
|
-
property,
|
|
4010
|
-
{ targetName, relationshipType, availableEntities: this._allEntities.map(e => e.__name) }
|
|
4011
|
-
); // ✅ Proper Error object with context
|
|
4012
|
-
}
|
|
4013
|
-
// ... unified logic
|
|
4014
|
-
}
|
|
4015
|
-
```
|
|
4016
|
-
|
|
4017
|
-
**Verification Results:**
|
|
4018
|
-
```bash
|
|
4019
|
-
$ grep -n "^\s*var " insertManager.js
|
|
4020
|
-
# ✅ No results - all var declarations eliminated
|
|
4021
|
-
|
|
4022
|
-
$ grep -n "throw '" insertManager.js
|
|
4023
|
-
# ✅ No results - all string throws replaced with Error objects
|
|
4024
|
-
|
|
4025
|
-
$ grep -A1 "catch.*{$" insertManager.js | grep "^\s*}$"
|
|
4026
|
-
# ✅ No empty catch blocks - all log errors appropriately
|
|
4027
|
-
```
|
|
4028
|
-
|
|
4029
|
-
### Breaking Changes (v0.3.17+)
|
|
4030
|
-
|
|
4031
|
-
**🔴 CRITICAL: All databases now require async/await for consistency**
|
|
4032
|
-
|
|
4033
|
-
MasterRecord now provides a **unified async API** across all database engines (SQLite, MySQL, PostgreSQL). This follows industry best practices from Sequelize, TypeORM, and Prisma.
|
|
4034
|
-
|
|
4035
|
-
**1. Database Operations (All Engines)**
|
|
4036
|
-
```javascript
|
|
4037
|
-
// ✅ NEW (v0.3.17+): All databases use async/await
|
|
4038
|
-
const db = new AppContext();
|
|
4039
|
-
await db.saveChanges(); // Required for SQLite, MySQL, PostgreSQL
|
|
4040
|
-
|
|
4041
|
-
// ❌ OLD (v0.3.16 and earlier): Mixed sync/async
|
|
4042
|
-
db.saveChanges(); // SQLite/MySQL were sync (no longer works)
|
|
4043
|
-
await db.saveChanges(); // Only PostgreSQL was async
|
|
4044
|
-
```
|
|
4045
|
-
|
|
4046
|
-
**2. Migration Files (Critical)**
|
|
4047
|
-
```javascript
|
|
4048
|
-
// ✅ NEW (v0.3.17+): Migrations must be async
|
|
4049
|
-
class CreateUser extends masterrecord.schema {
|
|
4050
|
-
async up(table) { // Must be async
|
|
4051
|
-
this.init(table);
|
|
4052
|
-
await this.createTable(table.User); // Must await
|
|
4053
|
-
}
|
|
4054
|
-
|
|
4055
|
-
async down(table) { // Must be async
|
|
4056
|
-
this.init(table);
|
|
4057
|
-
this.dropTable(table.User);
|
|
4058
|
-
}
|
|
4059
|
-
}
|
|
4060
|
-
|
|
4061
|
-
// ❌ OLD (v0.3.16 and earlier): Migrations were sync
|
|
4062
|
-
up(table) {
|
|
4063
|
-
this.createTable(table.User); // No await (no longer works)
|
|
4064
|
-
}
|
|
4065
|
-
```
|
|
4066
|
-
|
|
4067
|
-
**3. MySQL Connection**
|
|
4068
|
-
```javascript
|
|
4069
|
-
// ✅ NEW (v0.3.17+): MySQL uses mysql2/promise with async connection pooling
|
|
4070
|
-
this.env({
|
|
4071
|
-
type: 'mysql',
|
|
4072
|
-
host: 'localhost',
|
|
4073
|
-
port: 3306,
|
|
4074
|
-
database: 'myapp',
|
|
4075
|
-
user: 'root',
|
|
4076
|
-
password: 'password',
|
|
4077
|
-
connectionLimit: 10 // Connection pool size
|
|
4078
|
-
});
|
|
4079
|
-
|
|
4080
|
-
// ❌ OLD (v0.3.16 and earlier): MySQL used sync-mysql2 (synchronous driver)
|
|
4081
|
-
```
|
|
4082
|
-
|
|
4083
|
-
**Why This Change?**
|
|
4084
|
-
- ✅ **Consistent API**: Same code works for SQLite, MySQL, and PostgreSQL
|
|
4085
|
-
- ✅ **Industry Standard**: Matches Sequelize, TypeORM, Prisma patterns
|
|
4086
|
-
- ✅ **Better Performance**: MySQL now uses connection pooling
|
|
4087
|
-
- ✅ **Real MySQL**: No longer using SQLite disguised as MySQL
|
|
4088
|
-
- ✅ **Portable Code**: Switch databases without code changes
|
|
4089
|
-
|
|
4090
|
-
**Migration Path:**
|
|
4091
|
-
1. Update all `db.saveChanges()` calls to use `await`
|
|
4092
|
-
2. Make all migration `up()` and `down()` methods async
|
|
4093
|
-
3. Add `await` before `createTable()` calls in migrations
|
|
4094
|
-
4. Update `package.json`: Remove `sync-mysql2`, ensure `mysql2@^3.11.5`
|
|
4095
|
-
|
|
4096
|
-
**For more details, see:** `CHANGES.md`
|
|
4097
|
-
|
|
4098
|
-
**For more details, see:** `CHANGES.md`
|
|
4099
|
-
|
|
4100
|
-
---
|
|
4101
|
-
|
|
4102
|
-
**MasterRecord** - Code-first ORM for Node.js with multi-database support
|
|
3381
|
+
1. **`Migrations/migrations.js`** - Skip `newIndexes` and `newCompositeIndexes` for new tables in `#findNewIndexes()` and `#findNewCompositeIndexes()`
|
|
3382
|
+
2. **`test/v0.3.34-bug-fixes-test.js`** - Updated tests to verify correct behavior + added tests for adding indexes to existing tables
|
|
3383
|
+
3. **`package.json`** - Updated to v0.3.49
|