@memberjunction/codegen-lib 3.3.0 → 4.0.0

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 (92) hide show
  1. package/README.md +56 -1
  2. package/dist/Angular/angular-codegen.d.ts +1 -1
  3. package/dist/Angular/angular-codegen.d.ts.map +1 -1
  4. package/dist/Angular/angular-codegen.js +80 -136
  5. package/dist/Angular/angular-codegen.js.map +1 -1
  6. package/dist/Angular/entity-data-grid-related-entity-component.d.ts +1 -1
  7. package/dist/Angular/entity-data-grid-related-entity-component.js +6 -9
  8. package/dist/Angular/entity-data-grid-related-entity-component.js.map +1 -1
  9. package/dist/Angular/join-grid-related-entity-component.d.ts +1 -1
  10. package/dist/Angular/join-grid-related-entity-component.js +8 -36
  11. package/dist/Angular/join-grid-related-entity-component.js.map +1 -1
  12. package/dist/Angular/related-entity-components.js +13 -79
  13. package/dist/Angular/related-entity-components.js.map +1 -1
  14. package/dist/Angular/timeline-related-entity-component.d.ts +1 -1
  15. package/dist/Angular/timeline-related-entity-component.js +14 -38
  16. package/dist/Angular/timeline-related-entity-component.js.map +1 -1
  17. package/dist/Angular/user-view-grid-related-entity-component.d.ts +43 -0
  18. package/dist/Angular/user-view-grid-related-entity-component.d.ts.map +1 -0
  19. package/dist/Angular/user-view-grid-related-entity-component.js +85 -0
  20. package/dist/Angular/user-view-grid-related-entity-component.js.map +1 -0
  21. package/dist/Config/config.d.ts +79 -0
  22. package/dist/Config/config.d.ts.map +1 -1
  23. package/dist/Config/config.js +174 -172
  24. package/dist/Config/config.js.map +1 -1
  25. package/dist/Config/db-connection.d.ts +1 -1
  26. package/dist/Config/db-connection.d.ts.map +1 -1
  27. package/dist/Config/db-connection.js +6 -33
  28. package/dist/Config/db-connection.js.map +1 -1
  29. package/dist/Database/dbSchema.js +28 -35
  30. package/dist/Database/dbSchema.js.map +1 -1
  31. package/dist/Database/manage-metadata.d.ts +25 -9
  32. package/dist/Database/manage-metadata.d.ts.map +1 -1
  33. package/dist/Database/manage-metadata.js +438 -316
  34. package/dist/Database/manage-metadata.js.map +1 -1
  35. package/dist/Database/reorder-columns.d.ts +1 -1
  36. package/dist/Database/reorder-columns.d.ts.map +1 -1
  37. package/dist/Database/reorder-columns.js +1 -5
  38. package/dist/Database/reorder-columns.js.map +1 -1
  39. package/dist/Database/sql.d.ts +1 -1
  40. package/dist/Database/sql.d.ts.map +1 -1
  41. package/dist/Database/sql.js +67 -98
  42. package/dist/Database/sql.js.map +1 -1
  43. package/dist/Database/sql_codegen.d.ts +5 -2
  44. package/dist/Database/sql_codegen.d.ts.map +1 -1
  45. package/dist/Database/sql_codegen.js +344 -268
  46. package/dist/Database/sql_codegen.js.map +1 -1
  47. package/dist/Manifest/GenerateClassRegistrationsManifest.d.ts +110 -0
  48. package/dist/Manifest/GenerateClassRegistrationsManifest.d.ts.map +1 -0
  49. package/dist/Manifest/GenerateClassRegistrationsManifest.js +632 -0
  50. package/dist/Manifest/GenerateClassRegistrationsManifest.js.map +1 -0
  51. package/dist/Misc/action_subclasses_codegen.d.ts.map +1 -1
  52. package/dist/Misc/action_subclasses_codegen.js +15 -26
  53. package/dist/Misc/action_subclasses_codegen.js.map +1 -1
  54. package/dist/Misc/advanced_generation.d.ts +1 -1
  55. package/dist/Misc/advanced_generation.js +34 -40
  56. package/dist/Misc/advanced_generation.js.map +1 -1
  57. package/dist/Misc/createNewUser.d.ts +1 -1
  58. package/dist/Misc/createNewUser.js +22 -26
  59. package/dist/Misc/createNewUser.js.map +1 -1
  60. package/dist/Misc/entity_subclasses_codegen.d.ts +2 -2
  61. package/dist/Misc/entity_subclasses_codegen.d.ts.map +1 -1
  62. package/dist/Misc/entity_subclasses_codegen.js +33 -40
  63. package/dist/Misc/entity_subclasses_codegen.js.map +1 -1
  64. package/dist/Misc/graphql_server_codegen.js +36 -41
  65. package/dist/Misc/graphql_server_codegen.js.map +1 -1
  66. package/dist/Misc/runCommand.d.ts +1 -1
  67. package/dist/Misc/runCommand.js +13 -20
  68. package/dist/Misc/runCommand.js.map +1 -1
  69. package/dist/Misc/sql_logging.d.ts +7 -1
  70. package/dist/Misc/sql_logging.d.ts.map +1 -1
  71. package/dist/Misc/sql_logging.js +40 -53
  72. package/dist/Misc/sql_logging.js.map +1 -1
  73. package/dist/Misc/status_logging.js +45 -60
  74. package/dist/Misc/status_logging.js.map +1 -1
  75. package/dist/Misc/system_integrity.d.ts +1 -1
  76. package/dist/Misc/system_integrity.d.ts.map +1 -1
  77. package/dist/Misc/system_integrity.js +12 -16
  78. package/dist/Misc/system_integrity.js.map +1 -1
  79. package/dist/Misc/temp_batch_file.js +15 -22
  80. package/dist/Misc/temp_batch_file.js.map +1 -1
  81. package/dist/Misc/util.d.ts.map +1 -1
  82. package/dist/Misc/util.js +17 -28
  83. package/dist/Misc/util.js.map +1 -1
  84. package/dist/index.d.ts +21 -20
  85. package/dist/index.d.ts.map +1 -1
  86. package/dist/index.js +22 -40
  87. package/dist/index.js.map +1 -1
  88. package/dist/runCodeGen.d.ts +1 -0
  89. package/dist/runCodeGen.d.ts.map +1 -1
  90. package/dist/runCodeGen.js +151 -178
  91. package/dist/runCodeGen.js.map +1 -1
  92. package/package.json +24 -21
@@ -1,45 +1,16 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
25
- var __importDefault = (this && this.__importDefault) || function (mod) {
26
- return (mod && mod.__esModule) ? mod : { "default": mod };
27
- };
28
- Object.defineProperty(exports, "__esModule", { value: true });
29
- exports.SQLCodeGenBase = exports.SPType = void 0;
30
- const core_1 = require("@memberjunction/core");
31
- const status_logging_1 = require("../Misc/status_logging");
32
- const fs = __importStar(require("fs"));
33
- const path_1 = __importDefault(require("path"));
34
- const sql_1 = require("./sql");
35
- const config_1 = require("../Config/config");
36
- const manage_metadata_1 = require("./manage-metadata");
37
- const sqlserver_dataprovider_1 = require("@memberjunction/sqlserver-dataprovider");
38
- const util_1 = require("../Misc/util");
39
- const global_1 = require("@memberjunction/global");
40
- const sql_logging_1 = require("../Misc/sql_logging");
41
- const temp_batch_file_1 = require("../Misc/temp_batch_file");
42
- exports.SPType = {
1
+ import { EntityInfo, Metadata } from '@memberjunction/core';
2
+ import { logError, logStatus, logWarning, startSpinner, updateSpinner, succeedSpinner, failSpinner } from '../Misc/status_logging.js';
3
+ import * as fs from 'fs';
4
+ import path from 'path';
5
+ import { SQLUtilityBase } from './sql.js';
6
+ import { autoIndexForeignKeys, configInfo, customSqlScripts, dbDatabase, mjCoreSchema, MAX_INDEX_NAME_LENGTH } from '../Config/config.js';
7
+ import { ManageMetadataBase } from './manage-metadata.js';
8
+ import { UserCache } from '@memberjunction/sqlserver-dataprovider';
9
+ import { combineFiles, logIf, sortBySequenceAndCreatedAt } from '../Misc/util.js';
10
+ import { MJGlobal } from '@memberjunction/global';
11
+ import { SQLLogging } from '../Misc/sql_logging.js';
12
+ import { TempBatchFile } from '../Misc/temp_batch_file.js';
13
+ export const SPType = {
43
14
  Create: 'Create',
44
15
  Update: 'Update',
45
16
  Delete: 'Delete',
@@ -49,53 +20,55 @@ exports.SPType = {
49
20
  * databases. The base class implements support for SQL Server. In future versions of MJ, we will break out an abstract base class that has the skeleton of the logic and then the SQL Server version will be a sub-class
50
21
  * of that abstract base class and other databases will be sub-classes of the abstract base class as well.
51
22
  */
52
- class SQLCodeGenBase {
53
- _sqlUtilityObject = global_1.MJGlobal.Instance.ClassFactory.CreateInstance(sql_1.SQLUtilityBase);
23
+ export class SQLCodeGenBase {
24
+ constructor() {
25
+ this._sqlUtilityObject = MJGlobal.Instance.ClassFactory.CreateInstance(SQLUtilityBase);
26
+ /**
27
+ * Array of entity names that qualify for forced regeneration based on the whereClause filter
28
+ */
29
+ this.entitiesQualifiedForForcedRegeneration = [];
30
+ /**
31
+ * Flag indicating whether to filter entities for forced regeneration based on entityWhereClause
32
+ */
33
+ this.filterEntitiesQualifiedForRegeneration = false;
34
+ /**
35
+ * Tracks cascade delete dependencies between entities.
36
+ * Key: Entity ID whose update/delete SP is called by other entities' delete SPs
37
+ * Value: Set of Entity IDs that have CascadeDeletes=true and call this entity's update/delete SP
38
+ */
39
+ this.cascadeDeleteDependencies = new Map();
40
+ /**
41
+ * Tracks entities that need their delete stored procedures regenerated due to cascade dependencies
42
+ */
43
+ this.entitiesNeedingDeleteSPRegeneration = new Set();
44
+ /**
45
+ * Ordered list of entity IDs for delete SP regeneration (dependency order)
46
+ */
47
+ this.orderedEntitiesForDeleteSPRegeneration = [];
48
+ }
54
49
  get SQLUtilityObject() {
55
50
  return this._sqlUtilityObject;
56
51
  }
57
- /**
58
- * Array of entity names that qualify for forced regeneration based on the whereClause filter
59
- */
60
- entitiesQualifiedForForcedRegeneration = [];
61
- /**
62
- * Flag indicating whether to filter entities for forced regeneration based on entityWhereClause
63
- */
64
- filterEntitiesQualifiedForRegeneration = false;
65
- /**
66
- * Tracks cascade delete dependencies between entities.
67
- * Key: Entity ID whose update/delete SP is called by other entities' delete SPs
68
- * Value: Set of Entity IDs that have CascadeDeletes=true and call this entity's update/delete SP
69
- */
70
- cascadeDeleteDependencies = new Map();
71
- /**
72
- * Tracks entities that need their delete stored procedures regenerated due to cascade dependencies
73
- */
74
- entitiesNeedingDeleteSPRegeneration = new Set();
75
- /**
76
- * Ordered list of entity IDs for delete SP regeneration (dependency order)
77
- */
78
- orderedEntitiesForDeleteSPRegeneration = [];
79
52
  async manageSQLScriptsAndExecution(pool, entities, directory, currentUser) {
80
53
  try {
81
54
  // Build list of entities qualified for forced regeneration if entityWhereClause is provided
82
- if (config_1.configInfo.forceRegeneration?.enabled && config_1.configInfo.forceRegeneration?.entityWhereClause) {
55
+ if (configInfo.forceRegeneration?.enabled && configInfo.forceRegeneration?.entityWhereClause) {
83
56
  this.filterEntitiesQualifiedForRegeneration = true; // Enable filtering
84
57
  try {
85
- const whereClause = config_1.configInfo.forceRegeneration.entityWhereClause;
58
+ const whereClause = configInfo.forceRegeneration.entityWhereClause;
86
59
  const query = `
87
60
  SELECT Name
88
- FROM [${config_1.mjCoreSchema}].[Entity]
61
+ FROM [${mjCoreSchema}].[Entity]
89
62
  WHERE ${whereClause}
90
63
  `;
91
64
  const result = await pool.request().query(query);
92
65
  this.entitiesQualifiedForForcedRegeneration = result.recordset.map((r) => r.Name);
93
- (0, status_logging_1.logStatus)(`Force regeneration filter enabled: ${this.entitiesQualifiedForForcedRegeneration.length} entities qualified based on entityWhereClause: ${whereClause}`);
66
+ logStatus(`Force regeneration filter enabled: ${this.entitiesQualifiedForForcedRegeneration.length} entities qualified based on entityWhereClause: ${whereClause}`);
94
67
  }
95
68
  catch (error) {
96
- (0, status_logging_1.logError)(`CRITICAL ERROR: Failed to execute forceRegeneration.entityWhereClause query: ${error}`);
97
- (0, status_logging_1.logError)(`WHERE clause: ${config_1.configInfo.forceRegeneration.entityWhereClause}`);
98
- (0, status_logging_1.logError)(`Stopping execution due to invalid entityWhereClause configuration`);
69
+ logError(`CRITICAL ERROR: Failed to execute forceRegeneration.entityWhereClause query: ${error}`);
70
+ logError(`WHERE clause: ${configInfo.forceRegeneration.entityWhereClause}`);
71
+ logError(`Stopping execution due to invalid entityWhereClause configuration`);
99
72
  throw new Error(`Invalid forceRegeneration.entityWhereClause: ${error}`);
100
73
  }
101
74
  }
@@ -103,32 +76,32 @@ class SQLCodeGenBase {
103
76
  // we have custom base views, need to have them defined before we do
104
77
  // the rest as the generated stuff might use custom base views in compiled
105
78
  // objects like spCreate for a given entity might reference the vw for that entity
106
- (0, status_logging_1.startSpinner)('Running custom SQL scripts...');
79
+ startSpinner('Running custom SQL scripts...');
107
80
  const startTime = new Date();
108
81
  if (!await this.runCustomSQLScripts(pool, 'before-sql')) {
109
- (0, status_logging_1.failSpinner)('Failed to run custom SQL scripts');
82
+ failSpinner('Failed to run custom SQL scripts');
110
83
  return false;
111
84
  }
112
- (0, status_logging_1.succeedSpinner)(`Custom SQL scripts completed (${(new Date().getTime() - startTime.getTime()) / 1000}s)`);
85
+ succeedSpinner(`Custom SQL scripts completed (${(new Date().getTime() - startTime.getTime()) / 1000}s)`);
113
86
  // ALWAYS use the first filter where we only include entities that have IncludeInAPI = 1
114
87
  // Entities are already sorted by name in PostProcessEntityMetadata (see providerBase.ts)
115
88
  const baselineEntities = entities.filter(e => e.IncludeInAPI);
116
- const includedEntities = baselineEntities.filter(e => config_1.configInfo.excludeSchemas.find(s => s.toLowerCase() === e.SchemaName.toLowerCase()) === undefined); //only include entities that are NOT in the excludeSchemas list
117
- const excludedEntities = baselineEntities.filter(e => config_1.configInfo.excludeSchemas.find(s => s.toLowerCase() === e.SchemaName.toLowerCase()) !== undefined); //only include entities that ARE in the excludeSchemas list in this array
89
+ const includedEntities = baselineEntities.filter(e => configInfo.excludeSchemas.find(s => s.toLowerCase() === e.SchemaName.toLowerCase()) === undefined); //only include entities that are NOT in the excludeSchemas list
90
+ const excludedEntities = baselineEntities.filter(e => configInfo.excludeSchemas.find(s => s.toLowerCase() === e.SchemaName.toLowerCase()) !== undefined); //only include entities that ARE in the excludeSchemas list in this array
118
91
  // Initialize temp batch files for each schema
119
92
  // These will be populated as SQL is generated and will be used for actual execution
120
93
  const schemas = Array.from(new Set(baselineEntities.map(e => e.SchemaName)));
121
- temp_batch_file_1.TempBatchFile.initialize(directory, schemas);
94
+ TempBatchFile.initialize(directory, schemas);
122
95
  // STEP 1.5 - Check for cascade delete dependencies that require regeneration
123
- (0, status_logging_1.startSpinner)('Analyzing cascade delete dependencies...');
96
+ startSpinner('Analyzing cascade delete dependencies...');
124
97
  await this.markEntitiesForCascadeDeleteRegeneration(pool, includedEntities);
125
- (0, status_logging_1.succeedSpinner)('Cascade delete dependency analysis completed');
98
+ succeedSpinner('Cascade delete dependency analysis completed');
126
99
  // STEP 2(a) - clean out all *.generated.sql and *.permissions.generated.sql files from the directory
127
- (0, status_logging_1.startSpinner)('Cleaning generated files...');
100
+ startSpinner('Cleaning generated files...');
128
101
  this.deleteGeneratedEntityFiles(directory, baselineEntities);
129
- (0, status_logging_1.succeedSpinner)('Cleaned generated files');
102
+ succeedSpinner('Cleaned generated files');
130
103
  // STEP 2(b) - generate all the SQL files and execute them
131
- (0, status_logging_1.startSpinner)(`Generating SQL for ${includedEntities.length} entities...`);
104
+ startSpinner(`Generating SQL for ${includedEntities.length} entities...`);
132
105
  const step2StartTime = new Date();
133
106
  // First, separate entities that need cascade delete regeneration from others
134
107
  const entitiesWithoutCascadeRegeneration = includedEntities.filter(e => !this.entitiesNeedingDeleteSPRegeneration.has(e.ID));
@@ -147,12 +120,12 @@ class SQLCodeGenBase {
147
120
  enableSQLLoggingForNewOrModifiedEntities: true
148
121
  }); // enable sql logging for NEW entities....
149
122
  if (!genResult.Success) {
150
- (0, status_logging_1.failSpinner)('Failed to generate entity SQL files');
123
+ failSpinner('Failed to generate entity SQL files');
151
124
  return false;
152
125
  }
153
126
  // Generate SQL for cascade delete regenerations in dependency order (sequentially)
154
127
  if (entitiesForCascadeRegeneration.length > 0) {
155
- (0, status_logging_1.updateSpinner)(`Regenerating ${entitiesForCascadeRegeneration.length} delete SPs in dependency order...`);
128
+ updateSpinner(`Regenerating ${entitiesForCascadeRegeneration.length} delete SPs in dependency order...`);
156
129
  const cascadeGenResult = await this.generateAndExecuteEntitySQLToSeparateFiles({
157
130
  pool,
158
131
  entities: entitiesForCascadeRegeneration,
@@ -164,13 +137,13 @@ class SQLCodeGenBase {
164
137
  enableSQLLoggingForNewOrModifiedEntities: true
165
138
  });
166
139
  if (!cascadeGenResult.Success) {
167
- (0, status_logging_1.failSpinner)('Failed to regenerate cascade delete SPs');
140
+ failSpinner('Failed to regenerate cascade delete SPs');
168
141
  return false;
169
142
  }
170
143
  genResult.Files.push(...cascadeGenResult.Files);
171
144
  }
172
145
  // STEP 2(c) - for the excludedEntities, while we don't want to generate SQL, we do want to generate the permissions files for them
173
- (0, status_logging_1.updateSpinner)(`Generating permissions for ${excludedEntities.length} excluded entities...`);
146
+ updateSpinner(`Generating permissions for ${excludedEntities.length} excluded entities...`);
174
147
  const genResult2 = await this.generateAndExecuteEntitySQLToSeparateFiles({
175
148
  pool,
176
149
  entities: excludedEntities,
@@ -182,88 +155,88 @@ class SQLCodeGenBase {
182
155
  enableSQLLoggingForNewOrModifiedEntities: false /*don't log this stuff, it is just permissions for excluded entities*/
183
156
  });
184
157
  if (!genResult2.Success) {
185
- (0, status_logging_1.failSpinner)('Failed to generate permissions for excluded entities');
158
+ failSpinner('Failed to generate permissions for excluded entities');
186
159
  return false;
187
160
  }
188
- (0, status_logging_1.succeedSpinner)(`Entity generation completed (${(new Date().getTime() - step2StartTime.getTime()) / 1000}s)`);
161
+ succeedSpinner(`Entity generation completed (${(new Date().getTime() - step2StartTime.getTime()) / 1000}s)`);
189
162
  // STEP 2(d) now that we've generated the SQL, let's create a combined file in each schema sub-directory for convenience for a DBA
190
- (0, status_logging_1.startSpinner)('Creating combined SQL files...');
163
+ startSpinner('Creating combined SQL files...');
191
164
  const allEntityFiles = this.createCombinedEntitySQLFiles(directory, baselineEntities);
192
- (0, status_logging_1.succeedSpinner)(`Created combined SQL files for ${allEntityFiles.length} schemas`);
165
+ succeedSpinner(`Created combined SQL files for ${allEntityFiles.length} schemas`);
193
166
  // STEP 2(e) ---- FINALLY, we execute SQL in proper dependency order
194
167
  // Use temp batch files (which maintain CodeGen log order) if available, otherwise fall back to combined files
195
- (0, status_logging_1.startSpinner)('Executing entity SQL files...');
168
+ startSpinner('Executing entity SQL files...');
196
169
  const step2eStartTime = new Date();
197
170
  let executionSuccess = false;
198
- if (temp_batch_file_1.TempBatchFile.hasContent()) {
171
+ if (TempBatchFile.hasContent()) {
199
172
  // Execute temp batch files in dependency order (matches CodeGen run log)
200
- const tempFiles = temp_batch_file_1.TempBatchFile.getTempFilePaths();
201
- (0, util_1.logIf)(config_1.configInfo?.verboseOutput ?? false, `Executing ${tempFiles.length} temp batch file(s) in dependency order`);
202
- executionSuccess = await this.SQLUtilityObject.executeSQLFiles(tempFiles, config_1.configInfo?.verboseOutput ?? false);
173
+ const tempFiles = TempBatchFile.getTempFilePaths();
174
+ logIf(configInfo?.verboseOutput ?? false, `Executing ${tempFiles.length} temp batch file(s) in dependency order`);
175
+ executionSuccess = await this.SQLUtilityObject.executeSQLFiles(tempFiles, configInfo?.verboseOutput ?? false);
203
176
  // Clean up temp files after execution
204
- temp_batch_file_1.TempBatchFile.cleanup();
177
+ TempBatchFile.cleanup();
205
178
  }
206
179
  else {
207
180
  // Fall back to combined files (for backward compatibility or if temp files weren't created)
208
- (0, util_1.logIf)(config_1.configInfo?.verboseOutput ?? false, `Executing ${allEntityFiles.length} combined file(s)`);
209
- executionSuccess = await this.SQLUtilityObject.executeSQLFiles(allEntityFiles, config_1.configInfo?.verboseOutput ?? false);
181
+ logIf(configInfo?.verboseOutput ?? false, `Executing ${allEntityFiles.length} combined file(s)`);
182
+ executionSuccess = await this.SQLUtilityObject.executeSQLFiles(allEntityFiles, configInfo?.verboseOutput ?? false);
210
183
  }
211
184
  if (!executionSuccess) {
212
- (0, status_logging_1.failSpinner)('Failed to execute entity SQL files');
213
- temp_batch_file_1.TempBatchFile.cleanup(); // Cleanup on error
185
+ failSpinner('Failed to execute entity SQL files');
186
+ TempBatchFile.cleanup(); // Cleanup on error
214
187
  return false;
215
188
  }
216
189
  const step2eEndTime = new Date();
217
- (0, status_logging_1.succeedSpinner)(`SQL execution completed (${(step2eEndTime.getTime() - step2eStartTime.getTime()) / 1000}s)`);
218
- const manageMD = global_1.MJGlobal.Instance.ClassFactory.CreateInstance(manage_metadata_1.ManageMetadataBase);
190
+ succeedSpinner(`SQL execution completed (${(step2eEndTime.getTime() - step2eStartTime.getTime()) / 1000}s)`);
191
+ const manageMD = MJGlobal.Instance.ClassFactory.CreateInstance(ManageMetadataBase);
219
192
  // STEP 3 - re-run the process to manage entity fields since the Step 1 and 2 above might have resulted in differences in base view columns compared to what we had at first
220
193
  // we CAN skip the entity field values part because that wouldn't change from the first time we ran it
221
194
  // Run advanced generation here in case new virtual fields added, so we do NOT run advanced geneartion in the main manageMetadata() call
222
- (0, status_logging_1.startSpinner)('Managing entity fields metadata...');
223
- if (!await manageMD.manageEntityFields(pool, config_1.configInfo.excludeSchemas, true, true, currentUser, false)) {
224
- (0, status_logging_1.failSpinner)('Failed to manage entity fields');
195
+ startSpinner('Managing entity fields metadata...');
196
+ if (!await manageMD.manageEntityFields(pool, configInfo.excludeSchemas, true, true, currentUser, false)) {
197
+ failSpinner('Failed to manage entity fields');
225
198
  return false;
226
199
  }
227
- (0, status_logging_1.succeedSpinner)('Entity fields metadata updated');
200
+ succeedSpinner('Entity fields metadata updated');
228
201
  // no logStatus/timer for this because manageEntityFields() has its own internal logging for this including the total, so it is redundant to log it here
229
202
  // STEP 4- Apply permissions, executing all .permissions files
230
- (0, status_logging_1.startSpinner)('Applying permissions...');
203
+ startSpinner('Applying permissions...');
231
204
  const step4StartTime = new Date();
232
205
  if (!await this.applyPermissions(pool, directory, baselineEntities)) {
233
- (0, status_logging_1.failSpinner)('Failed to apply permissions');
206
+ failSpinner('Failed to apply permissions');
234
207
  return false;
235
208
  }
236
- (0, status_logging_1.succeedSpinner)(`Permissions applied (${(new Date().getTime() - step4StartTime.getTime()) / 1000}s)`);
209
+ succeedSpinner(`Permissions applied (${(new Date().getTime() - step4StartTime.getTime()) / 1000}s)`);
237
210
  // STEP 5 - execute any custom SQL scripts that should run afterwards
238
- (0, status_logging_1.startSpinner)('Running post-generation SQL scripts...');
211
+ startSpinner('Running post-generation SQL scripts...');
239
212
  const step5StartTime = new Date();
240
213
  if (!await this.runCustomSQLScripts(pool, 'after-sql')) {
241
- (0, status_logging_1.failSpinner)('Failed to run post-generation SQL scripts');
214
+ failSpinner('Failed to run post-generation SQL scripts');
242
215
  return false;
243
216
  }
244
- (0, status_logging_1.succeedSpinner)(`Post-generation scripts completed (${(new Date().getTime() - step5StartTime.getTime()) / 1000}s)`);
245
- (0, status_logging_1.succeedSpinner)(`SQL CodeGen completed successfully (${((new Date().getTime() - startTime.getTime()) / 1000)}s total)`);
217
+ succeedSpinner(`Post-generation scripts completed (${(new Date().getTime() - step5StartTime.getTime()) / 1000}s)`);
218
+ succeedSpinner(`SQL CodeGen completed successfully (${((new Date().getTime() - startTime.getTime()) / 1000)}s total)`);
246
219
  // now - we need to tell our metadata object to refresh itself
247
- const md = new core_1.Metadata();
220
+ const md = new Metadata();
248
221
  await md.Refresh();
249
222
  return true;
250
223
  }
251
224
  catch (err) {
252
- (0, status_logging_1.logError)(err);
225
+ logError(err);
253
226
  // Clean up temp batch files on error
254
- temp_batch_file_1.TempBatchFile.cleanup();
227
+ TempBatchFile.cleanup();
255
228
  return false;
256
229
  }
257
230
  }
258
231
  async runCustomSQLScripts(pool, when) {
259
232
  try {
260
- const scripts = (0, config_1.customSqlScripts)(when);
233
+ const scripts = customSqlScripts(when);
261
234
  let bSuccess = true;
262
235
  if (scripts) {
263
236
  for (let i = 0; i < scripts.length; ++i) {
264
237
  const s = scripts[i];
265
238
  if (!await this.SQLUtilityObject.executeSQLFile(s.scriptFile)) {
266
- (0, status_logging_1.logError)(`Error executing custom '${when}' SQL script ${s.scriptFile}`);
239
+ logError(`Error executing custom '${when}' SQL script ${s.scriptFile}`);
267
240
  bSuccess = false; // keep going if we have more scripts, but make sure we return false
268
241
  }
269
242
  }
@@ -271,7 +244,7 @@ class SQLCodeGenBase {
271
244
  return bSuccess;
272
245
  }
273
246
  catch (e) {
274
- (0, status_logging_1.logError)(e);
247
+ logError(e);
275
248
  return false;
276
249
  }
277
250
  }
@@ -285,7 +258,7 @@ class SQLCodeGenBase {
285
258
  const files = this.getEntityPermissionFileNames(e);
286
259
  let innerSuccess = true;
287
260
  for (const f of files) {
288
- const fullPath = path_1.default.join(directory, f);
261
+ const fullPath = path.join(directory, f);
289
262
  if (fs.existsSync(fullPath)) {
290
263
  const fileBuffer = fs.readFileSync(fullPath);
291
264
  const fileContents = fileBuffer.toString();
@@ -293,28 +266,28 @@ class SQLCodeGenBase {
293
266
  await pool.request().query(fileContents);
294
267
  }
295
268
  catch (e) {
296
- (0, status_logging_1.logError)(`Error executing permissions file ${fullPath} for entity ${e.Name}: ${e}`);
269
+ logError(`Error executing permissions file ${fullPath} for entity ${e.Name}: ${e}`);
297
270
  innerSuccess = false;
298
271
  }
299
272
  }
300
273
  else {
301
274
  // we don't have the file, so we can't execute it, but we should log it as an error
302
275
  // and then keep going
303
- (0, status_logging_1.logError)(`Permissions file ${fullPath} does not exist for entity ${e.Name}`);
276
+ logError(`Permissions file ${fullPath} does not exist for entity ${e.Name}`);
304
277
  }
305
278
  }
306
279
  return innerSuccess;
307
280
  });
308
281
  const results = await Promise.all(promises);
309
282
  if (results.includes(false)) {
310
- (0, status_logging_1.logError)(`Error executing one or more permissions files in batch starting from index ${i}`);
283
+ logError(`Error executing one or more permissions files in batch starting from index ${i}`);
311
284
  bSuccess = false; // keep going, but will return false at the end
312
285
  }
313
286
  }
314
287
  return bSuccess;
315
288
  }
316
289
  catch (err) {
317
- (0, status_logging_1.logError)(err);
290
+ logError(err);
318
291
  return false;
319
292
  }
320
293
  }
@@ -337,7 +310,7 @@ class SQLCodeGenBase {
337
310
  const promises = batch.map(async (e) => {
338
311
  const pkeyField = e.Fields.find(f => f.IsPrimaryKey);
339
312
  if (!pkeyField) {
340
- (0, status_logging_1.logError)(`SKIPPING ENTITY: Entity ${e.Name}, because it does not have a primary key field defined. A table must have a primary key defined to quality to be a MemberJunction entity`);
313
+ logError(`SKIPPING ENTITY: Entity ${e.Name}, because it does not have a primary key field defined. A table must have a primary key defined to quality to be a MemberJunction entity`);
341
314
  return { Success: false, Files: [] };
342
315
  }
343
316
  return this.generateAndExecuteSingleEntitySQLToSeparateFiles({
@@ -360,7 +333,7 @@ class SQLCodeGenBase {
360
333
  return { Success: !bFail, Files: files };
361
334
  }
362
335
  catch (err) {
363
- (0, status_logging_1.logError)(err);
336
+ logError(err);
364
337
  return { Success: false, Files: files };
365
338
  }
366
339
  }
@@ -369,7 +342,7 @@ class SQLCodeGenBase {
369
342
  // for the schemas associated with the specified entities, clean out all the generated files
370
343
  const schemaNames = entities.map(e => e.SchemaName).filter((value, index, self) => self.indexOf(value) === index);
371
344
  for (const s of schemaNames) {
372
- const fullPath = path_1.default.join(directory, s);
345
+ const fullPath = path.join(directory, s);
373
346
  // now, within each schema directory, clean out all the generated files
374
347
  // the generated files map this pattern: *.generated.sql or *.permissions.generated.sql
375
348
  let stats;
@@ -383,14 +356,14 @@ class SQLCodeGenBase {
383
356
  if (stats?.isDirectory()) {
384
357
  const files = fs.readdirSync(fullPath).filter(f => f.endsWith('.generated.sql') || f.endsWith('.permissions.generated.sql'));
385
358
  for (const f of files) {
386
- const filePath = path_1.default.join(fullPath, f);
359
+ const filePath = path.join(fullPath, f);
387
360
  fs.unlinkSync(filePath);
388
361
  }
389
362
  }
390
363
  }
391
364
  }
392
365
  catch (e) {
393
- (0, status_logging_1.logError)(e);
366
+ logError(e);
394
367
  }
395
368
  }
396
369
  createCombinedEntitySQLFiles(directory, entities) {
@@ -399,12 +372,12 @@ class SQLCodeGenBase {
399
372
  const schemaNames = entities.map(e => e.SchemaName).filter((value, index, self) => self.indexOf(value) === index);
400
373
  for (const s of schemaNames) {
401
374
  // generate the all-entities.sql file and all-entities.permissions.sql file in each schema folder
402
- const fullPath = path_1.default.join(directory, s);
375
+ const fullPath = path.join(directory, s);
403
376
  if (fs.statSync(fullPath).isDirectory()) {
404
- (0, util_1.combineFiles)(fullPath, '_all_entities.sql', '*.generated.sql', true);
405
- files.push(path_1.default.join(fullPath, '_all_entities.sql'));
406
- (0, util_1.combineFiles)(fullPath, '_all_entities.permissions.sql', '*.permissions.generated.sql', true);
407
- files.push(path_1.default.join(fullPath, '_all_entities.permissions.sql'));
377
+ combineFiles(fullPath, '_all_entities.sql', '*.generated.sql', true);
378
+ files.push(path.join(fullPath, '_all_entities.sql'));
379
+ combineFiles(fullPath, '_all_entities.permissions.sql', '*.permissions.generated.sql', true);
380
+ files.push(path.join(fullPath, '_all_entities.permissions.sql'));
408
381
  }
409
382
  }
410
383
  return files;
@@ -422,7 +395,7 @@ class SQLCodeGenBase {
422
395
  return { Success: true, Files: files };
423
396
  }
424
397
  catch (err) {
425
- (0, status_logging_1.logError)(err);
398
+ logError(err);
426
399
  return { Success: false, Files: [] };
427
400
  }
428
401
  }
@@ -431,28 +404,35 @@ class SQLCodeGenBase {
431
404
  let shouldLog = false;
432
405
  if (logSql) {
433
406
  // Check if entity is in new or modified lists
434
- const isNewOrModified = !!manage_metadata_1.ManageMetadataBase.newEntityList.find(e => e === entity.Name) ||
435
- !!manage_metadata_1.ManageMetadataBase.modifiedEntityList.find(e => e === entity.Name);
407
+ const isNewOrModified = !!ManageMetadataBase.newEntityList.find(e => e === entity.Name) ||
408
+ !!ManageMetadataBase.modifiedEntityList.find(e => e === entity.Name);
436
409
  // Check if entity is being regenerated due to cascade dependencies
437
410
  const isCascadeDependencyRegeneration = description.toLowerCase().includes('spdelete') &&
438
411
  this.entitiesNeedingDeleteSPRegeneration.has(entity.ID);
439
412
  // Check if force regeneration is enabled for relevant SQL types
440
- const isForceRegeneration = config_1.configInfo.forceRegeneration?.enabled && ((description.toLowerCase().includes('base view') && config_1.configInfo.forceRegeneration.baseViews) ||
441
- (description.toLowerCase().includes('root id function') && config_1.configInfo.forceRegeneration.baseViews) || // TVFs are part of base view infrastructure
442
- (description.toLowerCase().includes('spcreate') && config_1.configInfo.forceRegeneration.spCreate) ||
443
- (description.toLowerCase().includes('spupdate') && config_1.configInfo.forceRegeneration.spUpdate) ||
444
- (description.toLowerCase().includes('spdelete') && config_1.configInfo.forceRegeneration.spDelete) ||
445
- (description.toLowerCase().includes('index') && config_1.configInfo.forceRegeneration.indexes) ||
446
- (description.toLowerCase().includes('full text search') && config_1.configInfo.forceRegeneration.fullTextSearch) ||
447
- (config_1.configInfo.forceRegeneration.allStoredProcedures &&
413
+ const isForceRegeneration = configInfo.forceRegeneration?.enabled && ((description.toLowerCase().includes('base view') && configInfo.forceRegeneration.baseViews) ||
414
+ (description.toLowerCase().includes('root id function') && configInfo.forceRegeneration.baseViews) || // TVFs are part of base view infrastructure
415
+ (description.toLowerCase().includes('spcreate') && configInfo.forceRegeneration.spCreate) ||
416
+ (description.toLowerCase().includes('spupdate') && configInfo.forceRegeneration.spUpdate) ||
417
+ (description.toLowerCase().includes('spdelete') && configInfo.forceRegeneration.spDelete) ||
418
+ (description.toLowerCase().includes('index') && configInfo.forceRegeneration.indexes) ||
419
+ (description.toLowerCase().includes('full text search') && configInfo.forceRegeneration.fullTextSearch) ||
420
+ (configInfo.forceRegeneration.allStoredProcedures &&
448
421
  (description.toLowerCase().includes('spcreate') ||
449
422
  description.toLowerCase().includes('spupdate') ||
450
423
  description.toLowerCase().includes('spdelete'))));
424
+ // Check if entity has RelatedEntityJoinFields configured (requires view regeneration for metadata-only changes)
425
+ const hasRelatedEntityJoinFields = description.toLowerCase().includes('base view') &&
426
+ entity.Fields.some(f => f.RelatedEntityJoinFieldsConfig !== null);
451
427
  // Determine if we should log based on entity state and force regeneration settings
452
428
  if (isNewOrModified) {
453
429
  // Always log new or modified entities
454
430
  shouldLog = true;
455
431
  }
432
+ else if (hasRelatedEntityJoinFields) {
433
+ // Always regenerate base views for entities with RelatedEntityJoinFields configuration
434
+ shouldLog = true;
435
+ }
456
436
  else if (isCascadeDependencyRegeneration) {
457
437
  // Always log cascade dependency regenerations
458
438
  shouldLog = true;
@@ -471,11 +451,10 @@ class SQLCodeGenBase {
471
451
  }
472
452
  }
473
453
  if (shouldLog) {
474
- sql_logging_1.SQLLogging.appendToSQLLogFile(sql, description);
475
- // Also write to temp batch file for actual execution (matches CodeGen log order)
476
- temp_batch_file_1.TempBatchFile.appendToTempBatchFile(sql, entity.SchemaName);
454
+ SQLLogging.appendToSQLLogFile(sql, description);
455
+ TempBatchFile.appendToTempBatchFile(sql, entity.SchemaName);
477
456
  }
478
- (0, util_1.logIf)(config_1.configInfo.verboseOutput, `SQL Generated for ${entity.Name}: ${description}`);
457
+ logIf(configInfo.verboseOutput, `SQL Generated for ${entity.Name}: ${description}`);
479
458
  }
480
459
  async generateSingleEntitySQLToSeparateFiles(options) {
481
460
  const files = [];
@@ -484,18 +463,18 @@ class SQLCodeGenBase {
484
463
  if (options.writeFiles && !fs.existsSync(options.directory))
485
464
  fs.mkdirSync(options.directory, { recursive: true });
486
465
  // now do the same thing for the /schema directory within the provided directory
487
- const schemaDirectory = path_1.default.join(options.directory, options.entity.SchemaName);
466
+ const schemaDirectory = path.join(options.directory, options.entity.SchemaName);
488
467
  if (options.writeFiles && !fs.existsSync(schemaDirectory))
489
468
  fs.mkdirSync(schemaDirectory, { recursive: true }); // create the directory if it doesn't exist
490
469
  let sRet = '';
491
470
  let permissionsSQL = '';
492
471
  // Indexes for Fkeys for the table
493
472
  if (!options.onlyPermissions) {
494
- const shouldGenerateIndexes = (0, config_1.autoIndexForeignKeys)() || (config_1.configInfo.forceRegeneration?.enabled && config_1.configInfo.forceRegeneration?.indexes);
473
+ const shouldGenerateIndexes = autoIndexForeignKeys() || (configInfo.forceRegeneration?.enabled && configInfo.forceRegeneration?.indexes);
495
474
  const indexSQL = shouldGenerateIndexes ? this.generateIndexesForForeignKeys(options.pool, options.entity) : ''; // generate indexes if auto-indexing is on OR force regeneration is enabled
496
475
  const s = this.generateSingleEntitySQLFileHeader(options.entity, 'Index for Foreign Keys') + indexSQL;
497
476
  if (options.writeFiles) {
498
- const filePath = path_1.default.join(options.directory, this.SQLUtilityObject.getDBObjectFileName('index', options.entity.SchemaName, options.entity.BaseTable, false, true));
477
+ const filePath = path.join(options.directory, this.SQLUtilityObject.getDBObjectFileName('index', options.entity.SchemaName, options.entity.BaseTable, false, true));
499
478
  this.logSQLForNewOrModifiedEntity(options.entity, s, 'Index for Foreign Keys for ' + options.entity.BaseTable, options.enableSQLLoggingForNewOrModifiedEntities);
500
479
  fs.writeFileSync(filePath, s);
501
480
  files.push(filePath);
@@ -517,7 +496,7 @@ class SQLCodeGenBase {
517
496
  const functionName = `fn${options.entity.BaseTable}${field.Name}_GetRootID`;
518
497
  const s = this.generateSingleEntitySQLFileHeader(options.entity, functionName) +
519
498
  this.generateRootIDFunction(options.entity, field);
520
- const filePath = path_1.default.join(options.directory, this.SQLUtilityObject.getDBObjectFileName('function', options.entity.SchemaName, functionName, false, true));
499
+ const filePath = path.join(options.directory, this.SQLUtilityObject.getDBObjectFileName('function', options.entity.SchemaName, functionName, false, true));
521
500
  if (options.writeFiles) {
522
501
  this.logSQLForNewOrModifiedEntity(options.entity, s, `Root ID Function SQL for ${options.entity.Name}.${field.Name}`, options.enableSQLLoggingForNewOrModifiedEntities);
523
502
  fs.writeFileSync(filePath, s);
@@ -529,7 +508,7 @@ class SQLCodeGenBase {
529
508
  }
530
509
  // Generate the base view (which may reference the TVFs created above)
531
510
  const s = this.generateSingleEntitySQLFileHeader(options.entity, options.entity.BaseView) + await this.generateBaseView(options.pool, options.entity);
532
- const filePath = path_1.default.join(options.directory, this.SQLUtilityObject.getDBObjectFileName('view', options.entity.SchemaName, options.entity.BaseView, false, true));
511
+ const filePath = path.join(options.directory, this.SQLUtilityObject.getDBObjectFileName('view', options.entity.SchemaName, options.entity.BaseView, false, true));
533
512
  if (options.writeFiles) {
534
513
  this.logSQLForNewOrModifiedEntity(options.entity, s, `Base View SQL for ${options.entity.Name}`, options.enableSQLLoggingForNewOrModifiedEntities);
535
514
  fs.writeFileSync(filePath, s);
@@ -542,7 +521,7 @@ class SQLCodeGenBase {
542
521
  if (s.length > 0)
543
522
  permissionsSQL += s + '\nGO\n';
544
523
  if (options.writeFiles) {
545
- const filePath = path_1.default.join(options.directory, this.SQLUtilityObject.getDBObjectFileName('view', options.entity.SchemaName, options.entity.BaseView, true, true));
524
+ const filePath = path.join(options.directory, this.SQLUtilityObject.getDBObjectFileName('view', options.entity.SchemaName, options.entity.BaseView, true, true));
546
525
  fs.writeFileSync(filePath, s);
547
526
  this.logSQLForNewOrModifiedEntity(options.entity, s, `Base View Permissions SQL for ${options.entity.Name}`, options.enableSQLLoggingForNewOrModifiedEntities);
548
527
  files.push(filePath);
@@ -553,25 +532,25 @@ class SQLCodeGenBase {
553
532
  sRet += s + '\nGO\n';
554
533
  // CREATE SP
555
534
  if (options.entity.AllowCreateAPI && !options.entity.VirtualEntity) {
556
- const spName = this.getSPName(options.entity, exports.SPType.Create);
535
+ const spName = this.getSPName(options.entity, SPType.Create);
557
536
  // Only generate if spCreateGenerated is true (respects custom SPs where it's false)
558
537
  // forceRegeneration only forces regeneration of SPs where spCreateGenerated=true
559
538
  if (!options.onlyPermissions && options.entity.spCreateGenerated) {
560
539
  // generate the create SP
561
540
  const s = this.generateSingleEntitySQLFileHeader(options.entity, spName) + this.generateSPCreate(options.entity);
562
541
  if (options.writeFiles) {
563
- const filePath = path_1.default.join(options.directory, this.SQLUtilityObject.getDBObjectFileName('sp', options.entity.SchemaName, spName, false, true));
542
+ const filePath = path.join(options.directory, this.SQLUtilityObject.getDBObjectFileName('sp', options.entity.SchemaName, spName, false, true));
564
543
  this.logSQLForNewOrModifiedEntity(options.entity, s, `spCreate SQL for ${options.entity.Name}`, options.enableSQLLoggingForNewOrModifiedEntities);
565
544
  fs.writeFileSync(filePath, s);
566
545
  files.push(filePath);
567
546
  }
568
547
  sRet += s + '\nGO\n';
569
548
  }
570
- const s = this.generateSPPermissions(options.entity, spName, exports.SPType.Create) + '\n\n';
549
+ const s = this.generateSPPermissions(options.entity, spName, SPType.Create) + '\n\n';
571
550
  if (s.length > 0)
572
551
  permissionsSQL += s + '\nGO\n';
573
552
  if (options.writeFiles) {
574
- const filePath = path_1.default.join(options.directory, this.SQLUtilityObject.getDBObjectFileName('sp', options.entity.SchemaName, spName, true, true));
553
+ const filePath = path.join(options.directory, this.SQLUtilityObject.getDBObjectFileName('sp', options.entity.SchemaName, spName, true, true));
575
554
  this.logSQLForNewOrModifiedEntity(options.entity, s, `spCreate Permissions for ${options.entity.Name}`, options.enableSQLLoggingForNewOrModifiedEntities);
576
555
  fs.writeFileSync(filePath, s);
577
556
  files.push(filePath);
@@ -583,25 +562,25 @@ class SQLCodeGenBase {
583
562
  }
584
563
  // UPDATE SP
585
564
  if (options.entity.AllowUpdateAPI && !options.entity.VirtualEntity) {
586
- const spName = this.getSPName(options.entity, exports.SPType.Update);
565
+ const spName = this.getSPName(options.entity, SPType.Update);
587
566
  // Only generate if spUpdateGenerated is true (respects custom SPs where it's false)
588
567
  // forceRegeneration only forces regeneration of SPs where spUpdateGenerated=true
589
568
  if (!options.onlyPermissions && options.entity.spUpdateGenerated) {
590
569
  // generate the update SP
591
570
  const s = this.generateSingleEntitySQLFileHeader(options.entity, spName) + this.generateSPUpdate(options.entity);
592
571
  if (options.writeFiles) {
593
- const filePath = path_1.default.join(options.directory, this.SQLUtilityObject.getDBObjectFileName('sp', options.entity.SchemaName, spName, false, true));
572
+ const filePath = path.join(options.directory, this.SQLUtilityObject.getDBObjectFileName('sp', options.entity.SchemaName, spName, false, true));
594
573
  fs.writeFileSync(filePath, s);
595
574
  this.logSQLForNewOrModifiedEntity(options.entity, s, `spUpdate SQL for ${options.entity.Name}`, options.enableSQLLoggingForNewOrModifiedEntities);
596
575
  files.push(filePath);
597
576
  }
598
577
  sRet += s + '\nGO\n';
599
578
  }
600
- const s = this.generateSPPermissions(options.entity, spName, exports.SPType.Update) + '\n\n';
579
+ const s = this.generateSPPermissions(options.entity, spName, SPType.Update) + '\n\n';
601
580
  if (s.length > 0)
602
581
  permissionsSQL += s + '\nGO\n';
603
582
  if (options.writeFiles) {
604
- const filePath = path_1.default.join(options.directory, this.SQLUtilityObject.getDBObjectFileName('sp', options.entity.SchemaName, spName, true, true));
583
+ const filePath = path.join(options.directory, this.SQLUtilityObject.getDBObjectFileName('sp', options.entity.SchemaName, spName, true, true));
605
584
  this.logSQLForNewOrModifiedEntity(options.entity, s, `spUpdate Permissions for ${options.entity.Name}`, options.enableSQLLoggingForNewOrModifiedEntities);
606
585
  fs.writeFileSync(filePath, s);
607
586
  files.push(filePath);
@@ -613,7 +592,7 @@ class SQLCodeGenBase {
613
592
  }
614
593
  // DELETE SP
615
594
  if (options.entity.AllowDeleteAPI && !options.entity.VirtualEntity) {
616
- const spName = this.getSPName(options.entity, exports.SPType.Delete);
595
+ const spName = this.getSPName(options.entity, SPType.Delete);
617
596
  // Only generate if spDeleteGenerated is true (respects custom SPs where it's false)
618
597
  // OR if this entity has cascade delete dependencies that require regeneration
619
598
  // forceRegeneration only forces regeneration of SPs where spDeleteGenerated=true
@@ -622,22 +601,22 @@ class SQLCodeGenBase {
622
601
  this.entitiesNeedingDeleteSPRegeneration.has(options.entity.ID))) {
623
602
  // generate the delete SP
624
603
  if (this.entitiesNeedingDeleteSPRegeneration.has(options.entity.ID)) {
625
- (0, status_logging_1.logStatus)(` Regenerating ${spName} due to cascade dependency changes`);
604
+ logStatus(` Regenerating ${spName} due to cascade dependency changes`);
626
605
  }
627
606
  const s = this.generateSingleEntitySQLFileHeader(options.entity, spName) + this.generateSPDelete(options.entity);
628
607
  if (options.writeFiles) {
629
- const filePath = path_1.default.join(options.directory, this.SQLUtilityObject.getDBObjectFileName('sp', options.entity.SchemaName, spName, false, true));
608
+ const filePath = path.join(options.directory, this.SQLUtilityObject.getDBObjectFileName('sp', options.entity.SchemaName, spName, false, true));
630
609
  this.logSQLForNewOrModifiedEntity(options.entity, s, `spDelete SQL for ${options.entity.Name}`, options.enableSQLLoggingForNewOrModifiedEntities);
631
610
  fs.writeFileSync(filePath, s);
632
611
  files.push(filePath);
633
612
  }
634
613
  sRet += s + '\nGO\n';
635
614
  }
636
- const s = this.generateSPPermissions(options.entity, spName, exports.SPType.Delete) + '\n\n';
615
+ const s = this.generateSPPermissions(options.entity, spName, SPType.Delete) + '\n\n';
637
616
  if (s.length > 0)
638
617
  permissionsSQL += s + '\nGO\n';
639
618
  if (options.writeFiles) {
640
- const filePath = path_1.default.join(options.directory, this.SQLUtilityObject.getDBObjectFileName('sp', options.entity.SchemaName, spName, true, true));
619
+ const filePath = path.join(options.directory, this.SQLUtilityObject.getDBObjectFileName('sp', options.entity.SchemaName, spName, true, true));
641
620
  this.logSQLForNewOrModifiedEntity(options.entity, s, `spDelete Permissions for ${options.entity.Name}`, options.enableSQLLoggingForNewOrModifiedEntities);
642
621
  fs.writeFileSync(filePath, s);
643
622
  files.push(filePath);
@@ -648,12 +627,12 @@ class SQLCodeGenBase {
648
627
  sRet += s + '\nGO\n';
649
628
  }
650
629
  // check to see if the options.entity supports full text search or not
651
- if (options.entity.FullTextSearchEnabled || (config_1.configInfo.forceRegeneration?.enabled && config_1.configInfo.forceRegeneration?.fullTextSearch)) {
630
+ if (options.entity.FullTextSearchEnabled || (configInfo.forceRegeneration?.enabled && configInfo.forceRegeneration?.fullTextSearch)) {
652
631
  // always generate the code so we can get the function name from the below function call
653
632
  const ft = await this.generateEntityFullTextSearchSQL(options.pool, options.entity);
654
633
  if (!options.onlyPermissions) {
655
634
  // only write the actual sql out if we're not only generating permissions
656
- const filePath = path_1.default.join(options.directory, this.SQLUtilityObject.getDBObjectFileName('full_text_search_function', options.entity.SchemaName, options.entity.BaseTable, false, true));
635
+ const filePath = path.join(options.directory, this.SQLUtilityObject.getDBObjectFileName('full_text_search_function', options.entity.SchemaName, options.entity.BaseTable, false, true));
657
636
  if (options.writeFiles) {
658
637
  this.logSQLForNewOrModifiedEntity(options.entity, ft.sql, `Full Text Search SQL for ${options.entity.Name}`, options.enableSQLLoggingForNewOrModifiedEntities);
659
638
  fs.writeFileSync(filePath, ft.sql);
@@ -664,7 +643,7 @@ class SQLCodeGenBase {
664
643
  const sP = this.generateFullTextSearchFunctionPermissions(options.entity, ft.functionName) + '\n\n';
665
644
  if (sP.length > 0)
666
645
  permissionsSQL += sP + '\nGO\n';
667
- const filePath = path_1.default.join(options.directory, this.SQLUtilityObject.getDBObjectFileName('full_text_search_function', options.entity.SchemaName, options.entity.BaseTable, true, true));
646
+ const filePath = path.join(options.directory, this.SQLUtilityObject.getDBObjectFileName('full_text_search_function', options.entity.SchemaName, options.entity.BaseTable, true, true));
668
647
  if (options.writeFiles) {
669
648
  this.logSQLForNewOrModifiedEntity(options.entity, sP, `Full Text Search Permissions for ${options.entity.Name}`, options.enableSQLLoggingForNewOrModifiedEntities);
670
649
  fs.writeFileSync(filePath, sP);
@@ -678,17 +657,17 @@ class SQLCodeGenBase {
678
657
  return { sql: sRet, permissionsSQL: permissionsSQL, files: files };
679
658
  }
680
659
  catch (err) {
681
- (0, status_logging_1.logError)(err);
660
+ logError(err);
682
661
  return null;
683
662
  }
684
663
  }
685
664
  getSPName(entity, type) {
686
665
  switch (type) {
687
- case exports.SPType.Create:
666
+ case SPType.Create:
688
667
  return entity.spCreate && entity.spCreate.length > 0 ? entity.spCreate : 'spCreate' + entity.BaseTableCodeName;
689
- case exports.SPType.Update:
668
+ case SPType.Update:
690
669
  return entity.spUpdate && entity.spUpdate.length > 0 ? entity.spUpdate : 'spUpdate' + entity.BaseTableCodeName;
691
- case exports.SPType.Delete:
670
+ case SPType.Delete:
692
671
  return entity.spDelete && entity.spDelete.length > 0 ? entity.spDelete : 'spDelete' + entity.BaseTableCodeName;
693
672
  }
694
673
  }
@@ -700,11 +679,11 @@ class SQLCodeGenBase {
700
679
  if (!entity.VirtualEntity) {
701
680
  // only add each SP file if the Allow flags are set to true, doesn't matter if the SPs are generated or not, we always generate permissions
702
681
  if (entity.AllowCreateAPI)
703
- files.push(this.SQLUtilityObject.getDBObjectFileName('sp', entity.SchemaName, this.getSPName(entity, exports.SPType.Create), true, true));
682
+ files.push(this.SQLUtilityObject.getDBObjectFileName('sp', entity.SchemaName, this.getSPName(entity, SPType.Create), true, true));
704
683
  if (entity.AllowUpdateAPI)
705
- files.push(this.SQLUtilityObject.getDBObjectFileName('sp', entity.SchemaName, this.getSPName(entity, exports.SPType.Update), true, true));
684
+ files.push(this.SQLUtilityObject.getDBObjectFileName('sp', entity.SchemaName, this.getSPName(entity, SPType.Update), true, true));
706
685
  if (entity.AllowDeleteAPI)
707
- files.push(this.SQLUtilityObject.getDBObjectFileName('sp', entity.SchemaName, this.getSPName(entity, exports.SPType.Delete), true, true));
686
+ files.push(this.SQLUtilityObject.getDBObjectFileName('sp', entity.SchemaName, this.getSPName(entity, SPType.Delete), true, true));
708
687
  }
709
688
  if (entity.FullTextSearchEnabled)
710
689
  files.push(this.SQLUtilityObject.getDBObjectFileName('full_text_search_function', entity.SchemaName, entity.BaseTable, true, true));
@@ -731,7 +710,7 @@ class SQLCodeGenBase {
731
710
  sOutput += this.generateSPCreate(entity) + '\n\n';
732
711
  else
733
712
  // custom SP, still generate the permissions
734
- sOutput += this.generateSPPermissions(entity, entity.spCreate, exports.SPType.Create) + '\n\n';
713
+ sOutput += this.generateSPPermissions(entity, entity.spCreate, SPType.Create) + '\n\n';
735
714
  }
736
715
  if (entity.AllowUpdateAPI && !entity.VirtualEntity) {
737
716
  if (entity.spUpdateGenerated)
@@ -739,7 +718,7 @@ class SQLCodeGenBase {
739
718
  sOutput += this.generateSPUpdate(entity) + '\n\n';
740
719
  else
741
720
  // custom SP, still generate the permissions
742
- sOutput += this.generateSPPermissions(entity, entity.spUpdate, exports.SPType.Update) + '\n\n';
721
+ sOutput += this.generateSPPermissions(entity, entity.spUpdate, SPType.Update) + '\n\n';
743
722
  }
744
723
  if (entity.AllowDeleteAPI && !entity.VirtualEntity) {
745
724
  if (entity.spDeleteGenerated)
@@ -747,7 +726,7 @@ class SQLCodeGenBase {
747
726
  sOutput += this.generateSPDelete(entity) + '\n\n';
748
727
  else
749
728
  // custom SP, still generate the permissions
750
- sOutput += this.generateSPPermissions(entity, entity.spDelete, exports.SPType.Delete) + '\n\n';
729
+ sOutput += this.generateSPPermissions(entity, entity.spDelete, SPType.Delete) + '\n\n';
751
730
  }
752
731
  // check to see if the entity supports full text search or not
753
732
  if (entity.FullTextSearchEnabled) {
@@ -757,7 +736,7 @@ class SQLCodeGenBase {
757
736
  }
758
737
  async generateEntityFullTextSearchSQL(pool, entity) {
759
738
  let sql = '';
760
- const catalogName = entity.FullTextCatalog && entity.FullTextCatalog.length > 0 ? entity.FullTextCatalog : config_1.dbDatabase + '_FullTextCatalog';
739
+ const catalogName = entity.FullTextCatalog && entity.FullTextCatalog.length > 0 ? entity.FullTextCatalog : dbDatabase + '_FullTextCatalog';
761
740
  if (entity.FullTextCatalogGenerated) {
762
741
  // this situation means we have a generated catalog and the user has provided a name specific to THIS entity
763
742
  sql += ` -- CREATE THE FULL TEXT CATALOG FOR THE ENTITY, IF NOT ALREADY CREATED
@@ -810,8 +789,8 @@ class SQLCodeGenBase {
810
789
  throw new Error(`FullTextSearchFunctionGenerated is true for entity ${entity.Name}, but no fields are marked as FullTextSearchEnabled`);
811
790
  if (!entity.FullTextSearchFunction || entity.FullTextSearchFunction.length === 0) {
812
791
  // update this in the DB
813
- const md = new core_1.Metadata();
814
- const u = sqlserver_dataprovider_1.UserCache.Instance.Users[0];
792
+ const md = new Metadata();
793
+ const u = UserCache.Instance.Users[0];
815
794
  if (!u)
816
795
  throw new Error('Could not find the first user in the cache, cant generate the full text search function without a user');
817
796
  const e = await md.GetEntityObject('Entities', u);
@@ -900,8 +879,8 @@ class SQLCodeGenBase {
900
879
  if (f.RelatedEntity && f.RelatedEntity.length > 0) {
901
880
  // we have an fkey, so generate the create index
902
881
  let indexName = `IDX_AUTO_MJ_FKEY_${entity.BaseTableCodeName}_${f.CodeName}`; // use code names in case the table and/or field names have special characters or spaces/etc
903
- if (indexName.length > config_1.MAX_INDEX_NAME_LENGTH)
904
- indexName = indexName.substring(0, config_1.MAX_INDEX_NAME_LENGTH); // truncate to max length if necessary
882
+ if (indexName.length > MAX_INDEX_NAME_LENGTH)
883
+ indexName = indexName.substring(0, MAX_INDEX_NAME_LENGTH); // truncate to max length if necessary
905
884
  if (sOutput.length > 0)
906
885
  sOutput += '\n\n'; // do this way so we don't end up with a trailing newline at end of the string/file
907
886
  sOutput += `-- Index for foreign key ${f.Name} in table ${entity.BaseTable}
@@ -1047,7 +1026,7 @@ GO
1047
1026
  const relatedFieldsJoinString = this.generateBaseViewJoins(entity, entity.Fields);
1048
1027
  const permissions = this.generateViewPermissions(entity);
1049
1028
  const whereClause = entity.DeleteType === 'Soft' ? `WHERE
1050
- ${classNameFirstChar}.[${core_1.EntityInfo.DeletedAtFieldName}] IS NULL
1029
+ ${classNameFirstChar}.[${EntityInfo.DeletedAtFieldName}] IS NULL
1051
1030
  ` : '';
1052
1031
  // Detect recursive foreign keys and generate TVF joins and root field selects
1053
1032
  const recursiveFKs = this.detectRecursiveForeignKeys(entity);
@@ -1088,7 +1067,12 @@ ${whereClause}GO${permissions}
1088
1067
  const classNameFirstChar = entity.ClassName.charAt(0).toLowerCase();
1089
1068
  for (let i = 0; i < entityFields.length; i++) {
1090
1069
  const ef = entityFields[i];
1091
- if (ef.RelatedEntityID && ef.IncludeRelatedEntityNameFieldInBaseView && ef._RelatedEntityTableAlias) {
1070
+ // Generate SQL JOIN for related entities that have configured join fields
1071
+ // _RelatedEntityJoinFieldMappings is populated during field analysis if:
1072
+ // - IncludeRelatedEntityNameFieldInBaseView is true (legacy), OR
1073
+ // - RelatedEntityJoinFieldsConfig specifies fields to join
1074
+ // This generates the JOIN clause; the actual field aliases are added separately in generateBaseViewFields()
1075
+ if (ef.RelatedEntityID && ef._RelatedEntityJoinFieldMappings && ef._RelatedEntityJoinFieldMappings.length > 0 && ef._RelatedEntityTableAlias) {
1092
1076
  sOutput += sOutput == '' ? '' : '\n';
1093
1077
  sOutput += `${ef.AllowsNull ? 'LEFT OUTER' : 'INNER'} JOIN\n ${'[' + ef.RelatedEntitySchemaName + '].'}[${ef._RelatedEntityNameFieldIsVirtual ? ef.RelatedEntityBaseView : ef.RelatedEntityBaseTable}] AS ${ef._RelatedEntityTableAlias}\n ON\n [${classNameFirstChar}].[${ef.Name}] = ${ef._RelatedEntityTableAlias}.[${ef.RelatedEntityFieldName}]`;
1094
1078
  }
@@ -1098,51 +1082,117 @@ ${whereClause}GO${permissions}
1098
1082
  async generateBaseViewRelatedFieldsString(pool, entityFields) {
1099
1083
  let sOutput = '';
1100
1084
  let fieldCount = 0;
1101
- const manageMD = global_1.MJGlobal.Instance.ClassFactory.CreateInstance(manage_metadata_1.ManageMetadataBase);
1102
- // next get the fields that are related entities and have the IncludeRelatedEntityNameFieldInBaseView flag set to true
1103
- const qualifyingFields = entityFields.filter(f => f.RelatedEntityID && f.IncludeRelatedEntityNameFieldInBaseView);
1085
+ const manageMD = MJGlobal.Instance.ClassFactory.CreateInstance(ManageMetadataBase);
1086
+ const md = new Metadata();
1087
+ const allGeneratedAliases = [];
1088
+ // Get fields that are related entities with join field configuration.
1089
+ //
1090
+ // BACKWARD COMPATIBILITY LOGIC:
1091
+ // This handles two cases:
1092
+ // 1. Legacy behavior: IncludeRelatedEntityNameFieldInBaseView=true, no RelatedEntityJoinFieldsConfig
1093
+ // → Automatically defaults to { mode: 'extend' } and joins the related entity's NameField (as before)
1094
+ // 2. New behavior: RelatedEntityJoinFieldsConfig specified
1095
+ // → Can 'extend' the NameField with additional fields, 'override' it completely, or 'disable' joins
1096
+ //
1097
+ // Result: _RelatedEntityJoinFieldMappings is populated with all fields to be joined from the related entity.
1098
+ // If both old and new configs are set, they work together (new fields extend or replace the NameField).
1099
+ const qualifyingFields = entityFields.filter(f => f.RelatedEntityID && (f.IncludeRelatedEntityNameFieldInBaseView || f.RelatedEntityJoinFieldsConfig));
1104
1100
  for (const ef of qualifyingFields) {
1105
- const { nameField, nameFieldIsVirtual } = this.getIsNameFieldForSingleEntity(ef.RelatedEntity);
1106
- if (nameField !== '') {
1107
- // only add to the output, if we found a name field for the related entity.
1108
- ef._RelatedEntityTableAlias = ef.RelatedEntityClassName + '_' + ef.Name;
1109
- ef._RelatedEntityNameFieldIsVirtual = nameFieldIsVirtual;
1110
- // This next section generates a field name for the new virtual field and makes sure it doesn't collide with a field in the base table
1111
- const candidateName = this.stripID(ef.Name);
1112
- // Skip if candidateName is empty (e.g., field named exactly "ID")
1113
- // This happens in table-per-type inheritance where child.ID is FK to parent.ID
1114
- // stripID("ID") returns "" which would generate invalid SQL: AS []
1115
- if (candidateName.trim().length === 0) {
1116
- (0, status_logging_1.logStatus)(` Skipping related entity name field for ${ef.Name} in entity - stripID returned empty string (likely inheritance pattern)`);
1117
- continue; // Skip this field entirely - no valid alias can be generated
1101
+ const config = ef.RelatedEntityJoinFieldsConfig || { mode: 'extend' };
1102
+ if (config.mode === 'disable') {
1103
+ continue;
1104
+ }
1105
+ ef._RelatedEntityJoinFieldMappings = [];
1106
+ let anyFieldIsVirtual = false;
1107
+ // 1. Handle NameField (if not overridden)
1108
+ // In 'extend' mode: include the NameField (backward compatible with IncludeRelatedEntityNameFieldInBaseView)
1109
+ // In 'override' mode: skip the NameField, only use explicitly configured fields
1110
+ if (config.mode !== 'override' && ef.IncludeRelatedEntityNameFieldInBaseView) {
1111
+ const { nameField, nameFieldIsVirtual } = this.getIsNameFieldForSingleEntity(ef.RelatedEntity);
1112
+ if (nameField !== '') {
1113
+ // only add to the output, if we found a name field for the related entity.
1114
+ ef._RelatedEntityTableAlias = ef.RelatedEntityClassName + '_' + ef.Name;
1115
+ // This next section generates a field name for the new virtual field and makes sure it doesn't collide with a field in the base table
1116
+ const candidateName = this.stripID(ef.Name);
1117
+ // Skip if candidateName is empty (e.g., field named exactly "ID")
1118
+ // This happens in table-per-type inheritance where child.ID is FK to parent.ID
1119
+ // stripID("ID") returns "" which would generate invalid SQL: AS []
1120
+ if (candidateName.trim().length === 0) {
1121
+ logStatus(` Skipping related entity name field for ${ef.Name} in entity - stripID returned empty string (likely inheritance pattern)`);
1122
+ }
1123
+ else {
1124
+ // check to make sure candidateName is not already a field name in the base table (other than a virtual field of course, as that is what we're creating)
1125
+ // because if it is, we need to change it to something else
1126
+ const bFound = entityFields.find(f => f.IsVirtual === false && f.Name.trim().toLowerCase() === candidateName.trim().toLowerCase()) !== undefined ||
1127
+ allGeneratedAliases.some(a => a.toLowerCase() === candidateName.trim().toLowerCase());
1128
+ const safeAlias = bFound ? candidateName + '_Virtual' : candidateName;
1129
+ ef._RelatedEntityNameFieldMap = safeAlias;
1130
+ ef._RelatedEntityJoinFieldMappings.push({
1131
+ sourceField: nameField,
1132
+ alias: safeAlias,
1133
+ isVirtual: nameFieldIsVirtual
1134
+ });
1135
+ allGeneratedAliases.push(safeAlias);
1136
+ if (nameFieldIsVirtual)
1137
+ anyFieldIsVirtual = true;
1138
+ // check to see if the database already knows about the RelatedEntityNameFieldMap or not
1139
+ if (ef.RelatedEntityNameFieldMap === null ||
1140
+ ef.RelatedEntityNameFieldMap === undefined ||
1141
+ ef.RelatedEntityNameFieldMap.trim().length === 0) {
1142
+ // the database doesn't yet know about this RelatedEntityNameFieldMap, so we need to update it
1143
+ // first update the actul field in the metadata object so it can be used from this point forward
1144
+ // and it also reflects what the DB will hold
1145
+ ef.RelatedEntityNameFieldMap = ef._RelatedEntityNameFieldMap;
1146
+ // then update the database itself
1147
+ await manageMD.updateEntityFieldRelatedEntityNameFieldMap(pool, ef.ID, ef.RelatedEntityNameFieldMap);
1148
+ }
1149
+ }
1118
1150
  }
1119
- // check to make sure candidateName is not already a field name in the base table (other than a virtual field of course, as that is what we're creating)
1120
- // because if it is, we need to change it to something else
1121
- const bFound = entityFields.find(f => f.IsVirtual === false && f.Name.trim().toLowerCase() === candidateName.trim().toLowerCase()) !== undefined;
1122
- if (bFound)
1123
- ef._RelatedEntityNameFieldMap = candidateName + '_Virtual';
1124
- else
1125
- ef._RelatedEntityNameFieldMap = candidateName;
1126
- // now we have a safe field name alias for the new virtual field in the _RelatedEntityNameFieldMap property, so use it...
1127
- sOutput += `${fieldCount === 0 ? '' : ','}\n ${ef._RelatedEntityTableAlias}.[${nameField}] AS [${ef._RelatedEntityNameFieldMap}]`;
1128
- // check to see if the database already knows about the RelatedEntityNameFieldMap or not
1129
- if (ef.RelatedEntityNameFieldMap === null ||
1130
- ef.RelatedEntityNameFieldMap === undefined ||
1131
- ef.RelatedEntityNameFieldMap.trim().length === 0) {
1132
- // the database doesn't yet know about this RelatedEntityNameFieldMap, so we need to update it
1133
- // first update the actul field in the metadata object so it can be used from this point forward
1134
- // and it also reflects what the DB will hold
1135
- ef.RelatedEntityNameFieldMap = ef._RelatedEntityNameFieldMap;
1136
- // then update the database itself
1137
- await manageMD.updateEntityFieldRelatedEntityNameFieldMap(pool, ef.ID, ef.RelatedEntityNameFieldMap);
1151
+ }
1152
+ // 2. Handle configured additional fields
1153
+ if (config.fields && config.fields.length > 0) {
1154
+ const currentEntity = md.Entities.find(e => e.ID === ef.EntityID);
1155
+ for (const fieldConfig of config.fields) {
1156
+ const fieldName = fieldConfig.field;
1157
+ const alias = fieldConfig.alias || this.generateDefaultAlias(ef.Name, fieldName);
1158
+ // Validate field exists on related entity
1159
+ if (!this.validateFieldExists(ef.RelatedEntity, fieldName)) {
1160
+ logError(`RelatedEntityJoinFields: Field '${fieldName}' not found on entity '${ef.RelatedEntity}' (FK: ${ef.Name})`);
1161
+ continue;
1162
+ }
1163
+ // Check for alias collisions
1164
+ if (currentEntity && this.hasAliasCollision(currentEntity, alias, allGeneratedAliases)) {
1165
+ logError(`RelatedEntityJoinFields: Alias '${alias}' for field '${fieldName}' would collide with an existing field or alias in entity '${currentEntity.Name}'`);
1166
+ continue;
1167
+ }
1168
+ // Get field metadata from related entity to check if virtual
1169
+ const relatedEntity = md.Entities.find(e => e.Name === ef.RelatedEntity);
1170
+ const relatedField = relatedEntity?.Fields.find(f => f.Name.toLowerCase() === fieldName.toLowerCase());
1171
+ const isVirtual = relatedField?.IsVirtual || false;
1172
+ ef._RelatedEntityJoinFieldMappings.push({
1173
+ sourceField: fieldName,
1174
+ alias: alias,
1175
+ isVirtual: isVirtual
1176
+ });
1177
+ allGeneratedAliases.push(alias);
1178
+ if (isVirtual)
1179
+ anyFieldIsVirtual = true;
1180
+ }
1181
+ }
1182
+ // 3. Generate SQL for the mappings
1183
+ if (ef._RelatedEntityJoinFieldMappings.length > 0) {
1184
+ ef._RelatedEntityTableAlias = ef.RelatedEntityClassName + '_' + ef.Name;
1185
+ ef._RelatedEntityNameFieldIsVirtual = anyFieldIsVirtual;
1186
+ for (const mapping of ef._RelatedEntityJoinFieldMappings) {
1187
+ sOutput += `${fieldCount === 0 ? '' : ','}\n ${ef._RelatedEntityTableAlias}.[${mapping.sourceField}] AS [${mapping.alias}]`;
1188
+ fieldCount++;
1138
1189
  }
1139
- fieldCount++;
1140
1190
  }
1141
1191
  }
1142
1192
  return sOutput;
1143
1193
  }
1144
1194
  getIsNameFieldForSingleEntity(entityName) {
1145
- const md = new core_1.Metadata(); // use the full metadata entity list, not the filtered version that we receive
1195
+ const md = new Metadata(); // use the full metadata entity list, not the filtered version that we receive
1146
1196
  const e = md.Entities.find(e => e.Name === entityName);
1147
1197
  if (e) {
1148
1198
  const ef = e.NameField;
@@ -1150,7 +1200,7 @@ ${whereClause}GO${permissions}
1150
1200
  return { nameField: ef.Name, nameFieldIsVirtual: ef.IsVirtual };
1151
1201
  }
1152
1202
  else
1153
- (0, status_logging_1.logStatus)(`ERROR: Could not find entity with name ${entityName}`);
1203
+ logStatus(`ERROR: Could not find entity with name ${entityName}`);
1154
1204
  return { nameField: '', nameFieldIsVirtual: false };
1155
1205
  }
1156
1206
  stripID(name) {
@@ -1159,13 +1209,40 @@ ${whereClause}GO${permissions}
1159
1209
  else
1160
1210
  return name;
1161
1211
  }
1212
+ generateDefaultAlias(fkFieldName, relatedFieldName) {
1213
+ const baseName = this.stripID(fkFieldName);
1214
+ if (baseName.toLowerCase() === relatedFieldName.toLowerCase()) {
1215
+ return baseName;
1216
+ }
1217
+ return baseName + relatedFieldName;
1218
+ }
1219
+ validateFieldExists(entityName, fieldName) {
1220
+ const md = new Metadata();
1221
+ const entity = md.Entities.find(e => e.Name === entityName);
1222
+ if (!entity)
1223
+ return false;
1224
+ return entity.Fields.some(f => f.Name.toLowerCase() === fieldName.toLowerCase());
1225
+ }
1226
+ hasAliasCollision(entity, alias, generatedAliases) {
1227
+ // Check against existing fields in the entity (non-virtual fields first)
1228
+ if (entity.Fields.some(f => !f.IsVirtual && f.Name.toLowerCase() === alias.toLowerCase()))
1229
+ return true;
1230
+ // Check against other generated aliases in this view
1231
+ if (generatedAliases.some(a => a.toLowerCase() === alias.toLowerCase()))
1232
+ return true;
1233
+ // Check against system fields
1234
+ const systemFields = ['__mj_CreatedAt', '__mj_UpdatedAt', EntityInfo.DeletedAtFieldName];
1235
+ if (systemFields.some(sf => sf?.toLowerCase() === alias.toLowerCase()))
1236
+ return true;
1237
+ return false;
1238
+ }
1162
1239
  generateSPPermissions(entity, spName, type) {
1163
1240
  let sOutput = '';
1164
1241
  for (let i = 0; i < entity.Permissions.length; i++) {
1165
1242
  const ep = entity.Permissions[i];
1166
- if ((type == exports.SPType.Create && ep.CanCreate) ||
1167
- (type == exports.SPType.Update && ep.CanUpdate) ||
1168
- (type == exports.SPType.Delete && ep.CanDelete)) {
1243
+ if ((type == SPType.Create && ep.CanCreate) ||
1244
+ (type == SPType.Update && ep.CanUpdate) ||
1245
+ (type == SPType.Delete && ep.CanDelete)) {
1169
1246
  if (ep.RoleSQLName && ep.RoleSQLName.length > 0) {
1170
1247
  sOutput += (sOutput === '' ? `GRANT EXECUTE ON [${entity.SchemaName}].[${spName}] TO ` : ', ') + `[${ep.RoleSQLName}]`;
1171
1248
  }
@@ -1191,7 +1268,7 @@ ${whereClause}GO${permissions}
1191
1268
  //double exclamations used on the firstKey.DefaultValue property otherwise the type of this variable is 'number | ""';
1192
1269
  const primaryKeyAutomatic = firstKey.AutoIncrement; // Only exclude auto-increment fields, allow manual override for all other PKs including UUIDs with defaults
1193
1270
  const efString = this.createEntityFieldsParamString(entity.Fields, false); // Always pass false for isUpdate since this is generateSPCreate
1194
- const permissions = this.generateSPPermissions(entity, spName, exports.SPType.Create);
1271
+ const permissions = this.generateSPPermissions(entity, spName, SPType.Create);
1195
1272
  let preInsertCode = '';
1196
1273
  let outputCode = '';
1197
1274
  let selectInsertedRecord = '';
@@ -1294,12 +1371,12 @@ GO${permissions}
1294
1371
  `;
1295
1372
  }
1296
1373
  generateUpdatedAtTrigger(entity) {
1297
- const updatedAtField = entity.Fields.find(f => f.Name.toLowerCase().trim() === core_1.EntityInfo.UpdatedAtFieldName.toLowerCase().trim());
1374
+ const updatedAtField = entity.Fields.find(f => f.Name.toLowerCase().trim() === EntityInfo.UpdatedAtFieldName.toLowerCase().trim());
1298
1375
  if (!updatedAtField)
1299
1376
  return '';
1300
1377
  const triggerStatement = `
1301
1378
  ------------------------------------------------------------
1302
- ----- TRIGGER FOR ${core_1.EntityInfo.UpdatedAtFieldName} field for the ${entity.BaseTable} table
1379
+ ----- TRIGGER FOR ${EntityInfo.UpdatedAtFieldName} field for the ${entity.BaseTable} table
1303
1380
  ------------------------------------------------------------
1304
1381
  IF OBJECT_ID('[${entity.SchemaName}].[trgUpdate${entity.ClassName}]', 'TR') IS NOT NULL
1305
1382
  DROP TRIGGER [${entity.SchemaName}].[trgUpdate${entity.ClassName}];
@@ -1313,7 +1390,7 @@ BEGIN
1313
1390
  UPDATE
1314
1391
  [${entity.SchemaName}].[${entity.BaseTable}]
1315
1392
  SET
1316
- ${core_1.EntityInfo.UpdatedAtFieldName} = GETUTCDATE()
1393
+ ${EntityInfo.UpdatedAtFieldName} = GETUTCDATE()
1317
1394
  FROM
1318
1395
  [${entity.SchemaName}].[${entity.BaseTable}] AS _organicTable
1319
1396
  INNER JOIN
@@ -1326,8 +1403,8 @@ GO`;
1326
1403
  generateSPUpdate(entity) {
1327
1404
  const spName = entity.spUpdate ? entity.spUpdate : `spUpdate${entity.BaseTableCodeName}`;
1328
1405
  const efParamString = this.createEntityFieldsParamString(entity.Fields, true);
1329
- const permissions = this.generateSPPermissions(entity, spName, exports.SPType.Update);
1330
- const hasUpdatedAtField = entity.Fields.find(f => f.Name.toLowerCase().trim() === core_1.EntityInfo.UpdatedAtFieldName.trim().toLowerCase()) !== undefined;
1406
+ const permissions = this.generateSPPermissions(entity, spName, SPType.Update);
1407
+ const hasUpdatedAtField = entity.Fields.find(f => f.Name.toLowerCase().trim() === EntityInfo.UpdatedAtFieldName.trim().toLowerCase()) !== undefined;
1331
1408
  const updatedAtTrigger = hasUpdatedAtField ? this.generateUpdatedAtTrigger(entity) : '';
1332
1409
  let selectUpdatedRecord = `SELECT
1333
1410
  *
@@ -1521,7 +1598,7 @@ ${updatedAtTrigger}
1521
1598
  generateSPDelete(entity) {
1522
1599
  const spName = entity.spDelete ? entity.spDelete : `spDelete${entity.BaseTableCodeName}`;
1523
1600
  const sCascadeDeletes = this.generateCascadeDeletes(entity);
1524
- const permissions = this.generateSPPermissions(entity, spName, exports.SPType.Delete);
1601
+ const permissions = this.generateSPPermissions(entity, spName, SPType.Delete);
1525
1602
  let sVariables = '';
1526
1603
  let sSelect = '';
1527
1604
  for (let k of entity.PrimaryKeys) {
@@ -1546,8 +1623,8 @@ ${deleteCode}`;
1546
1623
  deleteCode = ` UPDATE
1547
1624
  [${entity.SchemaName}].[${entity.BaseTable}]
1548
1625
  SET
1549
- ${core_1.EntityInfo.DeletedAtFieldName} = GETUTCDATE()
1550
- ${deleteCode} AND ${core_1.EntityInfo.DeletedAtFieldName} IS NULL -- don't update the record if it's already been deleted via a soft delete`;
1626
+ ${EntityInfo.DeletedAtFieldName} = GETUTCDATE()
1627
+ ${deleteCode} AND ${EntityInfo.DeletedAtFieldName} IS NULL -- don't update the record if it's already been deleted via a soft delete`;
1551
1628
  }
1552
1629
  // Build the NULL select statement for when no rows are affected
1553
1630
  let sNullSelect = '';
@@ -1584,7 +1661,7 @@ GO${permissions}
1584
1661
  generateCascadeDeletes(entity) {
1585
1662
  let sOutput = '';
1586
1663
  if (entity.CascadeDeletes) {
1587
- const md = new core_1.Metadata();
1664
+ const md = new Metadata();
1588
1665
  // Find all fields in other entities that are foreign keys to this entity
1589
1666
  for (const e of md.Entities) {
1590
1667
  for (const ef of e.Fields) {
@@ -1614,7 +1691,7 @@ GO${permissions}
1614
1691
  // Nullable FK but no update API - this is a configuration error
1615
1692
  const sqlComment = `WARNING: ${relatedEntity.BaseTable} has nullable FK to ${parentEntity.BaseTable} but doesn't allow update API - cascade operation will fail`;
1616
1693
  const consoleMsg = `WARNING in spDelete${parentEntity.BaseTableCodeName} generation: ${relatedEntity.BaseTable} has nullable FK to ${parentEntity.BaseTable} but doesn't allow update API - cascade operation will fail`;
1617
- (0, status_logging_1.logWarning)(consoleMsg);
1694
+ logWarning(consoleMsg);
1618
1695
  return `
1619
1696
  -- ${sqlComment}
1620
1697
  -- This will cause the delete operation to fail due to referential integrity`;
@@ -1623,7 +1700,7 @@ GO${permissions}
1623
1700
  // Entity doesn't allow delete API, so we can't cascade delete
1624
1701
  const sqlComment = `WARNING: ${relatedEntity.BaseTable} has non-nullable FK to ${parentEntity.BaseTable} but doesn't allow delete API - cascade operation will fail`;
1625
1702
  const consoleMsg = `WARNING in spDelete${parentEntity.BaseTableCodeName} generation: ${relatedEntity.BaseTable} has non-nullable FK to ${parentEntity.BaseTable} but doesn't allow delete API - cascade operation will fail`;
1626
- (0, status_logging_1.logWarning)(consoleMsg);
1703
+ logWarning(consoleMsg);
1627
1704
  return `
1628
1705
  -- ${sqlComment}
1629
1706
  -- This will cause a referential integrity violation`;
@@ -1637,7 +1714,7 @@ GO${permissions}
1637
1714
  // Generate unique cursor name using entity code names
1638
1715
  const cursorName = `cascade_${operation}_${relatedEntity.CodeName}_cursor`;
1639
1716
  // Determine which SP to call
1640
- const spType = operation === 'delete' ? exports.SPType.Delete : exports.SPType.Update;
1717
+ const spType = operation === 'delete' ? SPType.Delete : SPType.Update;
1641
1718
  const spName = this.getSPName(relatedEntity, spType);
1642
1719
  if (operation === 'update') {
1643
1720
  // For update, we need to include all updateable fields
@@ -1731,7 +1808,7 @@ GO${permissions}
1731
1808
  fetchInto = pkComponents.fetchInto;
1732
1809
  allParams = pkComponents.spParams;
1733
1810
  // Then, add all updateable fields with the same prefix
1734
- const sortedFields = (0, util_1.sortBySequenceAndCreatedAt)(entity.Fields);
1811
+ const sortedFields = sortBySequenceAndCreatedAt(entity.Fields);
1735
1812
  for (const ef of sortedFields) {
1736
1813
  if (!ef.IsPrimaryKey && !ef.IsVirtual && ef.AllowUpdateAPI && !ef.AutoIncrement && !ef.IsSpecialDateField) {
1737
1814
  if (declarations !== '')
@@ -1757,7 +1834,7 @@ GO${permissions}
1757
1834
  */
1758
1835
  analyzeCascadeDeleteDependencies(entity) {
1759
1836
  if (entity.CascadeDeletes) {
1760
- const md = new core_1.Metadata();
1837
+ const md = new Metadata();
1761
1838
  // Find all fields in other entities that are foreign keys to this entity
1762
1839
  for (const e of md.Entities) {
1763
1840
  for (const ef of e.Fields) {
@@ -1799,7 +1876,7 @@ GO${permissions}
1799
1876
  async buildCascadeDeleteDependencies(entities) {
1800
1877
  // Clear existing dependencies
1801
1878
  this.cascadeDeleteDependencies.clear();
1802
- (0, status_logging_1.logStatus)(`Building cascade delete dependencies...`);
1879
+ logStatus(`Building cascade delete dependencies...`);
1803
1880
  // Analyze cascade deletes for each entity with CascadeDeletes=true
1804
1881
  // This will populate the cascadeDeleteDependencies map WITHOUT generating SQL
1805
1882
  let entitiesWithCascadeDeletes = 0;
@@ -1807,23 +1884,23 @@ GO${permissions}
1807
1884
  if (entity.CascadeDeletes) {
1808
1885
  entitiesWithCascadeDeletes++;
1809
1886
  if (entity.spDeleteGenerated) {
1810
- (0, status_logging_1.logStatus)(` Analyzing cascade deletes for ${entity.Name}...`);
1887
+ logStatus(` Analyzing cascade deletes for ${entity.Name}...`);
1811
1888
  this.analyzeCascadeDeleteDependencies(entity);
1812
1889
  }
1813
1890
  else {
1814
- (0, status_logging_1.logStatus)(` Skipping ${entity.Name} - has CascadeDeletes but spDeleteGenerated=false`);
1891
+ logStatus(` Skipping ${entity.Name} - has CascadeDeletes but spDeleteGenerated=false`);
1815
1892
  }
1816
1893
  }
1817
1894
  }
1818
- (0, status_logging_1.logStatus)(`Total entities with CascadeDeletes=true: ${entitiesWithCascadeDeletes}`);
1895
+ logStatus(`Total entities with CascadeDeletes=true: ${entitiesWithCascadeDeletes}`);
1819
1896
  // Log the dependency map
1820
- (0, status_logging_1.logStatus)(`Cascade delete dependency map built:`);
1897
+ logStatus(`Cascade delete dependency map built:`);
1821
1898
  for (const [dependedOnEntityId, dependentEntityIds] of this.cascadeDeleteDependencies) {
1822
1899
  const dependedOnEntity = entities.find(e => e.ID === dependedOnEntityId);
1823
1900
  const dependentNames = Array.from(dependentEntityIds)
1824
1901
  .map(id => entities.find(e => e.ID === id)?.Name || id)
1825
1902
  .join(', ');
1826
- (0, status_logging_1.logStatus)(` ${dependedOnEntity?.Name || dependedOnEntityId} is depended on by: ${dependentNames}`);
1903
+ logStatus(` ${dependedOnEntity?.Name || dependedOnEntityId} is depended on by: ${dependentNames}`);
1827
1904
  }
1828
1905
  }
1829
1906
  /**
@@ -1833,8 +1910,8 @@ GO${permissions}
1833
1910
  async getModifiedEntitiesWithUpdateAPI(entities) {
1834
1911
  const modifiedEntitiesMap = new Map();
1835
1912
  // Get the list of modified entity names from the metadata management phase
1836
- const modifiedEntityNames = manage_metadata_1.ManageMetadataBase.modifiedEntityList;
1837
- (0, status_logging_1.logStatus)(`Modified entities from metadata phase: ${modifiedEntityNames.join(', ')}`);
1913
+ const modifiedEntityNames = ManageMetadataBase.modifiedEntityList;
1914
+ logStatus(`Modified entities from metadata phase: ${modifiedEntityNames.join(', ')}`);
1838
1915
  // Convert entity names to IDs and filter for those with update API
1839
1916
  for (const entityName of modifiedEntityNames) {
1840
1917
  const entity = entities.find(e => e.Name === entityName &&
@@ -1842,15 +1919,15 @@ GO${permissions}
1842
1919
  e.spUpdateGenerated);
1843
1920
  if (entity) {
1844
1921
  modifiedEntitiesMap.set(entity.Name, entity.ID);
1845
- (0, status_logging_1.logStatus)(` - ${entity.Name} (${entity.ID}) has update API and will be tracked`);
1922
+ logStatus(` - ${entity.Name} (${entity.ID}) has update API and will be tracked`);
1846
1923
  }
1847
1924
  else {
1848
1925
  const nonUpdateEntity = entities.find(e => e.Name === entityName);
1849
1926
  if (nonUpdateEntity) {
1850
- (0, status_logging_1.logStatus)(` - ${entityName} found but AllowUpdateAPI=${nonUpdateEntity.AllowUpdateAPI}, spUpdateGenerated=${nonUpdateEntity.spUpdateGenerated}`);
1927
+ logStatus(` - ${entityName} found but AllowUpdateAPI=${nonUpdateEntity.AllowUpdateAPI}, spUpdateGenerated=${nonUpdateEntity.spUpdateGenerated}`);
1851
1928
  }
1852
1929
  else {
1853
- (0, status_logging_1.logStatus)(` - ${entityName} not found in entities list`);
1930
+ logStatus(` - ${entityName} not found in entities list`);
1854
1931
  }
1855
1932
  }
1856
1933
  }
@@ -1884,38 +1961,38 @@ GO${permissions}
1884
1961
  // Get entities that were modified during metadata management
1885
1962
  const modifiedEntitiesMap = await this.getModifiedEntitiesWithUpdateAPI(entities);
1886
1963
  if (modifiedEntitiesMap.size > 0) {
1887
- (0, status_logging_1.logStatus)(`Found ${modifiedEntitiesMap.size} entities with schema changes affecting update SPs`);
1964
+ logStatus(`Found ${modifiedEntitiesMap.size} entities with schema changes affecting update SPs`);
1888
1965
  // Convert map values to set of IDs
1889
1966
  const changedEntityIds = new Set(modifiedEntitiesMap.values());
1890
1967
  // Find entities that need delete SP regeneration
1891
1968
  const entitiesNeedingRegeneration = await this.getEntitiesRequiringCascadeDeleteRegeneration(pool, changedEntityIds);
1892
1969
  if (entitiesNeedingRegeneration.size > 0) {
1893
- (0, status_logging_1.logStatus)(`Identified ${entitiesNeedingRegeneration.size} entities requiring delete SP regeneration due to cascade dependencies`);
1970
+ logStatus(`Identified ${entitiesNeedingRegeneration.size} entities requiring delete SP regeneration due to cascade dependencies`);
1894
1971
  // Store the entity IDs that need regeneration (only if spDeleteGenerated=true)
1895
1972
  for (const entityId of entitiesNeedingRegeneration) {
1896
1973
  const entity = entities.find(e => e.ID === entityId);
1897
1974
  if (entity && entity.spDeleteGenerated) {
1898
1975
  this.entitiesNeedingDeleteSPRegeneration.add(entityId);
1899
- (0, status_logging_1.logStatus)(` - Marked ${entity.Name} for delete SP regeneration (cascade dependency)`);
1976
+ logStatus(` - Marked ${entity.Name} for delete SP regeneration (cascade dependency)`);
1900
1977
  }
1901
1978
  else if (entity && !entity.spDeleteGenerated) {
1902
- (0, status_logging_1.logStatus)(` - Skipping ${entity.Name} - has cascade dependency but spDeleteGenerated=false (custom SP)`);
1979
+ logStatus(` - Skipping ${entity.Name} - has cascade dependency but spDeleteGenerated=false (custom SP)`);
1903
1980
  }
1904
1981
  }
1905
1982
  // Order entities by dependencies for proper regeneration
1906
1983
  this.orderedEntitiesForDeleteSPRegeneration = this.orderEntitiesByDependencies(entities, this.entitiesNeedingDeleteSPRegeneration);
1907
1984
  if (this.orderedEntitiesForDeleteSPRegeneration.length > 0) {
1908
- (0, status_logging_1.logStatus)(`Ordered entities for delete SP regeneration:`);
1985
+ logStatus(`Ordered entities for delete SP regeneration:`);
1909
1986
  this.orderedEntitiesForDeleteSPRegeneration.forEach((entityId, index) => {
1910
1987
  const entity = entities.find(e => e.ID === entityId);
1911
- (0, status_logging_1.logStatus)(` ${index + 1}. ${entity?.Name || entityId}`);
1988
+ logStatus(` ${index + 1}. ${entity?.Name || entityId}`);
1912
1989
  });
1913
1990
  }
1914
1991
  }
1915
1992
  }
1916
1993
  }
1917
1994
  catch (error) {
1918
- (0, status_logging_1.logError)(`Error in cascade delete dependency analysis: ${error}`);
1995
+ logError(`Error in cascade delete dependency analysis: ${error}`);
1919
1996
  // Continue with normal processing even if dependency analysis fails
1920
1997
  }
1921
1998
  }
@@ -1956,7 +2033,7 @@ GO${permissions}
1956
2033
  if (visiting.has(entityId)) {
1957
2034
  // Circular dependency detected - mark it but don't fail
1958
2035
  const entity = entities.find(e => e.ID === entityId);
1959
- (0, status_logging_1.logStatus)(`Warning: Circular cascade delete dependency detected involving ${entity?.Name || entityId}`);
2036
+ logStatus(`Warning: Circular cascade delete dependency detected involving ${entity?.Name || entityId}`);
1960
2037
  circularDeps.add(entityId);
1961
2038
  return false; // Signal circular dependency but continue processing
1962
2039
  }
@@ -1982,17 +2059,16 @@ GO${permissions}
1982
2059
  if (!success && circularDeps.has(entityId)) {
1983
2060
  // Entity is part of circular dependency - add it anyway in arbitrary order
1984
2061
  // The SQL will still be generated, just not in perfect dependency order
1985
- (0, status_logging_1.logStatus)(` - Adding ${entities.find(e => e.ID === entityId)?.Name || entityId} despite circular dependency`);
2062
+ logStatus(` - Adding ${entities.find(e => e.ID === entityId)?.Name || entityId} despite circular dependency`);
1986
2063
  visited.add(entityId);
1987
2064
  ordered.push(entityId);
1988
2065
  }
1989
2066
  }
1990
2067
  }
1991
2068
  if (circularDeps.size > 0) {
1992
- (0, status_logging_1.logStatus)(`Note: ${circularDeps.size} entities have circular cascade delete dependencies and will be regenerated in arbitrary order.`);
2069
+ logStatus(`Note: ${circularDeps.size} entities have circular cascade delete dependencies and will be regenerated in arbitrary order.`);
1993
2070
  }
1994
2071
  return ordered;
1995
2072
  }
1996
2073
  }
1997
- exports.SQLCodeGenBase = SQLCodeGenBase;
1998
2074
  //# sourceMappingURL=sql_codegen.js.map