@memberjunction/codegen-lib 2.115.0 → 2.116.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 (39) hide show
  1. package/dist/Angular/angular-codegen.d.ts +50 -3
  2. package/dist/Angular/angular-codegen.d.ts.map +1 -1
  3. package/dist/Angular/angular-codegen.js +448 -93
  4. package/dist/Angular/angular-codegen.js.map +1 -1
  5. package/dist/Angular/related-entity-components.d.ts +6 -0
  6. package/dist/Angular/related-entity-components.d.ts.map +1 -1
  7. package/dist/Angular/related-entity-components.js +7 -0
  8. package/dist/Angular/related-entity-components.js.map +1 -1
  9. package/dist/Angular/user-view-grid-related-entity-component.d.ts.map +1 -1
  10. package/dist/Angular/user-view-grid-related-entity-component.js +12 -4
  11. package/dist/Angular/user-view-grid-related-entity-component.js.map +1 -1
  12. package/dist/Config/config.d.ts +0 -18
  13. package/dist/Config/config.d.ts.map +1 -1
  14. package/dist/Config/config.js +2 -2
  15. package/dist/Config/config.js.map +1 -1
  16. package/dist/Database/manage-metadata.d.ts +48 -7
  17. package/dist/Database/manage-metadata.d.ts.map +1 -1
  18. package/dist/Database/manage-metadata.js +358 -142
  19. package/dist/Database/manage-metadata.js.map +1 -1
  20. package/dist/Database/sql.d.ts.map +1 -1
  21. package/dist/Database/sql.js +134 -28
  22. package/dist/Database/sql.js.map +1 -1
  23. package/dist/Database/sql_codegen.d.ts.map +1 -1
  24. package/dist/Database/sql_codegen.js +3 -2
  25. package/dist/Database/sql_codegen.js.map +1 -1
  26. package/dist/Misc/action_subclasses_codegen.d.ts.map +1 -1
  27. package/dist/Misc/action_subclasses_codegen.js +5 -2
  28. package/dist/Misc/action_subclasses_codegen.js.map +1 -1
  29. package/dist/Misc/advanced_generation.d.ts +77 -22
  30. package/dist/Misc/advanced_generation.d.ts.map +1 -1
  31. package/dist/Misc/advanced_generation.js +248 -142
  32. package/dist/Misc/advanced_generation.js.map +1 -1
  33. package/dist/Misc/entity_subclasses_codegen.d.ts.map +1 -1
  34. package/dist/Misc/entity_subclasses_codegen.js +6 -1
  35. package/dist/Misc/entity_subclasses_codegen.js.map +1 -1
  36. package/dist/runCodeGen.d.ts.map +1 -1
  37. package/dist/runCodeGen.js +7 -0
  38. package/dist/runCodeGen.js.map +1 -1
  39. package/package.json +10 -10
@@ -38,7 +38,6 @@ const uuid_1 = require("uuid");
38
38
  const fs = __importStar(require("fs"));
39
39
  const path_1 = __importDefault(require("path"));
40
40
  const sql_logging_1 = require("../Misc/sql_logging");
41
- const aiengine_1 = require("@memberjunction/aiengine");
42
41
  class ValidatorResult {
43
42
  entityName = "";
44
43
  fieldName;
@@ -99,7 +98,7 @@ class ManageMetadataBase {
99
98
  let bSuccess = true;
100
99
  let start = new Date();
101
100
  (0, status_logging_1.logStatus)(' Creating new entities...');
102
- if (!await this.createNewEntities(pool)) {
101
+ if (!await this.createNewEntities(pool, currentUser)) {
103
102
  (0, status_logging_1.logError)(' Error creating new entities');
104
103
  bSuccess = false;
105
104
  }
@@ -133,7 +132,8 @@ class ManageMetadataBase {
133
132
  (0, status_logging_1.logStatus)(` > Recompiled base views in ${(new Date().getTime() - start.getTime()) / 1000} seconds`);
134
133
  start = new Date();
135
134
  (0, status_logging_1.logStatus)(' Managing entity fields...');
136
- if (!await this.manageEntityFields(pool, excludeSchemas, false, false, currentUser)) {
135
+ // note that we skip Advanced Generation here because we do it again later when the manageSQLScriptsAndExecution occurs in SQLCodeGen class
136
+ if (!await this.manageEntityFields(pool, excludeSchemas, false, false, currentUser, true)) {
137
137
  (0, status_logging_1.logError)(' Error managing entity fields');
138
138
  bSuccess = false;
139
139
  }
@@ -146,13 +146,20 @@ class ManageMetadataBase {
146
146
  }
147
147
  (0, status_logging_1.logStatus)(` > Managed entity relationships in ${(new Date().getTime() - start.getTime()) / 1000} seconds`);
148
148
  if (ManageMetadataBase.newEntityList.length > 0) {
149
- await this.generateNewEntityDescriptions(pool, md); // don't pass excludeSchemas becuase by definition this is the NEW entities we created
149
+ await this.generateNewEntityDescriptions(pool, md, currentUser); // don't pass excludeSchemas becuase by definition this is the NEW entities we created
150
150
  }
151
151
  const veResult = await this.manageVirtualEntities(pool);
152
152
  if (!veResult.success) {
153
153
  (0, status_logging_1.logError)(' Error managing virtual entities');
154
154
  bSuccess = false;
155
155
  }
156
+ start = new Date();
157
+ (0, status_logging_1.logStatus)(' Syncing schema info from database...');
158
+ if (!await this.updateSchemaInfoFromDatabase(pool, excludeSchemas)) {
159
+ (0, status_logging_1.logError)(' Error syncing schema info');
160
+ bSuccess = false;
161
+ }
162
+ (0, status_logging_1.logStatus)(` > Synced schema info in ${(new Date().getTime() - start.getTime()) / 1000} seconds`);
156
163
  return bSuccess;
157
164
  }
158
165
  async manageVirtualEntities(pool) {
@@ -484,7 +491,7 @@ class ManageMetadataBase {
484
491
  * @param excludeSchemas
485
492
  * @returns
486
493
  */
487
- async manageEntityFields(pool, excludeSchemas, skipCreatedAtUpdatedAtDeletedAtFieldValidation, skipEntityFieldValues, currentUser) {
494
+ async manageEntityFields(pool, excludeSchemas, skipCreatedAtUpdatedAtDeletedAtFieldValidation, skipEntityFieldValues, currentUser, skipAdvancedGeneration) {
488
495
  let bSuccess = true;
489
496
  const startTime = new Date();
490
497
  if (!skipCreatedAtUpdatedAtDeletedAtFieldValidation) {
@@ -540,6 +547,15 @@ class ManageMetadataBase {
540
547
  }
541
548
  (0, status_logging_1.logStatus)(` Managed entity field values in ${(new Date().getTime() - step6StartTime.getTime()) / 1000} seconds`);
542
549
  }
550
+ // Advanced Generation - Smart field identification and form layout
551
+ if (!skipAdvancedGeneration) {
552
+ const step7StartTime = new Date();
553
+ if (!await this.applyAdvancedGeneration(pool, excludeSchemas, currentUser)) {
554
+ (0, status_logging_1.logError)('Error applying advanced generation features');
555
+ // Don't fail the entire process - advanced generation is optional
556
+ }
557
+ (0, status_logging_1.logStatus)(` Applied advanced generation features in ${(new Date().getTime() - step7StartTime.getTime()) / 1000} seconds`);
558
+ }
543
559
  (0, status_logging_1.logStatus)(` Total time to manage entity fields: ${(new Date().getTime() - startTime.getTime()) / 1000} seconds`);
544
560
  return bSuccess;
545
561
  }
@@ -740,58 +756,26 @@ class ManageMetadataBase {
740
756
  * to be invoked, the EntityDescriptions feature must be enabled in the Advanced Generation configuration.
741
757
  * @param pool
742
758
  * @param md
759
+ * @param currentUser
743
760
  */
744
- async generateNewEntityDescriptions(pool, md) {
761
+ async generateNewEntityDescriptions(pool, md, currentUser) {
745
762
  // for the list of new entities, go through and attempt to generate new entity descriptions
746
763
  const ag = new advanced_generation_1.AdvancedGeneration();
747
764
  if (ag.featureEnabled('EntityDescriptions')) {
748
765
  // we have the feature enabled, so let's loop through the new entities and generate descriptions for them
749
- const llm = ag.LLM;
750
- const prompt = ag.getPrompt('EntityDescriptions');
751
- const systemPrompt = prompt.systemPrompt;
752
- const userMessage = prompt.userMessage + '\n\n';
753
- // now loop through the new entities and generate descriptions for them
754
766
  for (let e of ManageMetadataBase.newEntityList) {
755
767
  const dataResult = await pool.request().query(`SELECT * FROM [${(0, config_1.mj_core_schema)()}].vwEntities WHERE Name = '${e}'`);
756
768
  const data = dataResult.recordset;
757
769
  const fieldsResult = await pool.request().query(`SELECT * FROM [${(0, config_1.mj_core_schema)()}].vwEntityFields WHERE EntityID='${data[0].ID}'`);
758
770
  const fields = fieldsResult.recordset;
759
- const entityUserMessage = userMessage + `Entity Name: ${e},
760
- Base Table: ${data[0].BaseTable},
761
- Schema: ${data[0].SchemaName}.
762
- Fields:
763
- ${fields.map((f) => ` ${f.Name}: ${f.Type}`).join('\n')}`;
764
- const result = await llm.ChatCompletion({
765
- model: ag.AIModel,
766
- messages: [
767
- {
768
- role: 'system',
769
- content: systemPrompt
770
- },
771
- {
772
- role: 'user',
773
- content: entityUserMessage
774
- }
775
- ]
776
- });
777
- if (result?.success) {
778
- const resultText = result?.data.choices[0].message.content;
779
- try {
780
- const structuredResult = JSON.parse(resultText);
781
- if (structuredResult?.entityDescription && structuredResult.entityDescription.length > 0) {
782
- const sSQL = `UPDATE [${(0, config_1.mj_core_schema)()}].Entity SET Description = '${structuredResult.entityDescription}' WHERE Name = '${e}'`;
783
- await this.LogSQLAndExecute(pool, sSQL, `SQL text to update entity description for entity ${e}`);
784
- }
785
- else {
786
- console.warn(' >>> Advanced Generation Error: LLM returned a blank entity description, skipping entity description for entity ' + e);
787
- }
788
- }
789
- catch (e) {
790
- console.warn(' >>> Advanced Generation Error: LLM returned invalid result, skipping entity description for entity ' + e + '. Result from LLM: ' + resultText, e);
791
- }
771
+ // Use new API to generate entity description
772
+ const result = await ag.generateEntityDescription(e, data[0].BaseTable, fields.map((f) => ({ Name: f.Name, Type: f.Type, IsNullable: f.AllowsNull, Description: f.Description })), currentUser);
773
+ if (result?.entityDescription && result.entityDescription.length > 0) {
774
+ const sSQL = `UPDATE [${(0, config_1.mj_core_schema)()}].Entity SET Description = '${result.entityDescription}' WHERE Name = '${e}'`;
775
+ await this.LogSQLAndExecute(pool, sSQL, `SQL text to update entity description for entity ${e}`);
792
776
  }
793
777
  else {
794
- console.warn(' >>> Advanced Generation Error: LLM call failed, skipping entity description for entity ' + e);
778
+ console.warn(' >>> Advanced Generation Error: LLM returned invalid result, skipping entity description for entity ' + e);
795
779
  }
796
780
  }
797
781
  }
@@ -1197,6 +1181,28 @@ NumberedRows AS (
1197
1181
  return false;
1198
1182
  }
1199
1183
  }
1184
+ /**
1185
+ * Syncs SchemaInfo records from database schemas, capturing extended properties as descriptions.
1186
+ * Creates new SchemaInfo records for schemas that don't exist yet and updates descriptions
1187
+ * from schema extended properties for existing records.
1188
+ * @param pool - SQL connection pool
1189
+ * @param excludeSchemas - Array of schema names to exclude from processing
1190
+ * @returns Promise<boolean> - true if successful, false otherwise
1191
+ */
1192
+ async updateSchemaInfoFromDatabase(pool, excludeSchemas) {
1193
+ try {
1194
+ const sSQL = `EXEC [${(0, config_1.mj_core_schema)()}].spUpdateSchemaInfoFromDatabase @ExcludedSchemaNames='${excludeSchemas.join(',')}'`;
1195
+ const result = await this.LogSQLAndExecute(pool, sSQL, `SQL text to sync schema info from database schemas`, true);
1196
+ if (result && result.length > 0) {
1197
+ (0, status_logging_1.logStatus)(` > Updated/created ${result.length} SchemaInfo records`);
1198
+ }
1199
+ return true;
1200
+ }
1201
+ catch (e) {
1202
+ (0, status_logging_1.logError)(e);
1203
+ return false;
1204
+ }
1205
+ }
1200
1206
  async deleteUnneededEntityFields(pool, excludeSchemas) {
1201
1207
  try {
1202
1208
  const sSQL = `EXEC [${(0, config_1.mj_core_schema)()}].spDeleteUnneededEntityFields @ExcludedSchemaNames='${excludeSchemas.join(',')}'`;
@@ -1349,65 +1355,21 @@ NumberedRows AS (
1349
1355
  }
1350
1356
  try {
1351
1357
  if (generateNewCode && config_1.configInfo.advancedGeneration?.enableAdvancedGeneration && config_1.configInfo.advancedGeneration?.features.find(f => f.name === 'ParseCheckConstraints' && f.enabled)) {
1352
- // feature is enabled, so let's call the LLM to generate a function for us
1358
+ // feature is enabled, so let's call the AI to generate a function for us
1353
1359
  const ag = new advanced_generation_1.AdvancedGeneration();
1354
- const llm = ag.LLM;
1355
- if (!llm) {
1356
- // we weren't able to get an LLM instance, so log an error and return
1357
- (0, status_logging_1.logError)(' >>> Error generating validator function from check constraint. Unable to get an LLM instance.');
1358
- return returnResult;
1359
- }
1360
- await aiengine_1.AIEngine.Instance.Config(false, currentUser); // make sure metadata loaded
1361
- const model = aiengine_1.AIEngine.Instance.Models.find(m => {
1362
- const modelMatch = m.APINameOrName.trim().toLowerCase() === ag.AIModel.trim().toLowerCase();
1363
- if (!modelMatch)
1364
- return false;
1365
- // Check if model has a vendor matching the specified vendor
1366
- const hasVendor = aiengine_1.AIEngine.Instance.ModelVendors.some(mv => mv.ModelID === m.ID &&
1367
- mv.Vendor.trim().toLowerCase() === ag.AIVendor.trim().toLowerCase() &&
1368
- mv.Status === 'Active');
1369
- return hasVendor;
1370
- });
1371
- if (!model)
1372
- throw new Error(` >>> Error generating validator function from check constraint. Unable to find AI Model with name ${ag.AIModel} and vendor ${ag.AIVendor}.`);
1373
- const prompt = ag.getPrompt('CheckConstraintParser');
1374
1360
  const entityFieldListInfo = allEntityFields.filter(item => item.Entity.trim().toLowerCase() === data.EntityName.trim().toLowerCase()).map(item => ` * ${item.Name} - ${item.Type}${item.AllowsNull ? ' (nullable)' : ' (not null)'}`).join('\n');
1375
- const existingMethodNameBlock = generatedValidationFunctionName ? `Existing Method Name: ${generatedValidationFunctionName}\n Please reuse this SAME method name for the new generation` : '';
1376
- const markedUpSysPrompt = ag.fillTemplate(prompt.systemPrompt, {
1377
- ENTITY_FIELD_LIST: entityFieldListInfo,
1378
- EXISTING_METHOD_NAME: existingMethodNameBlock
1379
- }); // prompt.systemPrompt.replace(/{{ENTITY_FIELD_LIST}}/g, entityFieldListInfo);
1380
- const result = await llm.ChatCompletion({
1381
- messages: [
1382
- {
1383
- role: 'system',
1384
- content: markedUpSysPrompt
1385
- },
1386
- {
1387
- role: 'user',
1388
- content: `${constraintDefinition}`
1389
- }
1390
- ],
1391
- model: ag.AIModel,
1392
- responseFormat: 'JSON'
1393
- });
1394
- if (result && result.success) {
1395
- const jsonResult = result.data.choices[0].message.content;
1396
- const structuredResult = (0, global_1.SafeJSONParse)(jsonResult);
1397
- if (structuredResult?.Description && structuredResult?.Code && structuredResult?.MethodName) {
1398
- returnResult.functionText = structuredResult.Code;
1399
- returnResult.functionName = structuredResult.MethodName;
1400
- returnResult.functionDescription = structuredResult.Description;
1401
- returnResult.aiModelID = model.ID;
1402
- returnResult.wasGenerated = true; // we just generated this code
1403
- returnResult.success = true;
1404
- }
1405
- else {
1406
- (0, status_logging_1.logError)(`Error generating field validator function from check constraint for entity ${entityName} and field ${fieldName}. LLM returned invalid result.`);
1407
- }
1361
+ // Use new API to parse check constraint
1362
+ const result = await ag.parseCheckConstraint(constraintDefinition, entityFieldListInfo, generatedValidationFunctionName, currentUser);
1363
+ if (result?.Description && result?.Code && result?.MethodName) {
1364
+ returnResult.functionText = result.Code;
1365
+ returnResult.functionName = result.MethodName;
1366
+ returnResult.functionDescription = result.Description;
1367
+ returnResult.aiModelID = result.ModelID;
1368
+ returnResult.wasGenerated = true; // we just generated this code
1369
+ returnResult.success = true;
1408
1370
  }
1409
1371
  else {
1410
- (0, status_logging_1.logError)(`Error generating field validator function from check constraint for entity ${entityName} and field ${fieldName}. LLM call failed.`);
1372
+ (0, status_logging_1.logError)(`Error generating field validator function from check constraint for entity ${entityName} and field ${fieldName}. LLM returned invalid result.`);
1411
1373
  }
1412
1374
  }
1413
1375
  }
@@ -1552,7 +1514,7 @@ NumberedRows AS (
1552
1514
  (sExcludeSchemas.length > 0 ? (sExcludeTables.length > 0 ? ` AND ` : ``) + '(' + sExcludeSchemas + ')' : '');
1553
1515
  return sWhere;
1554
1516
  }
1555
- async createNewEntities(pool) {
1517
+ async createNewEntities(pool, currentUser) {
1556
1518
  try {
1557
1519
  const sSQL = `SELECT * FROM [${(0, config_1.mj_core_schema)()}].vwSQLTablesAndEntities WHERE EntityID IS NULL ` + this.createExcludeTablesAndSchemasFilter('');
1558
1520
  const newEntitiesResult = await pool.request().query(sSQL);
@@ -1565,7 +1527,7 @@ NumberedRows AS (
1565
1527
  // wrap in a transaction so we get all of it or none of it
1566
1528
  for (let i = 0; i < newEntities.length; ++i) {
1567
1529
  // process each of the new entities
1568
- await this.createNewEntity(pool, newEntities[i], md);
1530
+ await this.createNewEntity(pool, newEntities[i], md, currentUser);
1569
1531
  }
1570
1532
  await transaction.commit();
1571
1533
  }
@@ -1606,10 +1568,10 @@ NumberedRows AS (
1606
1568
  return { shouldCreate: false, validationMessage: errorMsg };
1607
1569
  }
1608
1570
  }
1609
- async createNewEntityName(newEntity) {
1571
+ async createNewEntityName(newEntity, currentUser) {
1610
1572
  const ag = new advanced_generation_1.AdvancedGeneration();
1611
1573
  if (ag.featureEnabled('EntityNames')) {
1612
- return this.newEntityNameWithAdvancedGeneration(ag, newEntity);
1574
+ return this.newEntityNameWithAdvancedGeneration(ag, newEntity, currentUser);
1613
1575
  }
1614
1576
  else {
1615
1577
  return this.simpleNewEntityName(newEntity.SchemaName, newEntity.TableName);
@@ -1636,44 +1598,13 @@ NumberedRows AS (
1636
1598
  }
1637
1599
  return null; // nothing to do here, the DisplayName can be null as it will just end up being the entity name
1638
1600
  }
1639
- async newEntityNameWithAdvancedGeneration(ag, newEntity) {
1640
- // get the LLM for this entity
1641
- const chat = ag.LLM;
1642
- const prompt = ag.getPrompt('EntityNames');
1643
- const systemPrompt = ag.fillTemplate(prompt.systemPrompt, newEntity);
1644
- const userMessage = ag.fillTemplate(prompt.userMessage, newEntity);
1645
- const result = await chat.ChatCompletion({
1646
- model: ag.AIModel,
1647
- messages: [
1648
- {
1649
- role: 'system',
1650
- content: systemPrompt
1651
- },
1652
- {
1653
- role: 'user',
1654
- content: userMessage
1655
- }
1656
- ]
1657
- });
1658
- if (result?.success) {
1659
- const resultText = result?.data.choices[0].message.content;
1660
- try {
1661
- const structuredResult = JSON.parse(resultText);
1662
- if (structuredResult?.entityName) {
1663
- return this.markupEntityName(newEntity.SchemaName, structuredResult.entityName);
1664
- }
1665
- else {
1666
- console.warn(' >>> Advanced Generation Error: LLM returned a blank entity name, falling back to simple generated entity name');
1667
- return this.simpleNewEntityName(newEntity.SchemaName, newEntity.TableName);
1668
- }
1669
- }
1670
- catch (e) {
1671
- console.warn(' >>> Advanced Generation Error: LLM returned invalid result, falling back to simple generated entity name. Result from LLM: ' + resultText, e);
1672
- return this.simpleNewEntityName(newEntity.SchemaName, newEntity.TableName);
1673
- }
1601
+ async newEntityNameWithAdvancedGeneration(ag, newEntity, currentUser) {
1602
+ const result = await ag.generateEntityName(newEntity.TableName, currentUser);
1603
+ if (result?.entityName) {
1604
+ return this.markupEntityName(newEntity.SchemaName, result.entityName);
1674
1605
  }
1675
1606
  else {
1676
- console.warn(' >>> Advanced Generation Error: LLM call failed, falling back to simple generated entity name.');
1607
+ console.warn(' >>> Advanced Generation Error: LLM returned invalid result, falling back to simple generated entity name');
1677
1608
  return this.simpleNewEntityName(newEntity.SchemaName, newEntity.TableName);
1678
1609
  }
1679
1610
  }
@@ -1712,12 +1643,12 @@ NumberedRows AS (
1712
1643
  createNewUUID() {
1713
1644
  return (0, uuid_1.v4)();
1714
1645
  }
1715
- async createNewEntity(pool, newEntity, md) {
1646
+ async createNewEntity(pool, newEntity, md, currentUser) {
1716
1647
  try {
1717
1648
  const { shouldCreate, validationMessage } = await this.shouldCreateNewEntity(pool, newEntity);
1718
1649
  if (shouldCreate) {
1719
1650
  // process a single new entity
1720
- let newEntityName = await this.createNewEntityName(newEntity);
1651
+ let newEntityName = await this.createNewEntityName(newEntity, currentUser);
1721
1652
  const newEntityDisplayName = this.createNewEntityDisplayName(newEntity, newEntityName);
1722
1653
  let suffix = '';
1723
1654
  const existingEntity = md.Entities.find(e => e.Name.toLowerCase() === newEntityName.toLowerCase());
@@ -1897,6 +1828,291 @@ NumberedRows AS (
1897
1828
  `;
1898
1829
  return sSQLInsert;
1899
1830
  }
1831
+ /**
1832
+ * Apply Advanced Generation features - Smart Field Identification and Form Layout Generation
1833
+ */
1834
+ async applyAdvancedGeneration(pool, excludeSchemas, currentUser) {
1835
+ try {
1836
+ const ag = new advanced_generation_1.AdvancedGeneration();
1837
+ if (!ag.enabled) {
1838
+ return true;
1839
+ }
1840
+ // Get list of entities to process
1841
+ // If forceRegeneration.enabled is true, process ALL entities
1842
+ // Otherwise, only process new or modified entities
1843
+ let entitiesToProcess = [];
1844
+ let whereClause = '';
1845
+ if (config_1.configInfo.forceRegeneration?.enabled) {
1846
+ // Force regeneration mode - process all entities (or filtered by entityWhereClause)
1847
+ (0, status_logging_1.logStatus)(` Force regeneration enabled - processing all entities...`);
1848
+ whereClause = 'e.VirtualEntity = 0';
1849
+ if (config_1.configInfo.forceRegeneration.entityWhereClause && config_1.configInfo.forceRegeneration.entityWhereClause.trim().length > 0) {
1850
+ whereClause += ` AND (${config_1.configInfo.forceRegeneration.entityWhereClause})`;
1851
+ (0, status_logging_1.logStatus)(` Filtered by: ${config_1.configInfo.forceRegeneration.entityWhereClause}`);
1852
+ }
1853
+ whereClause += ` AND e.SchemaName NOT IN (${excludeSchemas.map(s => `'${s}'`).join(',')})`;
1854
+ }
1855
+ else {
1856
+ // Normal mode - only process new or modified entities
1857
+ // Deduplicate in case an entity appears in both lists
1858
+ entitiesToProcess = [
1859
+ ...new Set([
1860
+ ...ManageMetadataBase.newEntityList,
1861
+ ...ManageMetadataBase.modifiedEntityList
1862
+ ])
1863
+ ];
1864
+ if (entitiesToProcess.length === 0) {
1865
+ return true;
1866
+ }
1867
+ (0, status_logging_1.logStatus)(` Advanced Generation enabled, processing ${entitiesToProcess.length} entities...`);
1868
+ whereClause = `e.VirtualEntity = 0 AND e.Name IN (${entitiesToProcess.map(name => `'${name}'`).join(',')}) AND e.SchemaName NOT IN (${excludeSchemas.map(s => `'${s}'`).join(',')})`;
1869
+ }
1870
+ // Get entity details for entities that need processing
1871
+ const entitiesSQL = `
1872
+ SELECT
1873
+ e.ID,
1874
+ e.Name,
1875
+ e.Description,
1876
+ e.SchemaName,
1877
+ e.BaseTable
1878
+ FROM
1879
+ [${(0, config_1.mj_core_schema)()}].vwEntities e
1880
+ WHERE
1881
+ ${whereClause}
1882
+ ORDER BY
1883
+ e.Name
1884
+ `;
1885
+ const entitiesResult = await pool.request().query(entitiesSQL);
1886
+ const entities = entitiesResult.recordset;
1887
+ if (entities.length === 0) {
1888
+ return true;
1889
+ }
1890
+ // Get ALL fields for ALL entities in a single query
1891
+ const entityIds = entities.map((e) => `'${e.ID}'`).join(',');
1892
+ const fieldsSQL = `
1893
+ SELECT
1894
+ ef.EntityID,
1895
+ ef.ID,
1896
+ ef.Name,
1897
+ ef.Type,
1898
+ ef.AllowsNull,
1899
+ ef.IsPrimaryKey,
1900
+ ef.IsUnique,
1901
+ ef.Description,
1902
+ ef.AutoUpdateIsNameField,
1903
+ ef.AutoUpdateDefaultInView,
1904
+ ef.AutoUpdateCategory
1905
+ FROM
1906
+ [${(0, config_1.mj_core_schema)()}].vwEntityFields ef
1907
+ WHERE
1908
+ ef.EntityID IN (${entityIds})
1909
+ ORDER BY
1910
+ ef.EntityID,
1911
+ ef.Sequence
1912
+ `;
1913
+ const fieldsResult = await pool.request().query(fieldsSQL);
1914
+ const allFields = fieldsResult.recordset;
1915
+ // Process entities in batches with parallelization
1916
+ return await this.processEntitiesBatched(pool, entities, allFields, ag, currentUser);
1917
+ }
1918
+ catch (error) {
1919
+ (0, status_logging_1.logError)(`Advanced Generation failed: ${error}`);
1920
+ return false;
1921
+ }
1922
+ }
1923
+ /**
1924
+ * Process entities in batches with parallel execution
1925
+ * @param pool Database connection pool
1926
+ * @param entities Entities to process
1927
+ * @param allFields All fields for all entities (will be filtered per entity)
1928
+ * @param ag AdvancedGeneration instance
1929
+ * @param currentUser User context
1930
+ * @param batchSize Number of entities to process in parallel (default 5)
1931
+ */
1932
+ async processEntitiesBatched(pool, entities, allFields, ag, currentUser, batchSize = 5) {
1933
+ let processedCount = 0;
1934
+ let errorCount = 0;
1935
+ // Process in batches
1936
+ for (let i = 0; i < entities.length; i += batchSize) {
1937
+ const batch = entities.slice(i, i + batchSize);
1938
+ // Process batch in parallel
1939
+ const batchResults = await Promise.allSettled(batch.map(entity => this.processEntityAdvancedGeneration(pool, entity, allFields, ag, currentUser)));
1940
+ // Tally results
1941
+ for (const result of batchResults) {
1942
+ if (result.status === 'fulfilled') {
1943
+ processedCount++;
1944
+ }
1945
+ else {
1946
+ errorCount++;
1947
+ (0, status_logging_1.logError)(` Error processing entity: ${result.reason}`);
1948
+ }
1949
+ }
1950
+ (0, status_logging_1.logStatus)(` Progress: ${processedCount}/${entities.length} entities processed`);
1951
+ }
1952
+ (0, status_logging_1.logStatus)(` Advanced Generation complete: ${processedCount} entities processed, ${errorCount} errors`);
1953
+ return errorCount === 0;
1954
+ }
1955
+ /**
1956
+ * Process advanced generation for a single entity
1957
+ * @param pool Database connection pool
1958
+ * @param entity Entity to process
1959
+ * @param allFields All fields for all entities (will be filtered for this entity)
1960
+ * @param ag AdvancedGeneration instance
1961
+ * @param currentUser User context
1962
+ */
1963
+ async processEntityAdvancedGeneration(pool, entity, allFields, ag, currentUser) {
1964
+ // Filter fields for this entity (client-side filtering)
1965
+ const fields = allFields.filter((f) => f.EntityID === entity.ID);
1966
+ // Smart Field Identification
1967
+ // Only run if at least one field allows auto-update
1968
+ if (fields.some((f) => f.AutoUpdateIsNameField || f.AutoUpdateDefaultInView)) {
1969
+ const fieldAnalysis = await ag.identifyFields({
1970
+ Name: entity.Name,
1971
+ Description: entity.Description,
1972
+ Fields: fields
1973
+ }, currentUser);
1974
+ if (fieldAnalysis) {
1975
+ await this.applySmartFieldIdentification(pool, entity.ID, fields, fieldAnalysis);
1976
+ }
1977
+ }
1978
+ // Form Layout Generation
1979
+ // Only run if at least one field allows auto-update
1980
+ if (fields.some((f) => f.AutoUpdateCategory)) {
1981
+ const layoutAnalysis = await ag.generateFormLayout({
1982
+ Name: entity.Name,
1983
+ Description: entity.Description,
1984
+ Fields: fields
1985
+ }, currentUser);
1986
+ if (layoutAnalysis) {
1987
+ await this.applyFormLayout(pool, entity.ID, fields, layoutAnalysis);
1988
+ (0, status_logging_1.logStatus)(` Applied form layout for ${entity.Name}`);
1989
+ }
1990
+ }
1991
+ }
1992
+ /**
1993
+ * Apply smart field identification results to entity fields
1994
+ */
1995
+ async applySmartFieldIdentification(pool, entityId, fields, result) {
1996
+ const sqlStatements = [];
1997
+ // Find the name field (exactly one)
1998
+ const nameField = fields.find(f => f.Name === result.nameField);
1999
+ if (nameField && nameField.AutoUpdateIsNameField && nameField.ID) {
2000
+ sqlStatements.push(`
2001
+ UPDATE [${(0, config_1.mj_core_schema)()}].EntityField
2002
+ SET IsNameField = 1
2003
+ WHERE ID = '${nameField.ID}'
2004
+ AND AutoUpdateIsNameField = 1
2005
+ `);
2006
+ }
2007
+ else if (!nameField) {
2008
+ (0, status_logging_1.logError)(`Smart field identification returned invalid nameField: '${result.nameField}' not found in entity fields`);
2009
+ }
2010
+ // Find all default in view fields (one or more)
2011
+ const defaultInViewFields = fields.filter(f => result.defaultInView.includes(f.Name) && f.AutoUpdateDefaultInView && f.ID);
2012
+ // Warn about any fields that weren't found
2013
+ const missingFields = result.defaultInView.filter(name => !fields.some(f => f.Name === name));
2014
+ if (missingFields.length > 0) {
2015
+ (0, status_logging_1.logError)(`Smart field identification returned invalid defaultInView fields: ${missingFields.join(', ')} not found in entity`);
2016
+ }
2017
+ // Build update statements for all default in view fields
2018
+ for (const field of defaultInViewFields) {
2019
+ sqlStatements.push(`
2020
+ UPDATE [${(0, config_1.mj_core_schema)()}].EntityField
2021
+ SET DefaultInView = 1
2022
+ WHERE ID = '${field.ID}'
2023
+ AND AutoUpdateDefaultInView = 1
2024
+ `);
2025
+ }
2026
+ // Execute all updates in one batch
2027
+ if (sqlStatements.length > 0) {
2028
+ const combinedSQL = sqlStatements.join('\n');
2029
+ await this.LogSQLAndExecute(pool, combinedSQL, `Set field properties for entity`, false);
2030
+ }
2031
+ }
2032
+ /**
2033
+ * Apply form layout generation results to set category on entity fields
2034
+ */
2035
+ async applyFormLayout(pool, entityId, fields, result) {
2036
+ // Collect all SQL statements for batch execution
2037
+ const sqlStatements = [];
2038
+ // Assign category to each field
2039
+ for (const fieldCategory of result.fieldCategories) {
2040
+ const field = fields.find(f => f.Name === fieldCategory.fieldName);
2041
+ if (field && field.AutoUpdateCategory && field.ID) {
2042
+ // Override category to "System Metadata" for __mj_ fields (system audit fields)
2043
+ let category = fieldCategory.category;
2044
+ if (field.Name.startsWith('__mj_')) {
2045
+ category = 'System Metadata';
2046
+ }
2047
+ const updateSQL = `UPDATE [${(0, config_1.mj_core_schema)()}].EntityField
2048
+ SET Category = '${category.replace(/'/g, "''")}',
2049
+ GeneratedFormSection = 'Category'
2050
+ WHERE ID = '${field.ID}'
2051
+ AND AutoUpdateCategory = 1`;
2052
+ sqlStatements.push(updateSQL);
2053
+ }
2054
+ else if (!field) {
2055
+ (0, status_logging_1.logError)(`Form layout generation returned invalid fieldName: '${fieldCategory.fieldName}' not found in entity`);
2056
+ }
2057
+ }
2058
+ // Execute all field updates in one batch
2059
+ if (sqlStatements.length > 0) {
2060
+ const combinedSQL = sqlStatements.join('\n');
2061
+ await this.LogSQLAndExecute(pool, combinedSQL, `Set categories for ${sqlStatements.length} fields`, false);
2062
+ }
2063
+ // Store entity icon if provided and entity doesn't already have one
2064
+ if (result.entityIcon && result.entityIcon.trim().length > 0) {
2065
+ // Check if entity already has an icon
2066
+ const checkEntitySQL = `
2067
+ SELECT Icon FROM [${(0, config_1.mj_core_schema)()}].Entity
2068
+ WHERE ID = '${entityId}'
2069
+ `;
2070
+ const entityCheck = await pool.request().query(checkEntitySQL);
2071
+ if (entityCheck.recordset.length > 0) {
2072
+ const currentIcon = entityCheck.recordset[0].Icon;
2073
+ // Only update if entity doesn't have an icon set
2074
+ if (!currentIcon || currentIcon.trim().length === 0) {
2075
+ const escapedIcon = result.entityIcon.replace(/'/g, "''");
2076
+ const updateEntitySQL = `
2077
+ UPDATE [${(0, config_1.mj_core_schema)()}].Entity
2078
+ SET Icon = '${escapedIcon}',
2079
+ __mj_UpdatedAt = GETUTCDATE()
2080
+ WHERE ID = '${entityId}'
2081
+ `;
2082
+ await this.LogSQLAndExecute(pool, updateEntitySQL, `Set entity icon to ${result.entityIcon}`, false);
2083
+ (0, status_logging_1.logStatus)(` ✓ Set entity icon: ${result.entityIcon}`);
2084
+ }
2085
+ }
2086
+ }
2087
+ // Store category icons in EntitySetting if provided
2088
+ if (result.categoryIcons && Object.keys(result.categoryIcons).length > 0) {
2089
+ const iconsJSON = JSON.stringify(result.categoryIcons).replace(/'/g, "''");
2090
+ // First check if setting already exists
2091
+ const checkSQL = `
2092
+ SELECT ID FROM [${(0, config_1.mj_core_schema)()}].EntitySetting
2093
+ WHERE EntityID = '${entityId}' AND Name = 'FieldCategoryIcons'
2094
+ `;
2095
+ const existing = await pool.request().query(checkSQL);
2096
+ if (existing.recordset.length > 0) {
2097
+ // Update existing setting
2098
+ const updateSQL = `
2099
+ UPDATE [${(0, config_1.mj_core_schema)()}].EntitySetting
2100
+ SET Value = '${iconsJSON}',
2101
+ __mj_UpdatedAt = GETUTCDATE()
2102
+ WHERE EntityID = '${entityId}' AND Name = 'FieldCategoryIcons'
2103
+ `;
2104
+ await this.LogSQLAndExecute(pool, updateSQL, `Update FieldCategoryIcons setting for entity`, false);
2105
+ }
2106
+ else {
2107
+ // Insert new setting
2108
+ const insertSQL = `
2109
+ INSERT INTO [${(0, config_1.mj_core_schema)()}].EntitySetting (ID, EntityID, Name, Value, __mj_CreatedAt, __mj_UpdatedAt)
2110
+ VALUES (NEWID(), '${entityId}', 'FieldCategoryIcons', '${iconsJSON}', GETUTCDATE(), GETUTCDATE())
2111
+ `;
2112
+ await this.LogSQLAndExecute(pool, insertSQL, `Insert FieldCategoryIcons setting for entity`, false);
2113
+ }
2114
+ }
2115
+ }
1900
2116
  /**
1901
2117
  * Executes the given SQL query using the given ConnectionPool object.
1902
2118
  * If the appendToLogFile parameter is true, the query will also be appended to the log file.