masterrecord 0.3.17 → 0.3.19
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/cli.js +7 -7
- package/Migrations/schema.js +6 -6
- package/package.json +1 -1
- package/readme.md +111 -36
package/Migrations/cli.js
CHANGED
|
@@ -153,7 +153,7 @@ program.option('-V', 'output the version');
|
|
|
153
153
|
|
|
154
154
|
if(typeof mig.createdatabase === 'function'){
|
|
155
155
|
try{
|
|
156
|
-
mig.createdatabase();
|
|
156
|
+
await mig.createdatabase();
|
|
157
157
|
console.log('✓ Database ensured');
|
|
158
158
|
await __cleanupAndExit(contextInstance, 0);
|
|
159
159
|
}catch(err){
|
|
@@ -163,7 +163,7 @@ program.option('-V', 'output the version');
|
|
|
163
163
|
}
|
|
164
164
|
} else if(typeof mig.createDatabase === 'function'){
|
|
165
165
|
try{
|
|
166
|
-
mig.createDatabase();
|
|
166
|
+
await mig.createDatabase();
|
|
167
167
|
console.log('✓ Database ensured');
|
|
168
168
|
await __cleanupAndExit(contextInstance, 0);
|
|
169
169
|
}catch(err){
|
|
@@ -447,7 +447,7 @@ program.option('-V', 'output the version');
|
|
|
447
447
|
try{
|
|
448
448
|
var newMigrationProjectInstance = new migrationProjectFile(ContextCtor);
|
|
449
449
|
var tableObj = migration.buildUpObject(contextSnapshot.schema, cleanEntities);
|
|
450
|
-
newMigrationProjectInstance.up(tableObj);
|
|
450
|
+
await newMigrationProjectInstance.up(tableObj);
|
|
451
451
|
}catch(err){
|
|
452
452
|
console.error(`\n❌ Error - Migration failed during execution`);
|
|
453
453
|
console.error(`\nMigration file: ${mFile}`);
|
|
@@ -571,7 +571,7 @@ program.option('-V', 'output the version');
|
|
|
571
571
|
var MigCtor = require(latestFile);
|
|
572
572
|
var migInstance = new MigCtor(ContextCtor);
|
|
573
573
|
if(typeof migInstance.down === 'function'){
|
|
574
|
-
migInstance.down(tableObj);
|
|
574
|
+
await migInstance.down(tableObj);
|
|
575
575
|
}else{
|
|
576
576
|
console.log(`Warning - Migration '${path.basename(latestFile)}' has no down method; skipping.`);
|
|
577
577
|
}
|
|
@@ -668,7 +668,7 @@ program.option('-V', 'output the version');
|
|
|
668
668
|
var migrationProjectFile = require(migFile);
|
|
669
669
|
var newMigrationProjectInstance = new migrationProjectFile(ContextCtor);
|
|
670
670
|
var tableObj = migration.buildUpObject(contextSnapshot.schema, cleanEntities);
|
|
671
|
-
newMigrationProjectInstance.up(tableObj);
|
|
671
|
+
await newMigrationProjectInstance.up(tableObj);
|
|
672
672
|
}
|
|
673
673
|
const snap = {
|
|
674
674
|
file : contextAbs,
|
|
@@ -820,7 +820,7 @@ program.option('-V', 'output the version');
|
|
|
820
820
|
var MigCtor = require(migFile);
|
|
821
821
|
var migInstance = new MigCtor(ContextCtor);
|
|
822
822
|
if(typeof migInstance.down === 'function'){
|
|
823
|
-
migInstance.down(tableObj);
|
|
823
|
+
await migInstance.down(tableObj);
|
|
824
824
|
} else {
|
|
825
825
|
console.log(`Warning - Migration '${path.basename(migFile)}' has no down method; skipping.`);
|
|
826
826
|
}
|
|
@@ -1004,7 +1004,7 @@ program.option('-V', 'output the version');
|
|
|
1004
1004
|
var newMigrationProjectInstance = new migrationProjectFile(ContextCtor);
|
|
1005
1005
|
var cleanEntities = migration.cleanEntities(contextInstance.__entities);
|
|
1006
1006
|
var tableObj = migration.buildUpObject(entry.cs.schema, cleanEntities);
|
|
1007
|
-
newMigrationProjectInstance.up(tableObj);
|
|
1007
|
+
await newMigrationProjectInstance.up(tableObj);
|
|
1008
1008
|
var snap = {
|
|
1009
1009
|
file : entry.contextAbs,
|
|
1010
1010
|
executedLocation : executedLocation,
|
package/Migrations/schema.js
CHANGED
|
@@ -82,13 +82,13 @@ class schema{
|
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
createTable(table){
|
|
85
|
+
async createTable(table){
|
|
86
86
|
|
|
87
87
|
if(table){
|
|
88
88
|
// If table exists, run sync instead of blind create
|
|
89
89
|
const tableName = table.__name;
|
|
90
|
-
if(this.context._SQLEngine.tableExists && this.context._SQLEngine.tableExists(tableName)){
|
|
91
|
-
this.syncTable(table);
|
|
90
|
+
if(this.context._SQLEngine.tableExists && await this.context._SQLEngine.tableExists(tableName)){
|
|
91
|
+
await this.syncTable(table);
|
|
92
92
|
} else {
|
|
93
93
|
if(this.context.isSQLite){
|
|
94
94
|
var sqliteQuery = require("./migrationSQLiteQuery");
|
|
@@ -117,10 +117,10 @@ class schema{
|
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
// Compute diffs and apply minimal changes
|
|
120
|
-
syncTable(table){
|
|
120
|
+
async syncTable(table){
|
|
121
121
|
const engine = this.context._SQLEngine;
|
|
122
122
|
const tableName = table.__name;
|
|
123
|
-
const existing = engine.getTableInfo ? engine.getTableInfo(tableName) : [];
|
|
123
|
+
const existing = engine.getTableInfo ? await engine.getTableInfo(tableName) : [];
|
|
124
124
|
// Build a set of existing columns (sqlite: name, mysql: name)
|
|
125
125
|
const existingNames = new Set((existing || []).map(c => (c.name || c.COLUMN_NAME))); // both engines map to name
|
|
126
126
|
// Add missing columns only (safe path)
|
|
@@ -254,7 +254,7 @@ class schema{
|
|
|
254
254
|
const create = queryBuilder.createTable(table);
|
|
255
255
|
this.context._execute(create);
|
|
256
256
|
// compute common columns
|
|
257
|
-
const oldInfo = engine.getTableInfo(tableName.replace(/.*/, '_temp_alter_column_update')) || engine.getTableInfo("_temp_alter_column_update");
|
|
257
|
+
const oldInfo = await engine.getTableInfo(tableName.replace(/.*/, '_temp_alter_column_update')) || await engine.getTableInfo("_temp_alter_column_update");
|
|
258
258
|
const oldNames = new Set((oldInfo || existing).map(r => r.name));
|
|
259
259
|
const newNames = desiredCols.map(d => d.name);
|
|
260
260
|
const common = newNames.filter(n => oldNames.has(n));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "masterrecord",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.19",
|
|
4
4
|
"description": "An Object-relational mapping for the Master framework. Master Record connects classes to relational database tables to establish a database with almost zero-configuration ",
|
|
5
5
|
"main": "MasterRecord.js",
|
|
6
6
|
"bin": {
|
package/readme.md
CHANGED
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
| Database | Version | Features |
|
|
24
24
|
|------------|--------------|---------------------------------------------------|
|
|
25
25
|
| PostgreSQL | 9.6+ (12+) | JSONB, UUID, async/await, connection pooling |
|
|
26
|
-
| MySQL | 5.7+ (8.0+) | JSON,
|
|
27
|
-
| SQLite | 3.x | Embedded, zero-config, file-based
|
|
26
|
+
| MySQL | 5.7+ (8.0+) | JSON, async/await, connection pooling, AUTO_INCREMENT |
|
|
27
|
+
| SQLite | 3.x | Embedded, zero-config, file-based, async API wrapper |
|
|
28
28
|
|
|
29
29
|
## Table of Contents
|
|
30
30
|
|
|
@@ -64,9 +64,9 @@ npm install masterrecord better-sqlite3 # SQLite
|
|
|
64
64
|
### Dependencies
|
|
65
65
|
|
|
66
66
|
MasterRecord includes the following database drivers by default:
|
|
67
|
-
- `pg@^8.17.2` - PostgreSQL
|
|
68
|
-
- `
|
|
69
|
-
- `better-sqlite3@^12.6.2` - SQLite
|
|
67
|
+
- `pg@^8.17.2` - PostgreSQL (async)
|
|
68
|
+
- `mysql2@^3.11.5` - MySQL (async with connection pooling)
|
|
69
|
+
- `better-sqlite3@^12.6.2` - SQLite (async API wrapper for consistency)
|
|
70
70
|
|
|
71
71
|
## Two Patterns: Entity Framework & Active Record
|
|
72
72
|
|
|
@@ -212,7 +212,7 @@ const db = new AppContext();
|
|
|
212
212
|
await db.saveChanges(); // PostgreSQL is async
|
|
213
213
|
```
|
|
214
214
|
|
|
215
|
-
### MySQL (
|
|
215
|
+
### MySQL (Async with Connection Pooling)
|
|
216
216
|
|
|
217
217
|
```javascript
|
|
218
218
|
class AppContext extends context {
|
|
@@ -225,19 +225,20 @@ class AppContext extends context {
|
|
|
225
225
|
port: 3306,
|
|
226
226
|
database: 'myapp',
|
|
227
227
|
user: 'root',
|
|
228
|
-
password: 'password'
|
|
228
|
+
password: 'password',
|
|
229
|
+
connectionLimit: 10 // Connection pool size (optional)
|
|
229
230
|
});
|
|
230
231
|
|
|
231
232
|
this.dbset(User);
|
|
232
233
|
}
|
|
233
234
|
}
|
|
234
235
|
|
|
235
|
-
// Usage
|
|
236
|
+
// Usage requires await (async like PostgreSQL)
|
|
236
237
|
const db = new AppContext();
|
|
237
|
-
db.saveChanges(); //
|
|
238
|
+
await db.saveChanges(); // MySQL now uses async/await
|
|
238
239
|
```
|
|
239
240
|
|
|
240
|
-
### SQLite (
|
|
241
|
+
### SQLite (Async API)
|
|
241
242
|
|
|
242
243
|
```javascript
|
|
243
244
|
class AppContext extends context {
|
|
@@ -252,6 +253,10 @@ class AppContext extends context {
|
|
|
252
253
|
this.dbset(User);
|
|
253
254
|
}
|
|
254
255
|
}
|
|
256
|
+
|
|
257
|
+
// Usage requires await for consistency across databases
|
|
258
|
+
const db = new AppContext();
|
|
259
|
+
await db.saveChanges(); // SQLite now has async API wrapper
|
|
255
260
|
```
|
|
256
261
|
|
|
257
262
|
### Environment Files
|
|
@@ -593,33 +598,48 @@ masterrecord update-database-all # Apply all pending migrations
|
|
|
593
598
|
|
|
594
599
|
```javascript
|
|
595
600
|
// db/migrations/20250111_143052_CreateUser.js
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
601
|
+
const masterrecord = require('masterrecord');
|
|
602
|
+
|
|
603
|
+
class CreateUser extends masterrecord.schema {
|
|
604
|
+
constructor(context) {
|
|
605
|
+
super(context);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// IMPORTANT: Migrations must be async
|
|
609
|
+
async up(table) {
|
|
610
|
+
this.init(table);
|
|
611
|
+
|
|
612
|
+
// Create table (requires await)
|
|
613
|
+
await this.createTable(table.User);
|
|
600
614
|
|
|
601
615
|
// Seed initial data
|
|
602
|
-
|
|
616
|
+
this.seed('User', {
|
|
603
617
|
name: 'Admin',
|
|
604
618
|
email: 'admin@example.com',
|
|
605
619
|
role: 'admin'
|
|
606
620
|
});
|
|
607
|
-
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
async down(table) {
|
|
624
|
+
this.init(table);
|
|
608
625
|
|
|
609
|
-
down: function(table, schema) {
|
|
610
626
|
// Rollback
|
|
611
|
-
|
|
627
|
+
this.dropTable(table.User);
|
|
612
628
|
}
|
|
613
|
-
}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
module.exports = CreateUser;
|
|
614
632
|
```
|
|
615
633
|
|
|
616
634
|
### Migration Operations
|
|
617
635
|
|
|
618
636
|
```javascript
|
|
619
|
-
|
|
620
|
-
up
|
|
621
|
-
|
|
622
|
-
|
|
637
|
+
class MyMigration extends masterrecord.schema {
|
|
638
|
+
async up(table) {
|
|
639
|
+
this.init(table);
|
|
640
|
+
|
|
641
|
+
// Create table (requires await)
|
|
642
|
+
await this.createTable(table.User);
|
|
623
643
|
|
|
624
644
|
// Add column
|
|
625
645
|
schema.addColumn({
|
|
@@ -1189,9 +1209,8 @@ const users = db._SQLEngine.exec(
|
|
|
1189
1209
|
context.dbset(EntityClass)
|
|
1190
1210
|
context.dbset(EntityClass, 'custom_table_name')
|
|
1191
1211
|
|
|
1192
|
-
// Save changes
|
|
1193
|
-
await context.saveChanges() // PostgreSQL (async)
|
|
1194
|
-
context.saveChanges() // MySQL/SQLite (sync)
|
|
1212
|
+
// Save changes (all databases now async)
|
|
1213
|
+
await context.saveChanges() // PostgreSQL, MySQL, SQLite (all async)
|
|
1195
1214
|
|
|
1196
1215
|
// Add/Remove entities
|
|
1197
1216
|
context.EntityName.add(entity)
|
|
@@ -1585,9 +1604,9 @@ user.name = null; // Error if name is { nullable: false }
|
|
|
1585
1604
|
| PostgreSQL | 9.6+ (12+) | Tested with 12, 13, 14, 15, 16 |
|
|
1586
1605
|
| MySQL | 5.7+ (8.0+) | Tested with 8.0+ |
|
|
1587
1606
|
| SQLite | 3.x | Any recent version |
|
|
1588
|
-
| pg | 8.17.2+ | PostgreSQL driver
|
|
1589
|
-
|
|
|
1590
|
-
| better-sqlite3| 12.6.2+ | SQLite driver
|
|
1607
|
+
| pg | 8.17.2+ | PostgreSQL driver (async) |
|
|
1608
|
+
| mysql2 | 3.11.5+ | MySQL driver (async with connection pooling) |
|
|
1609
|
+
| better-sqlite3| 12.6.2+ | SQLite driver (wrapped with async API) |
|
|
1591
1610
|
|
|
1592
1611
|
## Documentation
|
|
1593
1612
|
|
|
@@ -1814,19 +1833,75 @@ $ grep -A1 "catch.*{$" insertManager.js | grep "^\s*}$"
|
|
|
1814
1833
|
# ✅ No empty catch blocks - all log errors appropriately
|
|
1815
1834
|
```
|
|
1816
1835
|
|
|
1817
|
-
### Breaking Changes
|
|
1836
|
+
### Breaking Changes (v0.3.17+)
|
|
1837
|
+
|
|
1838
|
+
**🔴 CRITICAL: All databases now require async/await for consistency**
|
|
1839
|
+
|
|
1840
|
+
MasterRecord now provides a **unified async API** across all database engines (SQLite, MySQL, PostgreSQL). This follows industry best practices from Sequelize, TypeORM, and Prisma.
|
|
1818
1841
|
|
|
1819
|
-
**
|
|
1842
|
+
**1. Database Operations (All Engines)**
|
|
1820
1843
|
```javascript
|
|
1821
|
-
//
|
|
1844
|
+
// ✅ NEW (v0.3.17+): All databases use async/await
|
|
1822
1845
|
const db = new AppContext();
|
|
1823
|
-
await db.
|
|
1846
|
+
await db.saveChanges(); // Required for SQLite, MySQL, PostgreSQL
|
|
1824
1847
|
|
|
1825
|
-
//
|
|
1826
|
-
|
|
1827
|
-
db.
|
|
1848
|
+
// ❌ OLD (v0.3.16 and earlier): Mixed sync/async
|
|
1849
|
+
db.saveChanges(); // SQLite/MySQL were sync (no longer works)
|
|
1850
|
+
await db.saveChanges(); // Only PostgreSQL was async
|
|
1828
1851
|
```
|
|
1829
1852
|
|
|
1853
|
+
**2. Migration Files (Critical)**
|
|
1854
|
+
```javascript
|
|
1855
|
+
// ✅ NEW (v0.3.17+): Migrations must be async
|
|
1856
|
+
class CreateUser extends masterrecord.schema {
|
|
1857
|
+
async up(table) { // Must be async
|
|
1858
|
+
this.init(table);
|
|
1859
|
+
await this.createTable(table.User); // Must await
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
async down(table) { // Must be async
|
|
1863
|
+
this.init(table);
|
|
1864
|
+
this.dropTable(table.User);
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1868
|
+
// ❌ OLD (v0.3.16 and earlier): Migrations were sync
|
|
1869
|
+
up(table) {
|
|
1870
|
+
this.createTable(table.User); // No await (no longer works)
|
|
1871
|
+
}
|
|
1872
|
+
```
|
|
1873
|
+
|
|
1874
|
+
**3. MySQL Connection**
|
|
1875
|
+
```javascript
|
|
1876
|
+
// ✅ NEW (v0.3.17+): MySQL uses mysql2/promise with async connection pooling
|
|
1877
|
+
this.env({
|
|
1878
|
+
type: 'mysql',
|
|
1879
|
+
host: 'localhost',
|
|
1880
|
+
port: 3306,
|
|
1881
|
+
database: 'myapp',
|
|
1882
|
+
user: 'root',
|
|
1883
|
+
password: 'password',
|
|
1884
|
+
connectionLimit: 10 // Connection pool size
|
|
1885
|
+
});
|
|
1886
|
+
|
|
1887
|
+
// ❌ OLD (v0.3.16 and earlier): MySQL used sync-mysql2 (synchronous driver)
|
|
1888
|
+
```
|
|
1889
|
+
|
|
1890
|
+
**Why This Change?**
|
|
1891
|
+
- ✅ **Consistent API**: Same code works for SQLite, MySQL, and PostgreSQL
|
|
1892
|
+
- ✅ **Industry Standard**: Matches Sequelize, TypeORM, Prisma patterns
|
|
1893
|
+
- ✅ **Better Performance**: MySQL now uses connection pooling
|
|
1894
|
+
- ✅ **Real MySQL**: No longer using SQLite disguised as MySQL
|
|
1895
|
+
- ✅ **Portable Code**: Switch databases without code changes
|
|
1896
|
+
|
|
1897
|
+
**Migration Path:**
|
|
1898
|
+
1. Update all `db.saveChanges()` calls to use `await`
|
|
1899
|
+
2. Make all migration `up()` and `down()` methods async
|
|
1900
|
+
3. Add `await` before `createTable()` calls in migrations
|
|
1901
|
+
4. Update `package.json`: Remove `sync-mysql2`, ensure `mysql2@^3.11.5`
|
|
1902
|
+
|
|
1903
|
+
**For more details, see:** `CHANGES.md`
|
|
1904
|
+
|
|
1830
1905
|
**For more details, see:** `CHANGES.md`
|
|
1831
1906
|
|
|
1832
1907
|
---
|