masterrecord 0.3.66 → 0.3.68

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.
@@ -64,7 +64,9 @@
64
64
  "Bash(masterrecord add-migration:*)",
65
65
  "Bash(masterrecord:*)",
66
66
  "Bash(git -C /Users/alexanderrich/Documents/development/bookbaghq/bookbag-training log --oneline --all -- *MASTERRECORD_ISSUE*)",
67
- "Bash(npm ls -g masterrecord 2>&1 | head -5)"
67
+ "Bash(npm ls -g masterrecord 2>&1 | head -5)",
68
+ "Bash(gh pr:*)",
69
+ "Read(//tmp/**)"
68
70
  ],
69
71
  "deny": [],
70
72
  "ask": []
package/Migrations/cli.js CHANGED
@@ -7,8 +7,60 @@
7
7
  const { program } = require('commander');
8
8
  let fs = require('fs');
9
9
  let path = require('path');
10
+ const { pathToFileURL } = require('node:url');
10
11
  const Module = require('module');
11
12
  const { resolveMigrationsDirectory } = require('./pathUtils');
13
+
14
+ /**
15
+ * Load a user module (context, migration) via dynamic import.
16
+ *
17
+ * Handles both CJS and ESM targets. Required because:
18
+ * - CJS require() of an ESM file throws on older Node, or returns a Module
19
+ * namespace on newer Node (22.12+) — neither shape matches the
20
+ * `new ContextCtor()` pattern downstream.
21
+ * - await import() works in both directions and is consistent across Node
22
+ * versions.
23
+ *
24
+ * The returned value is unwrapped: ESM `export default X` -> X;
25
+ * CJS `module.exports = X` -> X; mixed shapes are handled via `.default ?? mod`.
26
+ *
27
+ * @param {string} filePath - Absolute path to the user file
28
+ * @returns {Promise<*>} The default export (or whole module if no default)
29
+ */
30
+ async function __loadUserModule(filePath) {
31
+ const mod = await import(pathToFileURL(filePath).href);
32
+ return (mod && mod.default !== undefined) ? mod.default : mod;
33
+ }
34
+
35
+ /**
36
+ * Walk up from a given directory looking for the host project's package.json
37
+ * and return whether it declares ESM (`"type": "module"`) or CJS.
38
+ * Used when generating migration files so the emitted syntax matches the
39
+ * host project's module type. Masterrecord's own package.json is skipped.
40
+ *
41
+ * @param {string} startDir - Directory to walk up from
42
+ * @returns {'esm' | 'cjs'}
43
+ */
44
+ function __detectHostModuleType(startDir) {
45
+ let dir = startDir;
46
+ for (let i = 0; i < 12; i++) {
47
+ const pkgPath = path.join(dir, 'package.json');
48
+ if (fs.existsSync(pkgPath)) {
49
+ try {
50
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
51
+ if (pkg && pkg.name === 'masterrecord') {
52
+ // Skip our own package.json — keep walking up to the host project
53
+ } else {
54
+ return (pkg && pkg.type === 'module') ? 'esm' : 'cjs';
55
+ }
56
+ } catch (_) { /* keep walking */ }
57
+ }
58
+ const parent = path.dirname(dir);
59
+ if (parent === dir) break;
60
+ dir = parent;
61
+ }
62
+ return 'cjs';
63
+ }
12
64
  // Alias require('masterrecord') to this global package so project files don't need a local install
13
65
  const __MASTERRECORD_ROOT__ = path.join(__dirname, '..');
14
66
  const __ORIGINAL_REQUIRE__ = Module.prototype.require;
@@ -139,7 +191,7 @@ program.option('-V', 'output the version');
139
191
 
140
192
  let ContextCtor;
141
193
  try{
142
- ContextCtor = require(contextAbs);
194
+ ContextCtor = await __loadUserModule(contextAbs);
143
195
  }catch(err){
144
196
  console.error(`\n❌ Error - Cannot load Context file at '${contextAbs}'`);
145
197
  console.error(`\nDetails:`);
@@ -152,7 +204,7 @@ program.option('-V', 'output the version');
152
204
  }
153
205
 
154
206
  // Use the migration class (extends schema) so createdatabase is available
155
- var MigrationCtor = require(mFile);
207
+ var MigrationCtor = await __loadUserModule(mFile);
156
208
  var mig = new MigrationCtor(ContextCtor);
157
209
  contextInstance = mig._context || mig.context || null;
158
210
 
@@ -256,7 +308,7 @@ program.option('-V', 'output the version');
256
308
 
257
309
  let ContextCtor;
258
310
  try{
259
- ContextCtor = require(contextAbs);
311
+ ContextCtor = await __loadUserModule(contextAbs);
260
312
  }catch(err){
261
313
  console.error(`\n❌ Error - Cannot load Context file at '${contextAbs}'`);
262
314
  console.error(`\nDetails:`);
@@ -298,7 +350,10 @@ program.option('-V', 'output the version');
298
350
  return;
299
351
  }
300
352
 
301
- var newEntity = migration.template(name, contextSnapshot.schema, cleanEntities, seedData, seedConfig);
353
+ // Emit the migration file in whatever module format the host project uses,
354
+ // so the generated .js file parses correctly when loaded by update-database.
355
+ var moduleType = __detectHostModuleType(path.dirname(contextAbs));
356
+ var newEntity = migration.template(name, contextSnapshot.schema, cleanEntities, seedData, seedConfig, null, moduleType);
302
357
  if(!fs.existsSync(migBase)){
303
358
  try{ fs.mkdirSync(migBase, { recursive: true }); }catch(_){ /* ignore */ }
304
359
  }
@@ -384,8 +439,8 @@ program.option('-V', 'output the version');
384
439
  var migrationProjectFile;
385
440
  var ContextCtor;
386
441
  try{
387
- migrationProjectFile = require(mFile);
388
- ContextCtor = require(contextAbs);
442
+ migrationProjectFile = await __loadUserModule(mFile);
443
+ ContextCtor = await __loadUserModule(contextAbs);
389
444
  }catch(err){
390
445
  console.error(`\n❌ Error - Cannot load Context or migration file`);
391
446
  console.error(`\nContext file: ${contextAbs}`);
@@ -547,7 +602,7 @@ program.option('-V', 'output the version');
547
602
  // Prepare context and table object
548
603
  let ContextCtor;
549
604
  try{
550
- ContextCtor = require(contextAbs);
605
+ ContextCtor = await __loadUserModule(contextAbs);
551
606
  }catch(err){
552
607
  console.error(`\n❌ Error - Cannot load Context file at '${contextAbs}'`);
553
608
  console.error(`\nDetails:`);
@@ -578,7 +633,7 @@ program.option('-V', 'output the version');
578
633
  var cleanEntities = migration.cleanEntities(contextInstance.__entities);
579
634
  var tableObj = migration.buildUpObject(contextSnapshot.schema, cleanEntities);
580
635
 
581
- var MigCtor = require(latestFile);
636
+ var MigCtor = await __loadUserModule(latestFile);
582
637
  var migInstance = new MigCtor(ContextCtor);
583
638
  if(typeof migInstance.down === 'function'){
584
639
  await migInstance.down(tableObj);
@@ -646,7 +701,7 @@ program.option('-V', 'output the version');
646
701
  });
647
702
  let ContextCtor;
648
703
  try{
649
- ContextCtor = require(contextAbs);
704
+ ContextCtor = await __loadUserModule(contextAbs);
650
705
  }catch(err){
651
706
  console.error(`\n❌ Error - Cannot load Context file at '${contextAbs}'`);
652
707
  console.error(`\nDetails:`);
@@ -677,7 +732,7 @@ program.option('-V', 'output the version');
677
732
  var cleanEntities = migration.cleanEntities(contextInstance.__entities);
678
733
  for (let i = 0; i < mFiles.length; i++) {
679
734
  var migFile = mFiles[i];
680
- var migrationProjectFile = require(migFile);
735
+ var migrationProjectFile = await __loadUserModule(migFile);
681
736
  var newMigrationProjectInstance = new migrationProjectFile(ContextCtor);
682
737
  var tableObj = migration.buildUpObject(contextSnapshot.schema, cleanEntities);
683
738
  await newMigrationProjectInstance.up(tableObj);
@@ -795,7 +850,7 @@ program.option('-V', 'output the version');
795
850
  // Prepare context and table object
796
851
  let ContextCtor;
797
852
  try{
798
- ContextCtor = require(contextAbs);
853
+ ContextCtor = await __loadUserModule(contextAbs);
799
854
  }catch(err){
800
855
  console.error(`\n❌ Error - Cannot load Context file at '${contextAbs}'`);
801
856
  console.error(`\nDetails:`);
@@ -829,7 +884,7 @@ program.option('-V', 'output the version');
829
884
  // Roll back (down) all migrations newer than the target (i.e., strictly after targetIndex)
830
885
  for (var i = sorted.length - 1; i > targetIndex; i--) {
831
886
  var migFile = path.resolve(migrationFolder, sorted[i]);
832
- var MigCtor = require(migFile);
887
+ var MigCtor = await __loadUserModule(migFile);
833
888
  var migInstance = new MigCtor(ContextCtor);
834
889
  if(typeof migInstance.down === 'function'){
835
890
  await migInstance.down(tableObj);
@@ -884,7 +939,7 @@ program.option('-V', 'output the version');
884
939
  // Load context
885
940
  let ContextCtor;
886
941
  try{
887
- ContextCtor = require(contextAbs);
942
+ ContextCtor = await __loadUserModule(contextAbs);
888
943
  }catch(err){
889
944
  console.error(`⚠️ Skipping ${path.basename(contextAbs)}: cannot load Context file`);
890
945
  console.error(` Details: ${err.message}`);
@@ -909,7 +964,8 @@ program.option('-V', 'output the version');
909
964
  console.log(`No changes detected for ${path.basename(contextAbs)}. Skipping.`);
910
965
  continue;
911
966
  }
912
- var newEntity = migration.template(name, cs.schema, cleanEntities, seedData, seedConfig);
967
+ var moduleType = __detectHostModuleType(path.dirname(contextAbs));
968
+ var newEntity = migration.template(name, cs.schema, cleanEntities, seedData, seedConfig, null, moduleType);
913
969
  if(!fs.existsSync(migBase)){
914
970
  try{ fs.mkdirSync(migBase, { recursive: true }); }catch(_){ /* ignore */ }
915
971
  }
@@ -1001,7 +1057,7 @@ program.option('-V', 'output the version');
1001
1057
 
1002
1058
  var ContextCtor;
1003
1059
  try{
1004
- ContextCtor = require(entry.contextAbs);
1060
+ ContextCtor = await __loadUserModule(entry.contextAbs);
1005
1061
  }catch(err){
1006
1062
  console.error(`⚠️ Skipping ${entry.ctxName}: cannot load Context file`);
1007
1063
  console.error(` Details: ${err.message}`);
@@ -1016,7 +1072,7 @@ program.option('-V', 'output the version');
1016
1072
  console.error(` Details: ${err.message}`);
1017
1073
  continue;
1018
1074
  }
1019
- var migrationProjectFile = require(mFile);
1075
+ var migrationProjectFile = await __loadUserModule(mFile);
1020
1076
  var newMigrationProjectInstance = new migrationProjectFile(ContextCtor);
1021
1077
  var cleanEntities = migration.cleanEntities(contextInstance.__entities);
1022
1078
  var tableObj = migration.buildUpObject(entry.cs.schema, cleanEntities);
@@ -13,7 +13,38 @@ class MigrationTemplate {
13
13
  #up = ''
14
14
  #down = ''
15
15
 
16
- get(){
16
+ /**
17
+ * Render the migration file source.
18
+ *
19
+ * @param {'esm'|'cjs'} [moduleType='cjs'] - Module type of the host project.
20
+ * When 'esm' the file is emitted with ESM syntax so it can be loaded in
21
+ * a `"type": "module"` project. When 'cjs' (default) it's emitted with
22
+ * CJS syntax for backward compatibility with existing projects.
23
+ */
24
+ get(moduleType = 'cjs'){
25
+ if (moduleType === 'esm') {
26
+ return `
27
+ import masterrecord from 'masterrecord';
28
+
29
+ class ${this.name} extends masterrecord.schema {
30
+ constructor(context){
31
+ super(context);
32
+ }
33
+
34
+ async up(table){
35
+ await this.init(table);
36
+ ${this.#up}
37
+ }
38
+
39
+ async down(table){
40
+ await this.init(table);
41
+ ${this.#down}
42
+ }
43
+ }
44
+ export default ${this.name};
45
+ `;
46
+ }
47
+
17
48
  return `
18
49
 
19
50
  var masterrecord = require('masterrecord');
@@ -511,7 +511,7 @@ class Migrations{
511
511
  return false;
512
512
  }
513
513
 
514
- template(name, oldSchema, newSchema, newSeedData = {}, seedConfig = {}, currentEnv = null){
514
+ template(name, oldSchema, newSchema, newSeedData = {}, seedConfig = {}, currentEnv = null, moduleType = 'cjs'){
515
515
  var MT = new MigrationTemplate(name);
516
516
  // Determine current environment if not provided
517
517
  if (!currentEnv) {
@@ -578,7 +578,7 @@ class Migrations{
578
578
 
579
579
  });
580
580
 
581
- return MT.get();
581
+ return MT.get(moduleType);
582
582
  }
583
583
 
584
584
  }
@@ -1,4 +1,6 @@
1
1
  // version 0.0.6
2
+ const { _poolKey } = require('masterrecord/context');
3
+
2
4
  class schema{
3
5
 
4
6
  constructor(context){
@@ -6,12 +8,6 @@ class schema{
6
8
  this._dbEnsured = false;
7
9
  }
8
10
 
9
- _poolKey(type, cfg) {
10
- const host = cfg.host || 'localhost';
11
- const port = cfg.port || (type === 'mysql' ? 3306 : 5432);
12
- return `${type}:${cfg.user}@${host}:${port}/${cfg.database}`;
13
- }
14
-
15
11
  /**
16
12
  * Wait for async database initialization (MySQL/PostgreSQL) to complete.
17
13
  * The context constructor fires off an async pool init that may not have
@@ -93,9 +89,9 @@ class schema{
93
89
 
94
90
  // Check global pool cache first -- another context may have already retried
95
91
  const _pools = global.__MR_POOLS__;
96
- const key = _pools ? this._poolKey('mysql', config) : null;
92
+ const key = _poolKey('mysql', config);
97
93
 
98
- if (_pools && key && _pools.has(key)) {
94
+ if (_pools.has(key)) {
99
95
  const cached = _pools.get(key);
100
96
  cached.refCount++;
101
97
  if (cached.promise) {
@@ -120,9 +116,7 @@ class schema{
120
116
  this.context.db = client;
121
117
 
122
118
  // Register in global pool cache so other contexts can reuse
123
- if (_pools && key) {
124
- _pools.set(key, { client, engine: this.context._SQLEngine, refCount: 1, dbType: 'mysql' });
125
- }
119
+ _pools.set(key, { client, engine: this.context._SQLEngine, refCount: 1, dbType: 'mysql' });
126
120
  console.log('[MySQL] Connection pool ready');
127
121
  }
128
122
 
@@ -174,9 +168,9 @@ class schema{
174
168
 
175
169
  // Check global pool cache first -- another context may have already retried
176
170
  const _pools = global.__MR_POOLS__;
177
- const key = _pools ? this._poolKey('postgres', config) : null;
171
+ const key = _poolKey('postgres', config);
178
172
 
179
- if (_pools && key && _pools.has(key)) {
173
+ if (_pools.has(key)) {
180
174
  const cached = _pools.get(key);
181
175
  cached.refCount++;
182
176
  if (cached.promise) {
@@ -202,9 +196,7 @@ class schema{
202
196
  this.context.db = pool;
203
197
 
204
198
  // Register in global pool cache so other contexts can reuse
205
- if (_pools && key) {
206
- _pools.set(key, { pool, engine: this.context._SQLEngine, client: connection, refCount: 1, dbType: 'postgres' });
207
- }
199
+ _pools.set(key, { pool, engine: this.context._SQLEngine, client: connection, refCount: 1, dbType: 'postgres' });
208
200
  console.log('[PostgreSQL] Connection pool ready');
209
201
  }
210
202
 
package/context.js CHANGED
@@ -2402,4 +2402,7 @@ module.exports = context;
2402
2402
  module.exports.ContextError = ContextError;
2403
2403
  module.exports.ConfigurationError = ConfigurationError;
2404
2404
  module.exports.DatabaseConnectionError = DatabaseConnectionError;
2405
- module.exports.EntityValidationError = EntityValidationError;
2405
+ module.exports.EntityValidationError = EntityValidationError;
2406
+
2407
+ // Export pool key generator for use by schema.js (single source of truth)
2408
+ module.exports._poolKey = _poolKey;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "masterrecord",
3
- "version": "0.3.66",
3
+ "version": "0.3.68",
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": {